Skip to content

Message builders

Every validation has a default message, listed on Error messages. Three things can replace it: message on the validation, messageBuilder on the validation, and l.messageBuilder for the whole isolate. Every generator annotation takes the first two.

message replaces the default with the string you pass. The field name is not inserted; the string is used as is.

final email = l
.string()
.email(message: 'Enter an email address')
.required(message: 'Email is required');
email.validate(input)+1-2
  1. valid: '[email protected]'
  2. invalid: 'bad'Enter an email address
  3. invalid: nullEmail is required

messageBuilder is a String Function(ValidationIssue issue). Luthor calls it when the validation fails, with the issue it is about to report. The issue carries the default message, the code, the path, the params and the field name, so the builder can use any of them.

String short(ValidationIssue issue) => 'at least ${issue.params['min']} characters';
l.string().min(8, messageBuilder: short).validate('ab').messages;
l.string().min(8, messageBuilder: short).withName('password').validate('ab').messages;
l.string().min(8, messageBuilder: (issue) => '${issue.fieldName}: ${issue.message}')
.withName('password')
.validate('ab')
.messages;
[at least 8 characters]
[at least 8 characters]
[password: password must be at least 8 characters long]

Set l.messageBuilder to translate or restyle every message in one place. It runs for every issue that has no message or messageBuilder of its own. Switch on issue.code and return issue.message for the codes you do not handle.

l.messageBuilder = (issue) => switch (issue.code) {
IssueCode.required => '${issue.fieldName ?? 'Wert'} fehlt',
IssueCode.invalidEmail => 'keine gültige E-Mail-Adresse',
_ => issue.message,
};
l.schema({'email': l.string().email().required(), 'age': l.int().min(18)})
.validate({'email': 'bad', 'age': 3})
.errors;
l.string().required().validate(null).messages;
l.string().required(message: 'Pflichtfeld').validate(null).messages;
{email: [keine gültige E-Mail-Adresse], age: [age must be greater than or equal to 18]}
[Wert fehlt]
[Pflichtfeld]

Set it back to null to restore the defaults.

For each issue: message on the validation, then messageBuilder on the validation, then l.messageBuilder, then the default.

l.string().min(8, message: 'fixed', messageBuilder: (issue) => 'built').validate('ab').messages
[fixed]

Annotations accept message and messageBuilder. A messageBuilder has to be a top-level function or a static method, because annotation arguments are constants and the generated code refers to the function by name.

bool isEven(int value) => value.isEven;
String seatMessage(ValidationIssue issue) => 'Seat ${issue.params['min']} or higher';
final ticket = l.schema({
'seat': l.int().custom(isEven, message: 'must be even').required(),
'row': l.int().min(1, messageBuilder: seatMessage).required(),
});
$TicketValidate(input)+1-1
  1. valid: {'seat': 4, 'row': 2}
  2. invalid: {'seat': 3, 'row': 0}seat: must be evenrow: Seat 1 or higher

Default messages start with the field name: the schema key, or the name given to withName, or value when there is neither. A custom message does not get the name added, so include it yourself if the message will be shown away from its field. A builder gets it as issue.fieldName.