
org.http4k.lens.lensSpec.kt Maven / Gradle / Ivy
package org.http4k.lens
import org.http4k.lens.ParamMeta.ArrayParam
import org.http4k.lens.ParamMeta.BooleanParam
import org.http4k.lens.ParamMeta.EnumParam
import org.http4k.lens.ParamMeta.IntegerParam
import org.http4k.lens.ParamMeta.NumberParam
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeFormatter.ISO_LOCAL_DATE
import java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME
import java.time.format.DateTimeFormatter.ISO_LOCAL_TIME
import java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME
import java.time.format.DateTimeFormatter.ISO_OFFSET_TIME
import java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME
class LensGet private constructor(private val getFn: (String, IN) -> List) {
operator fun invoke(name: String) = { target: IN -> getFn(name, target) }
fun map(nextFn: (OUT) -> NEXT) = LensGet { x, y: IN -> getFn(x, y).map(nextFn) }
companion object {
operator fun invoke(getFn: (String, IN) -> List): LensGet = LensGet(getFn)
}
}
class LensSet private constructor(private val setFn: (String, List, IN) -> IN) {
operator fun invoke(name: String) = { values: List, target: IN -> setFn(name, values, target) }
fun map(nextFn: (NEXT) -> OUT) = LensSet { a, b: List, c: IN -> setFn(a, b.map(nextFn), c) }
companion object {
operator fun invoke(setFn: (String, List, IN) -> IN): LensSet = LensSet(setFn)
}
}
/**
* Common construction patterns for all lens implementations.
*/
interface LensBuilder {
/**
* Make a concrete Lens for this spec that looks for an optional value in the target.
*/
fun optional(name: String, description: String? = null): Lens
/**
* Make a concrete Lens for this spec that looks for a required value in the target.
*/
fun required(name: String, description: String? = null): Lens
/**
* Make a concrete Lens for this spec that falls back to the default value if no value is found in the target.
*/
fun defaulted(name: String, default: OUT, description: String? = null): Lens
/**
* Make a concrete Lens for this spec that falls back to another lens if no value is found in the target.
*/
fun defaulted(name: String, default: Lens, description: String? = null): Lens
}
/**
* Represents a uni-directional extraction of an entity from a target.
*/
open class LensSpec(
val location: String,
protected val paramMeta: ParamMeta,
internal val get: LensGet
) : LensBuilder {
/**
* Create another LensSpec which applies the uni-directional transformation to the result. Any resultant Lens can only be
* used to extract the final type from a target.
*/
fun map(nextIn: (OUT) -> NEXT) = LensSpec(location, paramMeta, get.map(nextIn))
override fun defaulted(name: String, default: OUT, description: String?): Lens =
defaulted(name, Lens(Meta(false, location, paramMeta, name, description)) { default }, description)
override fun defaulted(name: String, default: Lens, description: String?): Lens {
val getLens = get(name)
return Lens(
Meta(
false,
location,
paramMeta,
name,
description
)
) { getLens(it).run { if (isEmpty()) default(it) else first() } }
}
override fun optional(name: String, description: String?): Lens {
val getLens = get(name)
return Lens(
Meta(
false,
location,
paramMeta,
name,
description
)
) { getLens(it).run { if (isEmpty()) null else first() } }
}
override fun required(name: String, description: String?): Lens {
val meta = Meta(true, location, paramMeta, name, description)
val getLens = get(name)
return Lens(meta) { getLens(it).firstOrNull() ?: throw LensFailure(listOf(Missing(meta)), target = it) }
}
open val multi = object : LensBuilder> {
override fun defaulted(name: String, default: List, description: String?): Lens> =
defaulted(
name,
Lens(Meta(false, location, ArrayParam(paramMeta), name, description)) { default },
description
)
override fun defaulted(name: String, default: Lens>, description: String?): Lens> {
val getLens = get(name)
return Lens(
Meta(
false,
location,
ArrayParam(paramMeta),
name,
description
)
) { getLens(it).run { ifEmpty { default(it) } } }
}
override fun optional(name: String, description: String?): Lens?> {
val getLens = get(name)
return Lens(
Meta(
false,
location,
ArrayParam(paramMeta),
name,
description
)
) { getLens(it).run { ifEmpty { null } } }
}
override fun required(name: String, description: String?): Lens> {
val getLens = get(name)
return Lens(Meta(true, location, ArrayParam(paramMeta), name, description)) {
getLens(it).run {
ifEmpty {
throw LensFailure(
Missing(Meta(true, location, paramMeta, name, description)),
target = it
)
}
}
}
}
}
}
/**
* Represents a bi-directional extraction of a list of entities from a target, or an insertion into a target.
*/
interface BiDiMultiLensSpec : BiDiLensBuilder> {
override fun defaulted(name: String, default: List, description: String?): BiDiLens>
override fun optional(name: String, description: String?): BiDiLens?>
override fun required(name: String, description: String?): BiDiLens>
}
interface BiDiLensBuilder : LensBuilder {
override fun optional(name: String, description: String?): BiDiLens
override fun required(name: String, description: String?): BiDiLens
override fun defaulted(name: String, default: OUT, description: String?): BiDiLens
override fun defaulted(name: String, default: Lens, description: String?): BiDiLens
}
/**
* Represents a bi-directional extraction of an entity from a target, or an insertion into a target.
*/
open class BiDiLensSpec(
location: String,
paramMeta: ParamMeta,
get: LensGet,
private val set: LensSet
) : LensSpec(location, paramMeta, get), BiDiLensBuilder {
/**
* Create another BiDiLensSpec which applies the bi-directional transformations to the result. Any resultant Lens can be
* used to extract or insert the final type from/into a target.
*/
fun map(nextIn: (OUT) -> NEXT, nextOut: (NEXT) -> OUT) = mapWithNewMeta(nextIn, nextOut, paramMeta)
fun mapWithNewMeta(nextIn: (OUT) -> NEXT, nextOut: (NEXT) -> OUT, paramMeta: ParamMeta) =
BiDiLensSpec(location, paramMeta, get.map(nextIn), set.map(nextOut))
override fun defaulted(name: String, default: OUT, description: String?) =
defaulted(name, Lens(Meta(false, location, paramMeta, name, description)) { default }, description)
override fun defaulted(name: String, default: Lens, description: String?): BiDiLens {
val getLens = get(name)
val setLens = set(name)
return BiDiLens(Meta(false, location, paramMeta, name, description),
{ getLens(it).run { if (isEmpty()) default(it) else first() } },
{ out: OUT, target: IN -> setLens(out?.let { listOf(it) } ?: emptyList(), target) }
)
}
override fun optional(name: String, description: String?): BiDiLens {
val getLens = get(name)
val setLens = set(name)
return BiDiLens(Meta(false, location, paramMeta, name, description),
{ getLens(it).run { if (isEmpty()) null else first() } },
{ out: OUT?, target: IN -> setLens(out?.let { listOf(it) } ?: emptyList(), target) }
)
}
override fun required(name: String, description: String?): BiDiLens {
val getLens = get(name)
val setLens = set(name)
return BiDiLens(Meta(true, location, paramMeta, name, description),
{
getLens(it).firstOrNull()
?: throw LensFailure(Missing(Meta(true, location, paramMeta, name, description)), target = it)
},
{ out: OUT, target: IN -> setLens(listOf(out), target) })
}
override val multi = object : BiDiMultiLensSpec {
override fun defaulted(name: String, default: List, description: String?): BiDiLens> =
defaulted(
name,
Lens(Meta(false, location, ArrayParam(paramMeta), name, description)) { default },
description
)
override fun defaulted(
name: String,
default: Lens>,
description: String?
): BiDiLens> {
val getLens = get(name)
val setLens = set(name)
return BiDiLens(Meta(false, location, ArrayParam(paramMeta), name, description),
{ getLens(it).run { ifEmpty { default(it) } } },
{ out: List, target: IN -> setLens(out, target) }
)
}
override fun optional(name: String, description: String?): BiDiLens?> {
val getLens = get(name)
val setLens = set(name)
return BiDiLens(Meta(false, location, ArrayParam(paramMeta), name, description),
{ getLens(it).run { ifEmpty { null } } },
{ out: List?, target: IN -> setLens(out ?: emptyList(), target) }
)
}
override fun required(name: String, description: String?): BiDiLens> {
val getLens = get(name)
val setLens = set(name)
return BiDiLens(Meta(true, location, ArrayParam(paramMeta), name, description),
{
getLens(it).run {
ifEmpty {
throw LensFailure(
Missing(
Meta(
true,
location,
ArrayParam(paramMeta),
name,
description
)
), target = it
)
}
}
},
{ out: List, target: IN -> setLens(out, target) })
}
}
}
fun BiDiLensSpec.string() = this
fun BiDiLensSpec.nonEmptyString() = map(StringBiDiMappings.nonEmpty())
fun BiDiLensSpec.int() = mapWithNewMeta(StringBiDiMappings.int(), IntegerParam)
fun BiDiLensSpec.long() = mapWithNewMeta(StringBiDiMappings.long(), IntegerParam)
fun BiDiLensSpec.double() = mapWithNewMeta(StringBiDiMappings.double(), NumberParam)
fun BiDiLensSpec.float() = mapWithNewMeta(StringBiDiMappings.float(), NumberParam)
fun BiDiLensSpec.boolean() = mapWithNewMeta(StringBiDiMappings.boolean(), BooleanParam)
fun BiDiLensSpec.bigInteger() = mapWithNewMeta(StringBiDiMappings.bigInteger(), IntegerParam)
fun BiDiLensSpec.bigDecimal() = mapWithNewMeta(StringBiDiMappings.bigDecimal(), NumberParam)
fun BiDiLensSpec.uuid() = map(StringBiDiMappings.uuid())
fun BiDiLensSpec.uri() = map(StringBiDiMappings.uri())
fun BiDiLensSpec.bytes() = map { s: String -> s.toByteArray() }
fun BiDiLensSpec.regex(pattern: String, group: Int = 1) =
map(StringBiDiMappings.regex(pattern, group))
fun BiDiLensSpec.urlEncoded() = map(StringBiDiMappings.urlEncoded())
fun BiDiLensSpec.regexObject() = map(StringBiDiMappings.regexObject())
fun BiDiLensSpec.duration() = map(StringBiDiMappings.duration())
fun BiDiLensSpec.base64() = map(StringBiDiMappings.base64())
fun BiDiLensSpec.instant() = map(StringBiDiMappings.instant())
fun BiDiLensSpec.yearMonth() = map(StringBiDiMappings.yearMonth())
fun BiDiLensSpec.dateTime(formatter: DateTimeFormatter = ISO_LOCAL_DATE_TIME) =
map(StringBiDiMappings.localDateTime(formatter))
fun BiDiLensSpec.zonedDateTime(formatter: DateTimeFormatter = ISO_ZONED_DATE_TIME) =
map(StringBiDiMappings.zonedDateTime(formatter))
fun BiDiLensSpec.localDate(formatter: DateTimeFormatter = ISO_LOCAL_DATE) =
map(StringBiDiMappings.localDate(formatter))
fun BiDiLensSpec.localTime(formatter: DateTimeFormatter = ISO_LOCAL_TIME) =
map(StringBiDiMappings.localTime(formatter))
fun BiDiLensSpec.offsetTime(formatter: DateTimeFormatter = ISO_OFFSET_TIME) =
map(StringBiDiMappings.offsetTime(formatter))
fun BiDiLensSpec.offsetDateTime(formatter: DateTimeFormatter = ISO_OFFSET_DATE_TIME) =
map(StringBiDiMappings.offsetDateTime(formatter))
fun BiDiLensSpec.zoneId() = map(StringBiDiMappings.zoneId())
fun BiDiLensSpec.zoneOffset() = map(StringBiDiMappings.zoneOffset())
fun BiDiLensSpec.locale() = map(StringBiDiMappings.locale())
fun BiDiLensSpec.basicCredentials() = map(StringBiDiMappings.basicCredentials())
fun BiDiLensSpec.csv(delimiter: String = ",", mapElement: BiDiMapping) =
map(StringBiDiMappings.csv(delimiter, mapElement))
fun BiDiLensSpec.csv(delimiter: String = ",") = csv(delimiter, BiDiMapping({ it }, { it }))
inline fun > BiDiLensSpec.enum() =
mapWithNewMeta(StringBiDiMappings.enum(), EnumParam(T::class))
inline fun > BiDiLensSpec.enum(
noinline nextOut: (String) -> T,
noinline nextIn: (T) -> String
) = mapWithNewMeta(BiDiMapping(nextOut, nextIn), EnumParam(T::class))
fun BiDiLensSpec.mapWithNewMeta(mapping: BiDiMapping, paramMeta: ParamMeta) =
mapWithNewMeta(
mapping::invoke, mapping::invoke, paramMeta
)
fun BiDiLensSpec.map(mapping: BiDiMapping) =
map(mapping::invoke, mapping::invoke)
/**
* This allows creation of a composite object from several values from the same source.
*/
inline fun BiDiLensSpec.composite(crossinline fn: BiDiLensSpec.(TARGET) -> T) =
LensSpec(
T::class.java.name,
ParamMeta.ObjectParam,
LensGet { _, target -> listOf(fn(target)) }).required(T::class.java.name)
inline fun BiDiLensSpec.composite(
crossinline getFn: BiDiLensSpec.(TARGET) -> T,
crossinline setFn: T.(TARGET) -> TARGET
) = BiDiLensSpec(
T::class.java.name, ParamMeta.ObjectParam,
LensGet { _, target -> listOf(getFn(target)) },
LensSet { _, values, target -> values.fold(target) { msg, next: T -> next.setFn(msg) } })
.required(T::class.java.name)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy