Worked example
This example puts the generator features together in one model: typed keys, @JsonKey names, a nested model, a schema custom validator for password confirmation, and a conditional rule that depends on another field. Read the earlier generator pages first; this one does not re-explain them. The generated code and the outputs are from a real run.
The models
Section titled “The models”Address is the one from What gets generated.
import 'package:freezed_annotation/freezed_annotation.dart';import 'package:luthor/luthor.dart';
import 'user.dart';
part 'registration.freezed.dart';part 'registration.g.dart';
bool matchesPassword(String value, SchemaData data) => value == data['password'];
/// A business account needs a phone number. The rule sits on `accountType`/// because that field is always present; a missing optional `phone` is never/// validated.bool phoneForBusiness(String value, SchemaData data) { if (value != 'business') return true; final phone = data['phone']; return phone is String && phone.isNotEmpty;}
@luthor@freezedabstract class Registration with _$Registration { const factory Registration({ @HasMin(2) @HasMax(50) required String name, @IsEmail() @JsonKey(name: 'email_address') required String email, @HasMin(8) required String password, @WithSchemaCustomValidator(matchesPassword, message: 'Passwords must match') required String confirm, @WithSchemaCustomValidator( phoneForBusiness, message: 'A business account needs a phone number', ) required String accountType, String? phone, @HasMin(18) required int age, required Address address, }) = _Registration;
factory Registration.fromJson(Map<String, dynamic> json) => _$RegistrationFromJson(json);}Validating
Section titled “Validating”import 'package:luthor/luthor.dart';import 'package:my_app/registration.dart';
void main() { final result = $RegistrationValidate({ 'name': 'A', 'email_address': 'ada', 'password': 'short', 'confirm': 'different', 'accountType': 'business', 'age': 17, 'address': {'street': '1 Main St', 'city': 'Springfield', 'postcode': '12'}, });
switch (result) { case ValidationSuccess(:final data): print('+ ${data.email}'); case ValidationFailure(): for (final key in [ RegistrationErrorKeys.name, RegistrationErrorKeys.email, RegistrationErrorKeys.password, RegistrationErrorKeys.confirm, RegistrationErrorKeys.accountType, RegistrationErrorKeys.age, RegistrationErrorKeys.address.postcode, ]) { print('- ${result.getError(key)}'); } }}- name must be at least 2 characters long- email_address must be a valid email address- password must be at least 8 characters long- Passwords must match- A business account needs a phone number- age must be greater than or equal to 18- postcode must match the pattern ^\d{5}$$RegistrationValidate(input)+1-1- valid:
{'name': 'Ada', 'email_address': '[email protected]', 'password': 'hunter2hunter2', 'confirm': 'hunter2hunter2', 'accountType': 'personal', 'age': 36, 'address': {...}} - invalid:
{'name': 'A', 'email_address': 'ada', 'password': 'short', 'confirm': 'different', 'accountType': 'business', 'age': 17, 'address': {..., 'postcode': '12'}}
Two details worth noticing. The email message says email_address, because the field name in messages is the input key, which @JsonKey renamed. The phone rule reports under accountType, where the validator lives, and RegistrationErrorKeys.accountType is the key that reads it.
What the generator wrote
Section titled “What the generator wrote”The schema, with annotations applied in the order they were written:
final SchemaValidator $RegistrationSchema = l .schema({ RegistrationSchemaKeys.name: l.string().min(2).max(50).required(), RegistrationSchemaKeys.email: l.string().email().required(), RegistrationSchemaKeys.password: l.string().min(8).required(), RegistrationSchemaKeys.confirm: l .string() .customWithSchema(matchesPassword, message: "Passwords must match") .required(), RegistrationSchemaKeys.accountType: l .string() .customWithSchema( phoneForBusiness, message: "A business account needs a phone number", ) .required(), RegistrationSchemaKeys.phone: l.string(), RegistrationSchemaKeys.age: l.int().min(18).required(), RegistrationSchemaKeys.address: forwardRef( () => $AddressSchema.required(), ), }) .withName("Registration");And the error keys, with the nested record for address:
const RegistrationErrorKeys = ( name: "name", email: "email_address", password: "password", confirm: "confirm", accountType: "accountType", phone: "phone", age: "age", address: ( $key: "address", street: "address.street", city: "address.city", postcode: "address.postcode", ),);