validate static method
Validates the data against the provided rules.
Implementation
static Future<ValidationResult> validate(
Map<String, dynamic> data,
Map<String, String> rules, [
Map<String, String>? customMessages,
]) async {
final errors = <String, List<String>>{};
final validatedData = <String, dynamic>{};
// 1. Expand wildcards (e.g. items.*.name)
final expandedRules = <String, String>{};
for (final entry in rules.entries) {
if (entry.key.contains('*')) {
final matches = _expandWildcardPath(data, entry.key);
for (final match in matches) {
expandedRules[match] = entry.value;
}
} else {
expandedRules[entry.key] = entry.value;
}
}
// 2. Process all rules (including expanded ones)
for (final entry in expandedRules.entries) {
final field = entry.key;
final ruleString = entry.value;
final fieldRules = ruleString.split('|');
final value = _getNestedValue(data, field);
var fieldFailed = false;
final shouldBail = fieldRules.contains('bail');
for (final rule in fieldRules) {
if (rule == 'bail') continue;
var ruleName = rule;
String? arg;
if (rule.contains(':')) {
final parts = rule.split(':');
ruleName = parts[0];
arg = parts[1];
}
final handler = ruleHandlers[ruleName];
if (handler != null) {
final passed = await handler(value, arg, data);
if (!passed) {
final message = _getMessage(field, ruleName, arg, customMessages);
errors[field] ??= [];
errors[field]!.add(message);
fieldFailed = true;
// Short-circuit on first failure if bail is set or if required fails
if (shouldBail || ruleName == 'required') break;
}
}
}
if (!fieldFailed) {
_setNestedValue(validatedData, field, _coerceValue(value, fieldRules));
}
}
return ValidationResult(errors, validatedData);
}