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

commonMain.schemas.ClassSchema.kt Maven / Gradle / Ivy

@file:OptIn(ExperimentalTypeInference::class)

package pt.lightweightform.lfkotlin.schemas

import kotlin.experimental.ExperimentalTypeInference
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import pt.lightweightform.lfkotlin.AllowedValues
import pt.lightweightform.lfkotlin.ComputedValue
import pt.lightweightform.lfkotlin.InitialValue
import pt.lightweightform.lfkotlin.IsRequired
import pt.lightweightform.lfkotlin.Schema
import pt.lightweightform.lfkotlin.Validation

/** Function responsible for creating an instance of type `T` from a map of arguments. */
public typealias ConstructorFunction = (arguments: Map) -> T

/**
 * Object holding information about a child of the class schema (class property used to access the
 * child + child schema).
 */
public class ChildInfo(public val prop: KMutableProperty1, public val schema: Schema<*>)

/** Interface representing a schema for a class of type [T]. */
public expect interface ClassSchema : Schema {
    public var kClass: KClass
    public var childInfoByName: Map>
    public var constructorFunction: ConstructorFunction?
}

/**
 * Builder of a class schema. Use [classSchema] or [nullableClassSchema] to build a class schema.
 */
public class ClassSchemaBuilder {
    public val childrenSchemas: MutableMap, Schema<*>> = mutableMapOf()

    /** Declare that the child schema of property [property] is [schema]. */
    public fun  childSchema(property: KMutableProperty1, schema: Schema) {
        childrenSchemas[property] = schema
    }

    /**
     * Declare that the child schema of the receiving property is the one returned by
     * [schemaBuilder].
     */
    public operator fun  KMutableProperty1.invoke(schemaBuilder: () -> Schema) {
        childSchema(this, schemaBuilder())
    }
}

public expect fun  classSchemaImpl(
    kClass: KClass,
    childrenSchemas: Map, Schema<*>>,
    constructorFunction: ConstructorFunction?,
    isNullable: Boolean,
    initialValue: T?,
    computedInitialValue: InitialValue?,
    computedValue: ComputedValue?,
    mismatchedComputedCode: String?,
    isClientOnly: Boolean?,
    isRequired: Boolean?,
    computedIsRequired: IsRequired?,
    isRequiredCode: String?,
    allowedValues: List?,
    computedAllowedValues: AllowedValues?,
    disallowedValueCode: String?,
    validations: List>?,
    initialState: Map?,
    extra: Map?
): ClassSchema

/**
 * Creates a schema for a class of type [T]. Maps to a schema of type "record" in LF.
 *
 * Example defining a `PersonSchema` for a class `Person` with `name` and `married` properties:
 * ```kotlin
 * data class Person(var name: String, var married: Boolean)
 *
 * val personSchema = classSchema {
 *     Person::name { stringSchema() }
 *     Person::married { booleanSchema() }
 * }
 * ```
 *
 * When running validations and computed values, LF-Kotlin converts LF "records" (which are simple
 * JS objects) into proper instances of the Kotlin class being represented by the schema. By
 * default, this conversion occurs by attempting to call a constructor of the class with the value
 * of each child as an argument, in the order they were defined in the schema. I.e., in the example
 * above, LF-Kotlin will attempt to create a `Person` from a JS object `{name: "Pat", married:
 * true}` by calling `Person("Pat", true)`.
 *
 * If the class' constructor has its parameters in a different order or requires other arguments,
 * then the user must provide a [constructorFunction] function, instructing how to properly
 * construct the class.
 */
public inline fun  classSchema(
    noinline constructorFunction: ConstructorFunction? = null,
    initialValue: T? = null,
    computedInitialValue: InitialValue? = null,
    computedValue: ComputedValue? = null,
    mismatchedComputedCode: String? = null,
    isClientOnly: Boolean? = null,
    allowedValues: List? = null,
    computedAllowedValues: AllowedValues? = null,
    disallowedValueCode: String? = null,
    validations: List>? = null,
    extra: Map? = null,
    initialState: Map? = null,
    @BuilderInference classSchemaBuilder: ClassSchemaBuilder.() -> Unit,
): ClassSchema {
    val builder = ClassSchemaBuilder()
    builder.classSchemaBuilder()
    @Suppress("UNCHECKED_CAST")
    return classSchemaImpl(
        T::class,
        builder.childrenSchemas,
        constructorFunction,
        false,
        initialValue,
        computedInitialValue,
        computedValue as ComputedValue?,
        mismatchedComputedCode,
        isClientOnly,
        null,
        null,
        null,
        allowedValues,
        computedAllowedValues,
        disallowedValueCode,
        validations,
        initialState,
        extra
    ) as
        ClassSchema
}

/**
 * Creates a nullable schema for a class of type [T]. Maps to a schema of type "record" with
 * `isNullable` set to `true` in LF.
 *
 * Example defining a `PersonSchema` for an optional class `Person` with `name` and `married`
 * properties:
 * ```kotlin
 * data class Person(var name: String, var married: Boolean)
 *
 * val personSchema = nullableClassSchema {
 *     Person::name { stringSchema() }
 *     Person::married { booleanSchema() }
 * }
 * ```
 *
 * When running validations and computed values, LF-Kotlin converts LF "records" (which are simple
 * JS objects) into proper instances of the Kotlin class being represented by the schema. By
 * default, this conversion occurs by attempting to call a constructor of the class with the value
 * of each child as an argument, in the order they were defined in the schema. I.e., in the example
 * above, LF-Kotlin will attempt to create a `Person` from a JS object `{name: "Pat", married:
 * true}` by calling `Person("Pat", true)`.
 *
 * If the class' constructor has its parameters in a different order or requires other arguments,
 * then the user must provide a [constructorFunction] function, instructing how to properly
 * construct the class.
 */
public inline fun  nullableClassSchema(
    noinline constructorFunction: ConstructorFunction? = null,
    initialValue: T? = null,
    computedInitialValue: InitialValue? = null,
    computedValue: ComputedValue? = null,
    mismatchedComputedCode: String? = null,
    isClientOnly: Boolean? = null,
    isRequired: Boolean? = null,
    computedIsRequired: IsRequired? = null,
    isRequiredCode: String? = null,
    allowedValues: List? = null,
    computedAllowedValues: AllowedValues? = null,
    disallowedValueCode: String? = null,
    validations: List>? = null,
    extra: Map? = null,
    initialState: Map? = null,
    @BuilderInference classSchemaBuilder: ClassSchemaBuilder.() -> Unit,
): ClassSchema {
    val builder = ClassSchemaBuilder()
    builder.classSchemaBuilder()
    return classSchemaImpl(
        T::class,
        builder.childrenSchemas,
        constructorFunction,
        true,
        initialValue,
        computedInitialValue,
        computedValue,
        mismatchedComputedCode,
        isClientOnly,
        isRequired,
        computedIsRequired,
        isRequiredCode,
        allowedValues,
        computedAllowedValues,
        disallowedValueCode,
        validations,
        initialState,
        extra
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy