How validators work
A validator is an immutable object that holds a type check and an ordered list of validations, and produces a typed output when a value passes. This page explains the model behind the API so the reference pages make sense.
Everything starts from l
Section titled “Everything starts from l”l is the validator factory. Its type methods each start a validator with one type check: string(), int(), double(), num(), bool(), list(), map(), schema(), union(), oneOf(), file(), any() and nullValue(). Type methods exist only on l, so l.string().int() does not compile. Each returns a validator class with the modifiers that fit its type.
l.string(); // StringValidator: .email(), .min(), .regex(), and the other string modifiersl.int(); // NumberValidator<int, int?>: .min(), .max(), .finite()l.schema({}); // SchemaValidator: .passthrough(), .strict(), validateSchema()l.list(l.string()); // ListValidator: only the shared modifiersEvery validator has the shared modifiers required(), withName(), custom() and customWithSchema(). l.nullValue() is the exception: it has only withName().
Chaining adds validations
Section titled “Chaining adds validations”A modifier returns a new validator with one more validation appended, and the return type is the same validator class, so modifiers chain in any order. l.string().required().min(3) and l.string().min(3).required() are the same validator.
[value must be at least 3 characters long][value must be at least 3 characters long]Validations run in chain order, and every validation after the type check runs, so one failed input can carry several messages. The type check is the one exception: when it fails, nothing else runs.
[value must be at least 8 characters long, value does not contain "@"][value must be a string]The output is typed
Section titled “The output is typed”The type parameter of a validator is its output type. l.string() is a StringValidator<String?>, and .required() narrows it to StringValidator<String>. On success, data has that type, so you get a String or a List<int> without a cast or any generated code.
final count = l.int().required().validate(42);if (count case ValidationSuccess(:final data)) { print(data.runtimeType); // data is an int}
final tags = l.list(l.string().required()).required().validate(['a']);if (tags case ValidationSuccess(:final data)) { print(data.runtimeType); // data is a List<String>}intList<String>Schemas, lists and maps produce a new collection of the validated values rather than returning the input. Schemas covers what that means for unknown keys.
Validators are immutable
Section titled “Validators are immutable”Chaining never changes the validator you chained from. A base validator can be shared and extended in several places without the extensions affecting each other. Define common validators once and reuse them across schemas.
final text = l.string();final email = text.email();final uuid = text.uuid();
text.validate('anything').isValid;email.validate('anything').isValid;uuid.validate('anything').isValid;truefalsefalseField names in messages
Section titled “Field names in messages”Default messages start with a field name. Validated on its own, a validator has none and messages say value. withName() sets one. Inside a schema, the field key is the name, and withName on the field’s validator is ignored.
l.string().email().validate('bad').messages.first;l.string().email().withName('email').validate('bad').messages.first;l.schema({'email': l.string().email()}).validate({'email': 'bad'}).errors;value must be a valid email addressemail must be a valid email address{email: [email must be a valid email address]}Every generated schema ends in .withName('ModelName'), which names the model in the must be a map message when the input is not a map.
Custom messages
Section titled “Custom messages”Every type and modifier accepts message, a fixed string, and messageBuilder, a function that receives the issue and returns the string. l.messageBuilder sets one builder for every message. Message builders covers all three.
Where to go next
Section titled “Where to go next”- Optional by default covers how
nulland missing fields are treated. - Results and issues covers
ValidationResultandValidationIssue. - Schemas covers nesting, unknown keys, and forward references.