Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Validation approach #570

Merged
20 commits merged into from
Feb 1, 2022
Merged

New Validation approach #570

20 commits merged into from
Feb 1, 2022

Conversation

jamowei
Copy link
Collaborator

@jamowei jamowei commented Jan 20, 2022

First, you need to specify a Validation for your data-model. Therefore, you can use one of the two new global convenience functions:

// creates a Validation for data-model D with metadata T and validation-messages of M
fun <D, T, M> validation(validate: MutableList<M>.(Inspector<D>, T?) -> Unit): Validation<D, T, M>

// creates a Validation for data-model D and validation-messages of M
fun <D, M> validation(validate: MutableList<M>.(Inspector<D>) -> Unit): Validation<D, Unit, M>

These functions are available in the commonMain source set, so you can create your Validation object right next to your data classes to keep them together. Example:

@Lenses
data class Person(
    val name: String = "",
    val height: Double = 0.0,
) {
    companion object {
        val validation = validation<Person, String> { inspector ->
            if(inspector.data.name.isBlank()) add("Please give the person a name.")
            if(inspector.data.height < 1) add("Please give the person a correct height.")
        }
    }
}

Then you can call your Validation everywhere (e.g. JVM- or JS-site) to get a list of messages which shows if your model is valid or not. We recommend extending your validation messages from the ValidationMessage interface. Then your validation message type must implement the path which is important for matching your message to the corresponding attribute of your data-model and the isError value which is needed to know when your model is valid or not:

data class MyMessage(override val path: String, val text: String) : ValidationMessage {
    override val isError: Boolean = text.startsWith("Error")
}

// change your Validation to use your own validation message
val validation = validation<Person, MyMessage> { inspector ->
    val name = inspector.sub(Person.name())
    if (name.data.isBlank())
        add(MyMessage(name.path, "Error: Please give the person a name."))

    val height = inspector.sub(Person.height())
    if (height.data < 1)
        add(MyMessage(height.path, "Error: Please give the person a correct height."))
}

// then you can use the valid attribute to check if your validation result is valid or not
val messages: List<MyMessage> = Person.validation(Person())
messages.valid  // == false

New ValidatingStore

We introduce a new type of Store which we call ValidatingStore. This Store has the same properties as a RootStore and additionally a Validation which it uses to validate the stored model. With these additional information you get next to your known data flow also a messages flow which you can use to render out your list of validation messages. Furthermore, you can decide on store creation if you want to automatically validate your model after an update to your store takes place. Then you have to set the validateAfterUpdate flag and the flow of messages gets accordingly updated. Otherwise, you can call the validate(data: D, metadata: T? = null) function inside your own handlers to update the list of messages by your own. For cases, you want to reset your validation state to an empty list or to a specific list of messages, you can use the resetMessages(messages: List<M> = emptyList()) function. Example:

// create your ValidatingStore
val myValidationStore = object : ValidatingStore<Person, Unit, MyMessage>(
    Person(), Person.validation, validateAfterUpdate = true, id = "myPerson"
) {
    // check if your model is valid and then save
    val save = handle { person ->
        if(validate(person).valid) {
            localStorage.setItem(person.name, person.height.toString())
            Person()
        } else person
    }
    //...
}

render {
    //...
    input {
        val name = myValidationStore.sub(Person.name())
        value(name.data)
        changes.values() handledBy name.update
    }
    //...
    button {
        +"Save"
        clicks handledBy myValidationStore.save
    }

    // render out the messages
    myValidationStore.messages.renderEach(MyMessage::path) {
        p {
            +it.text
            if(it.isError) inlineStyle("color: red")
        }
    }
}

@jamowei jamowei requested review from a user and jwstegemann January 20, 2022 15:16
@jamowei jamowei self-assigned this Jan 20, 2022
@jamowei jamowei added api breaking Forces client sources to change api improvement enhancement New feature or request labels Jan 20, 2022
@jamowei jamowei mentioned this pull request Jan 21, 2022
@jamowei
Copy link
Collaborator Author

jamowei commented Jan 21, 2022

Opened up a ticket at Jetbrains Youtrack: https://youtrack.jetbrains.com/issue/KT-50897

@jamowei jamowei force-pushed the jamowei/newValidation branch from 46e9b77 to 9a6e329 Compare January 24, 2022 10:46
@jamowei jamowei marked this pull request as ready for review January 25, 2022 16:08
Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some proposals for the API docs.

Besides this looks really good! I hardly can wait to use this :-)

I have some proposals for the release notes too. We should talk about those afterwards.

core/src/jsMain/kotlin/dev/fritz2/binding/store.kt Outdated Show resolved Hide resolved
core/src/jsMain/kotlin/dev/fritz2/binding/store.kt Outdated Show resolved Hide resolved
core/src/jsMain/kotlin/dev/fritz2/binding/store.kt Outdated Show resolved Hide resolved
core/src/jsMain/kotlin/dev/fritz2/validation/validation.kt Outdated Show resolved Hide resolved
chausknecht and others added 4 commits January 28, 2022 14:46
- add / change some API docs
- remove obsolete files
- remove obsolete module (core/jvmMain)
# Conflicts:
#	core/src/jsMain/kotlin/dev/fritz2/binding/store.kt
#	core/src/jsMain/kotlin/dev/fritz2/binding/substores.kt
@ghost ghost self-requested a review January 31, 2022 10:33
ghost
ghost previously approved these changes Jan 31, 2022
@jamowei jamowei requested a review from a user January 31, 2022 16:32
@jamowei jamowei added this to the 1.0-RC1 milestone Jan 31, 2022
@ghost ghost merged commit 7d15c13 into master Feb 1, 2022
@ghost ghost deleted the jamowei/newValidation branch February 1, 2022 08:48
@jamowei jamowei linked an issue Feb 3, 2022 that may be closed by this pull request
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api breaking Forces client sources to change api improvement enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Using RootInspector<D> instead of D as parameter in validate() function
1 participant