Link Search Menu Expand Document

Validation

When accepting user-input, it is a nice idea to validate the data before processing it any further.

To do validation in fritz2, you first have to implement the interface Validator. This interface takes three type-parameters:

  • the type of data to validate
  • a type describing the validation-results (like a message, etc.)
  • a type for metadata you want to forward from your Handlers to your validation (or Unit if you do not need this)

Put your validation code in the commonMain section of your multiplatform-project where your data models are located as well. Code in commonMain can be used in jsMain (frontend) and jvmMain (backend).

In the commonMain section write something like this:

@Lenses
data class Person(
    val name: String,
    val age: Int
)

enum class Severity {
    Info,
    Warning,
    Error
}

data class Message(val id: String, val severity: Severity, val text: String): ValidationMessage {
    override fun isError(): Boolean = severity > Severity.Warning
}

class PersonValidator : Validator<Person, Message, String>() {

    override fun validate(data: Person, metadata: String): List<Message> {
        val msgs = mutableListOf<Message>()
        val inspector = inspect(data)
        
        val name = inspector.sub(L.Person.name)
        if(name.data.trim().isBlank()) {
            msgs.add(Message(name.id, Severity.Error, "Please provide a name"))
        }

        val age = inspector.sub(L.Person.age)
        if(age.data < 1) {
            msgs.add(Message(age,id, Severity.Error, "Please correct the age"))
        } else if(age.date > 100) {
            msgs.add(Message(age,id, Severity.Warning, "Is the person really older then 100 years!?"))
        }

        return msgs
    }
}

You can structure and implement your concrete validation-rules with everything Kotlin offers. Also, for getting the same ids as with using sub() method on the SubStores, we create a new Inspector with the ìnspect() function. The Inspector can then navigate through the model by calling the sub() method with the corresponding Lens. The resulting SubInspectors then have two attributes data and ìd. The first one gives you the current data and the second one the corresponding id to it.

Now you can use the PersonValidator in your jsMain section:

val store = object : RootStore<String>("") {
    val validator = PersonValidator()

    val updateWithValidation = handle<Person> { oldPerson, newPerson ->
        if (validator.isValid(newPerson, "update")) newPerson else oldPerson
    }
}

You can access your validation-results with Validator.msgs. This gives you a Flow<List<M> where M is your ValidationMessage-type. You can handle this like any other Flow of a List, for example by rendering your messages to html:

render {
    ul {
        store.validator.msgs.each(Message::id).render {
            li(baseClass = it.severity.name.toLowerCase()) {
                text(it.text)
            }
        }.bind()
    }
}

Have a look at a more complete example here.

By the way: fritz2 supports connecting to your (http)-backend from the browser with Remote Calls.


Distributed by an MIT license.