
# Validator, Errors and DataBinder

`BeanValidator` checks an object against its constraint annotations, `Errors` holds what went wrong, and `DataBinder` fills an object from text input before checking it.

## Why you need them

The constraint annotations only describe rules. Something has to run them and give you the result in a form you can use:

- a list of violations with finished messages, for logs or your own response format: `BeanValidator.validateObject`;
- an `Errors` object with message codes, so a message file can word each error in the user's language: `BeanValidator` as a `Validator`;
- a full round trip from form fields or query parameters (strings) to a checked object: `DataBinder`.

You do not need to call them yourself for `@Valid()` request bodies, `@Validated()` beans and `@Validated()` configuration properties: the framework does it.

## BeanValidator

`BeanValidator` reads the constraints of `@Introspected()` classes. Create it with the context's `BeanIntrospector`: the default introspector, `BeanIntrospector.shared`, does not know the classes of your application modules.

```dart
import 'package:spring_core_dart/spring_core_dart.dart';

const log = SpringLog('import');

@Service()
class CustomerImport {
  // The context registers its BeanIntrospector as a bean.
  CustomerImport(BeanIntrospector introspector)
    : _validator = BeanValidator(introspector: introspector);

  final BeanValidator _validator;

  // Rows from a file do not pass through a controller: check them here.
  bool accept(CustomerRow row) {
    final violations = _validator.validateObject(row);
    for (final v in violations) {
      log.warning('${v.propertyPath}: ${v.message}'); // name: must not be blank
    }
    return violations.isEmpty;
  }
}
```

With `spring_boot_dart`, a `BeanValidator` bean named `defaultValidator` already exists. Inject `BeanValidator` or `Validator` instead of creating one.

### Constructor

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `introspector` | `BeanIntrospector?` | `BeanIntrospector.shared` | Describes the classes. Pass the context's. |
| `validationMessageSource` | `MessageSource?` | `null` | Resolves `{key}` placeholders in constraint messages, such as `@NotBlank(message: '{signup.name.missing}')`. |
| `clockProvider` | `ClockProvider` | `DefaultClockProvider()` | Gives "now" to the date constraints. |
| `groupSequences` | `Map<Type, List<Type>>` | `const {}` | Group sequences: each key group stands for the listed groups, checked in order until one has a violation. |

### Methods

| Method | Returns | Meaning |
| --- | --- | --- |
| `validateObject(target, {groups})` | `List<ConstraintViolation>` | Every violation of `target`, including those inside `@Valid()` properties. Empty when valid or not `@Introspected`. |
| `validateProperty(target, property, {groups})` | `List<ConstraintViolation>` | The violations of one property, without `@Valid()` cascade. Useful to check one form field. |
| `validateValue(type, property, value, {groups})` | `List<ConstraintViolation>` | The violations `value` would cause as `property` of class `type`, without an instance. |
| `validateParameters(target, method, arguments, {groups})` | `List<ConstraintViolation>` | Used by method validation; applications rarely call it. |
| `validateReturnValue(target, method, returnValue, {groups})` | `List<ConstraintViolation>` | Used by method validation; applications rarely call it. |
| `validate(target, errors)` | `void` | As a `Validator`: records each violation as a `FieldError` in `errors`. |
| `validateWithGroups(target, errors, groups)` | `void` | As a `SmartValidator`: the same, for some groups only. |

```dart
// Check one field as the user leaves it.
final nameProblems = validator.validateProperty(form, 'name');

// Check input before an object exists.
final ageProblems = validator.validateValue(SignupForm, 'age', 12);
```

### ConstraintViolation

| Member | Type | Meaning |
| --- | --- | --- |
| `propertyPath` | `String` | Where the invalid value is: `name`, `address.city`, `items[0].sku`. Empty for a class-level constraint. |
| `constraint` | `Constraint` | The constraint that failed; `constraint.name` is the error code. |
| `invalidValue` | `Object?` | The value that broke the constraint. |
| `message` | `String` | The finished message, such as `size must be between 0 and 20`. |
| `messageTemplate` | `String?` | The message before interpolation. |
| `rootBean` | `Object?` | The object validation started from. |
| `leafBean` | `Object?` | The object that holds the invalid property. |

### Messages

Messages are filled in this order:

1. `{key}` placeholders are looked up in `validationMessageSource`. The built-in messages are also available under `jakarta.validation.constraints.<Name>.message`, so a message source can replace them.
2. `{attribute}` placeholders take the constraint's attributes (`{min}`, `{max}`, `{value}`).
3. `${validatedValue}` takes the invalid value.

`\{`, `\}`, `\$` and `\\` stay literal characters.

```dart
// Replace the built-in NotBlank text and use a key in a custom message.
final messages = StaticMessageSource()
  ..addMessage('jakarta.validation.constraints.NotBlank.message', '', 'please fill in')
  ..addMessage('nick.size', '', 'at least {min} letters');

final validator = BeanValidator(
  introspector: introspector,
  validationMessageSource: messages,
);
```

