Validation for Dart that returns a result instead of throwing.
Build a validator by chaining l.string().email().required(). Validate any input and get a sealed result back, typed, with every issue carrying a code, a path and a message. Point luthor_generator at a freezed, json_serializable or dart_mappable model and it writes the schema for you.
dart pub add luthorStart with installationUpgrade from 0.x
- Release
- 1.0.0
- Requires
- Dart 3.11 or Flutter 3.41
- Dependencies
- none
final user = l.schema({ 'email': l.string().email().required(), 'age': l.int().min(18), 'profile': l.schema({ 'displayName': l.string().min(2).required(), }).required(),});user.validate(input)+2-1- valid:
{'email': '[email protected]', 'profile': {'displayName': 'Ada'}} - valid:
{'email': '[email protected]', 'age': 42, 'profile': {'displayName': 'Ada'}} - invalid:
{'email': 'bad', 'age': 12, 'profile': {'displayName': 'x'}}
From install to a generated schema in four steps
Section titled “From install to a generated schema in four steps”-
Install
luthor, andluthor_generatorif you want generated schemas. Luthor needs Dart 3.11 or Flutter 3.41 and has no dependencies of its own. -
Write your first validator. Chain a type and some modifiers, call
validate, and read the typed result. -
Write your first schema. Name the fields of a map, validate it, and look up errors by path.
-
Generate a schema from a model. Annotate a class with
@luthor, runbuild_runner, and use the generated keys instead of strings.
Coming from 0.x? Upgrading to 1.0 lists every renamed call, and What’s new in 1.0 has the additions.
A result, not an exception
Section titled “A result, not an exception”validate returns a ValidationResult<T>. It is sealed, so a switch over it is exhaustive, and T follows the validator: l.string().required() succeeds with a String, l.list(l.int().required()) with a List<int>.
final result = l.string().email().required().validate(input);
switch (result) { case ValidationSuccess(:final data): sendTo(data); // data is a String case ValidationFailure(:final issues): print(issues.first.code); // IssueCode.invalidEmail}l.string().email().required().validate(input)+1-2- valid:
'[email protected]' - invalid:
'bad' - invalid:
null
Values are optional until you say otherwise. Drop .required() from the validator above and null passes. Optional by default explains why.
The same schema, written or generated
Section titled “The same schema, written or generated”A schema is a map of field names to validators. You can write it by hand, or annotate a model and let luthor_generator write it, along with typed keys for the fields and for the error paths. The two tabs below stay in sync across the whole site, so pick the one you use.
import 'package:luthor/luthor.dart';
final userSchema = l.schema({ 'name': l.string().min(2).required(), 'email': l.string().email().required(), 'age': l.int().min(18),});
final result = userSchema.validateSchema(json, fromJson: User.fromJson);import 'package:freezed_annotation/freezed_annotation.dart';import 'package:luthor/luthor.dart';
part 'user.freezed.dart';part 'user.g.dart';
@luthor@freezedabstract class User with _$User { const factory User({ @HasMin(2) required String name, @IsEmail() required String email, @HasMin(18) int? age, }) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);}
final result = $UserValidate(json);Everything in the API
Section titled “Everything in the API”Each entry links to a reference page with the signature, the parameters, the issue code and default message, and the matching generator annotation. Every example output on this site comes from running the code in this repository.
Types
l.string()Must be a String.l.int()Must be an int.l.double()Must be a double.l.num()Must be a num.l.bool()Must be a bool.l.list(e)Must be a List; every element passes e.l.map()Must be a Map; optional key and value validators.l.schema({...})Must be a map with named fields.l.union([...])Passes any one of several validators.l.oneOf([...])Is one of a fixed list of values.l.file()Must be a file-like value.l.any()Accepts any value.l.nullValue()Must be null.
Modifiers
.required()Rejects null and missing; narrows the output type..withName(n)Sets the field name in messages..custom(f)Checks the typed value with your function..customWithSchema(f)Checks the value with access to sibling fields..passthrough()Schema keeps unknown keys..strict()Schema rejects unknown keys.
String modifiers
.min(n)At least n characters..max(n)At most n characters..length(n)Exactly n characters..contains(s)Contains s..startsWith(s)Starts with s..endsWith(s)Ends with s..regex(RegExp)Matches the pattern..email()Is an email address..uri()Is an absolute URI; optional allowed schemes..url()Is a URL with a scheme and a host..ip()Is an IPv4 or IPv6 address..uuid()Is a UUID..cuid()Is a CUID..cuid2()Is a CUID2..dateTime()Is an ISO 8601 date or date-time..emoji()Is made of emoji.
Number modifiers
Results and settings
ValidationResultSuccess with data, or failure with issues.ValidationIssueCode, path, message, params, field name.IssueCodeEvery code and its params.getError(path)Reads one message by error path.MessageBuilderBuilds a message from an issue.l.messageBuilderOne builder for every message.l.maxDepthNesting limit, default 512.forwardRef()Refers to a schema defined later or to itself.