Skip to content

.customWithSchema()

Runs validator(value, data) where data is a SchemaData: a read-only map of the sibling fields as they were received, with data.root holding the outermost schema’s map. Passes when the function returns true. Like .custom(), it receives the typed value, is never called for null, treats a thrown exception as false, and runs on a schema or list only after every child passed. Inside a list, data is the schema that holds the list. Outside any schema, data is empty. A missing optional field is skipped before this runs, so put a rule on a field that is always present.

data holds the raw input, not validated values. A sibling can be missing, null or of the wrong type, even when its own validator fails, so check its type before you use it. Passing when the sibling has the wrong type leaves the error to the sibling’s own validator:

bool atMostLimit(int value, SchemaData data) {
final limit = data['limit'];
if (limit is! int) return true;
return value <= limit;
}
Self customWithSchema(SchemaCustomValidator<T> validator, {String? message, MessageBuilder? messageBuilder})
typedef SchemaCustomValidator<T> = bool Function(T value, SchemaData data);
Parameter Type Meaning
validator bool Function(T, SchemaData) Receives the field value and the enclosing schema’s data. Return true to pass.
message String? Replaces the default error message.
messageBuilder MessageBuilder? Builds the message from the issue. message wins when both are given. See Message builders.

Code custom. Default message: value does not pass schema custom validation. value is replaced by the field name when there is one. Pass message in practice.

@WithSchemaCustomValidator(fn, message: ...). fn has to be a top-level function or a static method with the field’s type: bool matchesPassword(String value, SchemaData data).

bool matchesPassword(String value, SchemaData data) =>
value == data['password'];
final signUp = l.schema({
'password': l.string().min(8).required(),
'confirm': l
.string()
.customWithSchema(matchesPassword, message: 'Passwords must match')
.required(),
});
signUp.validate(input)+1-2
  1. valid: {'password': 'hunter2hunter2', 'confirm': 'hunter2hunter2'}
  2. invalid: {'password': 'hunter2hunter2', 'confirm': 'nope'}confirm: Passwords must match
  3. invalid: {'password': 'hunter2hunter2'}confirm: confirm is required

data.root reaches the top of a nested input:

final order = l.schema({
'currency': l.string().required(),
'lines': l.list(l.schema({
'amount': l.num().required(),
'currency': l
.string()
.customWithSchema((value, data) => value == data.root['currency'],
message: 'line currency must match the order currency')
.required(),
}).required()),
});
{lines.1.currency: [line currency must match the order currency]}