You do not need `validationMessageSource` for keys such as `NotBlank.signup.name` in `messages.properties`. Those word field errors when they are shown through the application's message source.

## Validator: rules in code

Write a `Validator` for rules that do not fit an annotation: a value that depends on another one, or a lookup. It receives the object and records problems in an `Errors`.

```dart
// Two fields must agree: written in code instead of as an annotation.
class SignupValidator implements Validator {
  @override
  bool supports(Object target) => target is SignupForm;

  @override
  void validate(Object target, Errors errors) {
    ValidationUtils.rejectIfEmptyOrWhitespace(errors, 'name', 'required');
    final form = target as SignupForm;
    if (form.password != form.confirm) {
      errors.rejectValue('confirm', 'mismatch');
    }
  }
}
```

| Member | Meaning |
| --- | --- |
| `supports(Object target)` | Whether this validator can check `target`. It receives the object, not its class. |
| `validate(Object target, Errors errors)` | Records every problem in `errors`. Does not throw for invalid data. |

`SmartValidator` adds `validateWithGroups(target, errors, groups)`. `BeanValidator` implements it.

If you register a `Validator` as a bean, the web starter checks `@Valid()` arguments with the single `Validator` bean. With a second one and neither marked `@Primary`, those arguments are not checked.

### ValidationUtils

| Method | Meaning |
| --- | --- |
| `invokeValidator(validator, target, errors, {groups})` | Runs `validator`. Throws `ArgumentError` when it does not support `target`. Passes `groups` to a `SmartValidator`. |
| `rejectIfEmpty(errors, field, errorCode, {args, defaultMessage})` | Records an error when the field is `null`, `''`, or an empty collection or map. |
| `rejectIfEmptyOrWhitespace(errors, field, errorCode, {args, defaultMessage})` | The same, and also rejects a string of only whitespace. |

## Errors and BindingResult

`Errors` is the list of errors for one object, and the way a validator records them. `BindingResult` adds the target object. The usual implementations are `BeanPropertyBindingResult(target, objectName)` and `MapBindingResult(map, objectName)`.

```dart
// Run two validators into one result and read it.
final result = BeanPropertyBindingResult(form, 'signup');
ValidationUtils.invokeValidator(validator, form, result);
ValidationUtils.invokeValidator(SignupValidator(), form, result);

if (result.hasErrors) {
  for (final error in result.fieldErrors()) {
    // NotBlank.signup.name, NotBlank.name, NotBlank.String, NotBlank
    log.info('${error.field}: ${error.codes}');
  }
}
```

### Recording errors

| Method | Meaning |
| --- | --- |
| `reject(errorCode, {args, defaultMessage, renderDefaultMessage})` | An error of the whole object (an `ObjectError`). |
| `rejectValue(field, errorCode, {args, defaultMessage, renderDefaultMessage})` | An error of one field (a `FieldError`); the field's current value becomes the rejected value. |
| `pushNestedPath(subPath)` / `popNestedPath()` | Name fields relative to a nested object, so one address validator serves `billing.city` and `shipping.city`. |
| `addAllErrors(errors)` | Copies the errors of another result with the same object name. |

### Reading errors

| Member | Meaning |
| --- | --- |
| `hasErrors`, `errorCount`, `allErrors` | Every error, of fields and of the object. |
| `hasGlobalErrors`, `globalErrors`, `globalError` | Errors of the whole object. |
| `hasFieldErrors([field])`, `fieldErrors([field])`, `fieldError([field])` | Field errors; `address.*` matches every field under a path. |
| `getFieldValue(field)` | The value to show in a form: the rejected value if the field has an error, else the current value. |

### ObjectError and FieldError

An error has no fixed text. It carries message codes, from the most specific to the plain one, plus arguments and a default message. A `MessageSource` turns it into text with the first code it defines.

| Member | Type | Meaning |
| --- | --- | --- |
| `objectName` | `String` | Name of the validated object, such as `signup`. |
| `codes` | `List<String>` | Message codes, most specific first. |
| `code` | `String` | The last, plain code, such as `NotBlank`. |
| `arguments` | `List<Object?>` | Values for `{0}`, `{1}`. For constraints: the field, then the attributes sorted by name. |
| `defaultMessage` | `String?` | Text when no code has a message. |
| `field` (`FieldError`) | `String` | Property path of the field. |
| `rejectedValue` (`FieldError`) | `Object?` | The value that was rejected. |
| `bindingFailure` (`FieldError`) | `bool` | `true` when the input could not be converted. Such a field gets no constraint error on top. |

```dart
// Word an error with the application's messages.properties:
// Min={0} must be at least {1}
final error = result.fieldError('age')!;
final text = context.getMessageFromResolvable(error); // age must be at least 18
```

Codes follow Spring's scheme. For the constraint `NotBlank` on field `name` of object `signup`, of type `String`: `NotBlank.signup.name`, `NotBlank.name`, `NotBlank.String`, `NotBlank`. `DefaultMessageCodesResolver(prefix: 'validation.', postfixFormat: true)` changes the scheme; implement `MessageCodesResolver` to use your own.

