Validation results
SingleValidationResult
When validating a value, the validateValue method returns a SingleValidationResult object. This object is an instance of a sealed class that can be either a SingleValidationSuccess or a SingleValidationError.
Example:
import 'package:luthor/luthor.dart';
void main() { final validator = l.string();
final successResult = validator.validateValue('Hello, World!'); final errorResult = validator.validateValue(42); // This should be a string but it is a number
switch (successResult) { case SingleValidationSuccess(data: final String data): print(data); // Hello, World! case SingleValidationError(data: final String data, errors: final List<String> errors): print('Error: ${errors}'); // This will not be executed }
switch (errorResult) { case SingleValidationSuccess(data: final String data): print(data); // This will not be executed case SingleValidationError(data: final String data, errors: final List<String> errors): print('Error: ${errors}'); // Error: [value is not a string] }}You can also use the isValid property to check if the validation was successful:
print(successResult.isValid); // trueprint(errorResult.isValid); // falseSchemaValidationResult
When using the validateSchema method on a Map to validate a schema, the validateSchema method returns a SchemaValidationResult object. This object is an instance of a sealed class that can be either a SchemaValidationSuccess or a SchemaValidationError.
Example:
import 'package:luthor/luthor.dart';
void main() { final validator = l.schema({ 'name': l.string(), 'age': l.number(), 'address': l.schema({ 'street': l.string(), 'city': l.string(), }).required(), });
final successResult = validator.validateSchema({ 'name': 'John Doe', 'age': 42, 'address': { 'street': '123 Main St', 'city': 'Anytown', }, }); final errorResult = validator.validateSchema({ 'name': 'John Doe', 'age': '42', // This should be a number but it is a string 'address': { 'street': '123 Main St', // 'city' is missing but it is required }, });
switch (successResult) { case SchemaValidationSuccess(data: final Map<String, dynamic> data): print(data); // {name: John Doe, age: 42, address: {street: 123 Main St, city: Anytown}} case SchemaValidationError(data: final Map<String, dynamic> data, errors: final Map<String, List<String>> errors): print('Error: ${errors}'); // This will not be executed }
switch (errorResult) { case SchemaValidationSuccess(data: final Map<String, dynamic> data): print(data); // This will not be executed case SchemaValidationError(data: final Map<String, dynamic> data, errors: final Map<String, List<String>> errors): print('Error: ${errors}'); // Error: {age: [value is not a number], address: {city: [value is required]}} }}You can also use the isValid property to check if the validation was successful:
print(successResult.isValid); // trueprint(errorResult.isValid); // falseWhen dealing with a SchemaValidationError, you can use the getError() method to get the first error message for a specific key or nested key using a dot-separated path. If there are no errors for the specified key, it will return null.
print(errorResult.getError('address.city')); // value is requiredType-Safe Error Access with ErrorKeys
When using code generation, Luthor generates ErrorKeys constants that provide type-safe access to validation errors. This eliminates the need for error-prone string literals and provides IDE autocomplete support.
Generated ErrorKeys
For each @luthor annotated class, ErrorKeys are generated with proper dot notation for nested fields:
// Simple class ErrorKeysconst UserErrorKeys = ( name: "name", email: "email", age: "age",);
// Nested class ErrorKeys with dot notationconst ProfileErrorKeys = ( id: "id", user: ( name: "user.name", email: "user.email", age: "user.age", ), settings: ( theme: "settings.theme", notifications: "settings.notifications", ),);Using ErrorKeys with getError()
Instead of using string literals that can contain typos:
// ❌ Error-prone - no compile-time checkingfinal emailError = result.getError('user.email');final themeError = result.getError('settings.theme');final typoError = result.getError('user.emial'); // Typo! Won't be caught until runtimeUse type-safe ErrorKeys with full IDE support:
// ✅ Type-safe with autocomplete and compile-time checkingfinal emailError = result.getError(ProfileErrorKeys.user.email);final themeError = result.getError(ProfileErrorKeys.settings.theme);// Typos are caught at compile time!Complete Example with ErrorKeys
import 'package:freezed_annotation/freezed_annotation.dart';import 'package:luthor/luthor.dart';
part 'user_profile.freezed.dart';part 'user_profile.g.dart';
@luthor@freezedabstract class UserProfile with _$UserProfile { const factory UserProfile({ required String name, required String email, required UserSettings settings, }) = _UserProfile;
factory UserProfile.fromJson(Map<String, dynamic> json) => _$UserProfileFromJson(json);}
@luthor@freezedabstract class UserSettings with _$UserSettings { const factory UserSettings({ required String theme, required bool notifications, }) = _UserSettings;
factory UserSettings.fromJson(Map<String, dynamic> json) => _$UserSettingsFromJson(json);}
void main() { final result = $UserProfileValidate({ 'name': '', // Invalid - empty string 'email': 'invalid-email', // Invalid - not an email 'settings': { 'theme': null, // Invalid - required field 'notifications': 'yes', // Invalid - should be boolean }, });
switch (result) { case SchemaValidationError(): print('Validation failed:');
// Type-safe error access with ErrorKeys final nameError = result.getError(UserProfileErrorKeys.name); final emailError = result.getError(UserProfileErrorKeys.email); final themeError = result.getError(UserProfileErrorKeys.settings.theme); final notificationsError = result.getError(UserProfileErrorKeys.settings.notifications);
if (nameError != null) print('Name: $nameError'); if (emailError != null) print('Email: $emailError'); if (themeError != null) print('Theme: $themeError'); if (notificationsError != null) print('Notifications: $notificationsError');
case SchemaValidationSuccess(data: final profile): print('✅ Valid profile: ${profile.name}'); }}Benefits of ErrorKeys
- Compile-time Safety - Typos in field names are caught during compilation
- IDE Support - Full autocomplete and go-to-definition support
- Refactoring Safety - Renaming fields automatically updates ErrorKeys
- Nested Field Support - Automatic dot-notation handling for nested objects
- JsonKey Respect - Properly maps to JSON field names when using
@JsonKeyannotations
JsonKey Integration
ErrorKeys automatically handle @JsonKey annotations:
@luthor@freezedabstract class ApiUser with _$ApiUser { const factory ApiUser({ required String name, @JsonKey(name: 'email_address') required String email, }) = _ApiUser;
factory ApiUser.fromJson(Map<String, dynamic> json) => _$ApiUserFromJson(json);}
// Generated ErrorKeys respect JsonKey mappingconst ApiUserErrorKeys = ( name: "name", email: "email_address", // Uses JsonKey name);
void main() { final result = $ApiUserValidate({'email_address': 'invalid'});
switch (result) { case SchemaValidationError(): // Use Dart field name as key, but accesses JSON field name final emailError = result.getError(ApiUserErrorKeys.email); print('Email error: $emailError'); case SchemaValidationSuccess(): break; }}Using ErrorKeys makes error handling both safer and more maintainable, especially in large applications with complex nested validation schemas.