Skip to content

Validation

The Property System provides a PropertyValidator<T> abstraction for defining validation rules on property values. Validators are bridged to reactive_forms validators internally, so validation integrates with the form control lifecycle.

PropertyValidator<T>

The base class for all validators. Implement validate() to return an error map or null:

dart
abstract class PropertyValidator<T> {
  const PropertyValidator();

  /// Return error map if invalid, null if valid.
  Map<String, dynamic>? validate(T? value);
}

Error Format

Errors are returned as a Map<String, dynamic> where:

  • The key is the error type (e.g., 'required', 'min', 'pattern')
  • The value is a detail map containing at minimum a 'message' key
dart
// Example error from PropertyValidators.min(10)
{
  'min': {
    'min': 10,
    'actual': 5,
    'message': 'Must be at least 10',
  }
}

The PropertyCollectionEditor and reactive_forms integration extract the 'message' key for display.

PropertyValidators Factory

The PropertyValidators abstract final class provides factory methods for all built-in validators:

required

Validates that the value is not null, not an empty string, and not an empty iterable.

dart
PropertyValidators.required()
PropertyValidators.required(message: 'Title is required')

Can also be set via the required: true parameter on any property, which automatically adds this validator.

min / max

Validates numeric bounds. Works with both int and double properties.

dart
PropertyValidators.min(1)
PropertyValidators.min(0, message: 'Must be non-negative')

PropertyValidators.max(100)
PropertyValidators.max(1000, message: 'Maximum 1000 participants')

minLength / maxLength

Validates string length bounds.

dart
PropertyValidators.minLength(3)
PropertyValidators.minLength(3, message: 'At least 3 characters required')

PropertyValidators.maxLength(200)
PropertyValidators.maxLength(200, message: 'Maximum 200 characters')

pattern

Validates that a string matches a regular expression.

dart
PropertyValidators.pattern(r'^[a-zA-Z0-9_]+$')
PropertyValidators.pattern(
  r'^[a-zA-Z0-9_]+$',
  message: 'Only letters, numbers, and underscores allowed',
)

identifier

A convenience validator that checks for a valid Dart-style identifier (letters, digits, underscores, starting with a letter or underscore).

dart
PropertyValidators.identifier()
PropertyValidators.identifier(message: 'Must be a valid identifier')

Pattern used: ^[a-zA-Z_][a-zA-Z0-9_]*$

email

Validates that a string is a valid email address.

dart
PropertyValidators.email()
PropertyValidators.email(message: 'Please enter a valid email')

custom

Creates a validator from a predicate function for one-off validation logic.

dart
PropertyValidators.custom<String>((value) {
  if (value != null && value.contains('admin')) {
    return {'forbidden': {'message': 'Cannot contain "admin"'}};
  }
  return null;
})

Using Validators

On Property Construction

Validators are passed via the validators parameter:

dart
StringProperty(
  key: 'email',
  label: 'Email',
  required: true,  // adds required validator automatically
  validators: [
    PropertyValidators.email(),
    PropertyValidators.maxLength(255),
  ],
);

Via the Builder

dart
b.string('email', 'Email',
  required: true,
  validators: [
    PropertyValidators.email(),
    PropertyValidators.maxLength(255),
  ],
);

Checking Validation State

dart
// Single property
final property = StringProperty(key: 'name', label: 'Name', required: true);
property.control.valid;      // false (empty string, required)
property.control.hasErrors;  // true
property.control.errors;     // {'required': {'message': 'This field is required'}}

// Entire collection
final errors = collection.validateAll();  // Map<String, String>
final isValid = collection.isValid;       // bool
final messages = collection.validationErrors;  // List<String>

Touch and Error Display

Validators only show errors after the control has been "touched". Call markAsTouched() to trigger error display:

dart
property.control.markAsTouched();

// validateAll() automatically marks all controls as touched
collection.validateAll();

Validator Summary

ValidatorType TError KeyDescription
required()dynamicrequiredNot null, not empty
min(value)numminMinimum numeric value
max(value)nummaxMaximum numeric value
minLength(length)StringminLengthMinimum string length
maxLength(length)StringmaxLengthMaximum string length
pattern(regex)StringpatternRegex match
identifier()StringpatternValid identifier format
email()StringemailValid email format
custom(fn)T(user-defined)Custom function

Utility: toIdentifier

PropertyValidators.toIdentifier() is a static utility that converts a human-readable string to a valid identifier:

dart
PropertyValidators.toIdentifier('First Name');       // 'first_name'
PropertyValidators.toIdentifier('pH Meter - Type A'); // 'ph_meter_type_a'
PropertyValidators.toIdentifier('  extra   spaces  '); // 'extra_spaces'

This is useful with PropertyDerivation to auto-generate identifiers from title fields.

Next Steps