asyncApplyValidator function
Asynchronously apply a validator, running any AngelMatchers, also those
of nested Validators.
Like Validator.check, matchers are given the input, not the filtered
data, as their context, and an AngelMatcher does not match null.
Throws a StateError if app is null and the validator has an
AngelMatcher.
Implementation
Future<ValidationResult> asyncApplyValidator(
Validator validator,
Map data,
Angel? app,
) async {
if (app == null && _hasAngelMatcher(validator)) {
throw StateError(
'The validator has an AngelMatcher, which needs an Angel app, but none '
'was given.',
);
}
var result = validator.check(data);
if (result.errors.isNotEmpty) return result;
// The input, with the default values that [Validator.check] added.
var context = {...result.data, ...data};
var errors = <String>[];
var m = Map<String, dynamic>.from(result.data);
for (var key in result.data.keys) {
var value = result.data[key];
var input = context[key];
var description = StringDescription("'$key': expected ");
var valid = true;
for (var rule in validator.rules[key]!) {
if (rule is Validator && input is Map && _hasAngelMatcher(rule)) {
var nested = await asyncApplyValidator(rule, input, app);
if (nested.errors.isNotEmpty) {
errors.addAll(nested.errors);
valid = false;
break;
}
// Several validators of one key keep the data of each.
m[key] = {...m[key] as Map, ...nested.data};
} else if (rule is AngelMatcher) {
// [Validator.check] has refused `null` for an [AngelMatcher].
if (!await rule.matchesWithAngel(
value as Object,
key,
context,
{},
app!,
)) {
if (!validator.customErrorMessages.containsKey(key)) {
errors.add(rule.describe(description).toString().trim());
}
valid = false;
break;
}
}
}
if (!valid) {
m.remove(key);
if (validator.customErrorMessages.containsKey(key)) {
errors.add(validator.customError(key, value));
}
}
}
return result.withData(m).withErrors(errors);
}