Docs/Data & text

Validation

Check user input against rules.

On this page

valk.validate checks input, usually decoded JSON or form data, against rules you build by chaining calls on a Field.

Check an email address#

Valk
use valk.validate

fn main() {
    println(validate.email("ada@example.com")) // true
    println(validate.email("ada@example"))     // false
}

validate.email is a simple syntax check; it does not contact the mail server.

Validate an object#

Valk
use valk.json
use valk.validate

fn main() {
    let rule = validate.Field.object(.{
        "name" => validate.Field.string().min(1).max(50)
        "email" => validate.Field.string().email()
        "age" => validate.Field.int().min(18).optional()
        "tags" => validate.Field.array(validate.Field.string()).max(5)
    })

    let data = json.decode("{ \"name\": \"Ada\", \"email\": \"nope\", \"age\": 12, \"tags\": [] }") ! return
    let errors = Map[String]{}
    if !rule.validate_and_translate(data, errors) {
        each errors as message, field {
            println(field + ": " + message)
        }
    }
    // field.email: Invalid email address
    // field.age: Minimum value is 18
}

validate returns whether the data passes, and records one error code per failing field in errors, keyed by its path. validate_and_translate also turns the codes into English messages. A third argument to validate replaces the field prefix.

Rules#

Constructor Accepts
Field.string() Text
Field.int(), Field.float() Numbers (an integer also passes as a float)
Field.bool() true or false
Field.array(of) An array, each item checked against of
Field.object(fields) An object, each named field checked
Check Meaning
min(n), max(n) String byte length, array or object size, or number value
utf8_min(n), utf8_max(n) String length in characters
fmin(x), fmax(x) Float bounds
email() A valid email address
equals_string(text) Exactly this text
lower(), upper() No uppercase / no lowercase letters
username(numbers, underscore) Letters, plus digits and _ when allowed
is_syntax(mask) Only these characters: a = a-z, A = A-Z, 0 = 0-9, others literal
custom(func, code) func returns false: the field gets error code
optional() The field may be absent
nullable() null is accepted
default(value) Filled in by try_cast when absent

Custom checks and messages#

Valk
use valk.json
use valk.validate

fn main() {
    let even = validate.Field.int().custom(fn(v: json.Value) bool {
        return v.is_int && v.int % 2 == 0
    }, "not_even")

    let errors = Map[String]{}
    even.validate(json.from(3), errors)
    validate.Field.translate_errors(errors, .{ "not_even" => "Must be an even number" })
    println(errors.get("field") !? "") // Must be an even number
}

translate_errors uses validate.default_translations unless you pass your own map of code to message, for example to show messages in another language.

Convert loose input#

Valk
use valk.json
use valk.validate

fn main() {
    let rule = validate.Field.object(.{
        "count" => validate.Field.int()
        "notify" => validate.Field.bool().default(false)
    })
    let form = json.decode("{ \"count\": \"3\" }") ! return
    let data = rule.try_cast(form)
    println(rule.validate(data))    // true
    println(json.encode(data))      // {"count":3,"notify":false}
}

try_cast converts values toward the rule's types, such as the text "3" to the number 3, and fills in defaults. Values that cannot be converted stay as they are, so validate afterwards.

get_form_rules() returns a rule's limits as JSON, for client-side form checks.