## DataBinder

`DataBinder` copies input values, usually strings, onto the properties of an `@Introspected()` object, then runs its validators. Every problem ends up in one `bindingResult`. Text that cannot be converted becomes a `typeMismatch` field error that keeps what was typed, instead of an exception.

Use it when the input is a flat map of strings: form fields, query parameters, a CSV row. JSON request bodies do not need it; they are read by the `ObjectMapper` and checked with `@Valid()`.

```dart
// A signup form: `email` is required, the client may never set `id`.
final introspector = context.getBean<BeanIntrospector>();
final binder = DataBinder(SignupForm(), objectName: 'signup', introspector: introspector)
  ..requiredFields = ['email']
  ..disallowedFields = ['id']
  ..validators.add(BeanValidator(introspector: introspector))
  ..bind({'name': 'Ada', 'age': 'old', 'address.city': 'Oslo'})
  ..validate();

final result = binder.bindingResult;
result.fieldError('age')!.codes.first; // typeMismatch.signup.age
result.fieldError('email')!.code; // required
binder.suppressedFields; // fields skipped by allowed/disallowed rules
```

`close()` ends binding: it throws a `BindException` when there are errors, else returns a model map with the target under `objectName` and the result under `BindingResult.<objectName>`.

### Constructor

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `target` (positional) | `Object?` | required | The object to bind onto; `null` when `construct` creates it. |
| `objectName` | `String` | `'target'` | Name in error codes and the model. |
| `introspector` | `BeanIntrospector?` | `BeanIntrospector.shared` | Describes the target's class. Pass the context's. |
| `conversionService` | `ConversionService?` | `DefaultConversionService.sharedInstance` | Converts input strings to property types. |
| `messageCodesResolver` | `MessageCodesResolver` | `DefaultMessageCodesResolver()` | Builds error codes. |

### Settings

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `allowedFields` | `List<String>?` | `null` | Fields that may be bound, with `*` wildcards. `null` or empty allows every field. |
| `disallowedFields` | `List<String>` | `const []` | Fields never bound, such as `id` or `*.admin`. They win over `allowedFields`. Matching ignores case. |
| `requiredFields` | `List<String>` | `const []` | Fields the input must contain with a non-blank value; a missing one gets a `required` error. |
| `ignoreUnknownFields` | `bool` | `true` | Skip input for fields the target does not have. When `false`, `bind` throws. |
| `ignoreInvalidFields` | `bool` | `false` | Skip input for paths that cannot be reached. When `false`, `bind` throws. |
| `autoGrowNestedPaths` | `bool` | `true` | Create `null` nested objects, lists and maps while binding `address.city` or `items[2].sku`. |
| `autoGrowCollectionLimit` | `int` | `256` | Highest list index plus one that binding may create. |
| `validators` | `List<Validator>` | empty | The validators `validate` runs, in order. Nothing is checked until you add one. |
| `targetType` | `Type?` | `null` | The class `construct` creates. |

### Methods

| Method | Meaning |
| --- | --- |
| `bind(Map<String, Object?> values)` | Sets properties from values whose keys are paths (`name`, `address.city`, `items[0].sku`). |
| `validate({groups})` | Runs each validator that supports the target. A field with a `typeMismatch` error gets no constraint error on top. |
| `construct(ValueResolver valueResolver)` | Creates the target through its constructor, for classes with final fields. |
| `close()` | Throws `BindException` on errors, else returns the model map. |

### Immutable classes

A class with final fields and no setters cannot be bound onto. Create it from the values with `construct` instead:

```dart
// Start without a target; construct calls Book's constructor.
final binder = DataBinder(null, objectName: 'book', introspector: introspector)
  ..targetType = Book
  ..construct(MapValueResolver({'isbn': '978-0', 'title': 'Dune'}));

final book = binder.target as Book?; // null when a value was invalid
```

## In Spring (Java)

```java
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();

DataBinder binder = new DataBinder(form, "signup");
binder.setRequiredFields("email");
binder.setDisallowedFields("id");
binder.setValidator(validator);
binder.bind(new MutablePropertyValues(Map.of("name", "Ada", "age", "17")));
binder.validate();
BindingResult result = binder.getBindingResult();
```

Differences that matter:

- `BeanValidator` plays both roles of `LocalValidatorFactoryBean`: Jakarta's `validator.validate(object)` is `validateObject`, and Spring's `validate(target, errors)` is `validate`.
- `Validator.supports` receives the object, not a `Class`, because Dart cannot test a `Type` for subtypes.
- `DataBinder.bind` takes a `Map<String, Object?>` instead of `PropertyValues`, and settings are fields (`requiredFields = [...]`) instead of setters.
- Validation groups are passed as `groups:` instead of validation hints.
- Classes must be `@Introspected()`; there is no reflection.

## See also

- [Validation overview](index.md)
- [Constraints](constraints.md)
- [Method validation](method-validation.md)
- [Type conversion](../conversion/index.md)
- [Application context](../beans/application-context.md)
