All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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