Skip to content

Commit 3024e98

Browse files
implement and composition validator
1 parent 086fd66 commit 3024e98

File tree

8 files changed

+203
-54
lines changed

8 files changed

+203
-54
lines changed

lib/new_api_prototype/composition_validators.dart

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Composition validators
2+
import '../constants.dart';
3+
4+
// TODO (ArturAssisComp): add the translated 'and' separator error message.
5+
// Something like: FormBuilderLocalizations.current.andSeparator;
6+
const String andSeparatorTemporary = ' and ';
7+
8+
/// This function returns a validator that is an AND composition of `validators`.
9+
/// The composition is done following the order of validators from `validators`.
10+
/// If `printErrorAsSoonAsPossible` is true, it prints the error of the first
11+
/// validator in `validators` that returns an error message. Otherwise, the
12+
/// failure message will be composed in the following way:
13+
/// '$prefix<msg1>$separator<msg2>$separator<msg3>...$separator<msgN>$suffix'.
14+
/// If every validator returns null, this validator also returns null.
15+
///
16+
/// # Parameters
17+
/// - String? `separator`: the separator of each validator failure message when
18+
/// `printErrorAsSoonAsPossible` is false. By default, it is
19+
/// `FormBuilderLocalizations.current.andSeparator`,
20+
///
21+
/// # Errors
22+
/// - Throws [AssertionError] if `validators` is empty.
23+
Validator<T> and<T extends Object>(
24+
List<Validator<T>> validators, {
25+
String prefix = '',
26+
String suffix = '',
27+
String? separator,
28+
bool printErrorAsSoonAsPossible = true,
29+
}) {
30+
assert(validators.isNotEmpty, 'The input validators may not be empty.');
31+
return (T value) {
32+
final List<String> errorMessageBuilder = <String>[];
33+
for (final Validator<T> validator in validators) {
34+
final String? errorMessage = validator(value);
35+
if (errorMessage != null) {
36+
if (printErrorAsSoonAsPossible) {
37+
return errorMessage;
38+
}
39+
errorMessageBuilder.add(errorMessage);
40+
}
41+
}
42+
if (errorMessageBuilder.isNotEmpty) {
43+
return '$prefix${errorMessageBuilder.join(separator ?? andSeparatorTemporary)}$suffix';
44+
}
45+
46+
return null;
47+
};
48+
}
49+
50+
Validator<T> or<T extends Object>(List<Validator<T>> validators) {
51+
return (T value) {
52+
final errorMessageBuilder = <String>[];
53+
for (final validator in validators) {
54+
final errorMessage = validator(value);
55+
if (errorMessage == null) {
56+
return null;
57+
}
58+
errorMessageBuilder.add(errorMessage);
59+
}
60+
return errorMessageBuilder.join(' OR ');
61+
};
62+
}

lib/new_api_prototype/override_error_msg.dart renamed to lib/new_api_prototype/core_validators/override_error_msg.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'constants.dart';
1+
import '../constants.dart';
22

33
/// Replaces any inner message with [errorMsg]. It is useful for changing
44
/// the message of direct validator implementations.

lib/new_api_prototype/required_validators.dart renamed to lib/new_api_prototype/core_validators/required_validators.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import '../localization/l10n.dart';
2-
import 'constants.dart';
1+
import '../../localization/l10n.dart';
2+
import '../constants.dart';
33

44
Validator<T?> isRequired<T extends Object>(
55
Validator<T>? v, {

lib/new_api_prototype/type_validators.dart renamed to lib/new_api_prototype/core_validators/type_validators.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Type validator:
22

3-
import '../localization/l10n.dart';
4-
import 'constants.dart';
3+
import '../../localization/l10n.dart';
4+
import '../constants.dart';
55

66
Validator<T> isString<T extends Object>(Validator<String>? v,
77
{String? isStringMsg}) {
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export 'collection_validators.dart';
2-
export 'composition_validators.dart';
32
export 'constants.dart';
3+
export 'core_validators/composition_validators.dart';
4+
export 'core_validators/override_error_msg.dart';
5+
export 'core_validators/required_validators.dart';
6+
export 'core_validators/type_validators.dart';
47
export 'generic_type_validators.dart';
58
export 'numeric_validators.dart';
6-
export 'override_error_msg.dart';
7-
export 'required_validators.dart';
8-
export 'string_validators.dart';
9-
export 'type_validators.dart';
9+
export 'string_validators/string_validators.dart';

lib/new_api_prototype/string_validators.dart renamed to lib/new_api_prototype/string_validators/string_validators.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import '../localization/l10n.dart';
2-
import 'collection_validators.dart';
3-
import 'composition_validators.dart';
4-
import 'constants.dart';
5-
import 'override_error_msg.dart';
1+
import '../../localization/l10n.dart';
2+
import '../collection_validators.dart';
3+
import '../constants.dart';
4+
import '../core_validators/composition_validators.dart';
5+
import '../core_validators/override_error_msg.dart';
66

77
const _minL = minLength;
88
const _maxL = maxLength;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:form_builder_validators/form_builder_validators.dart';
3+
4+
const String errorGt = 'error gt';
5+
Validator<num> gt(num target) {
6+
return (num v) {
7+
return v > target ? null : errorGt;
8+
};
9+
}
10+
11+
const String errorLtE = 'error lte';
12+
Validator<num> ltE(num target) {
13+
return (num v) {
14+
return v <= target ? null : errorLtE;
15+
};
16+
}
17+
18+
const String errorIsEven = 'error is even';
19+
Validator<num> isEven() {
20+
return (num v) {
21+
if (v is int && v % 2 == 0) {
22+
return null;
23+
}
24+
return errorIsEven;
25+
};
26+
}
27+
28+
void main() {
29+
group('Composition: and', () {
30+
test('Should validate using only one validator', () {
31+
final Validator<num> v1 = and(<Validator<num>>[gt(10)]);
32+
final Validator<num> v2 =
33+
and(<Validator<num>>[gt(10)], printErrorAsSoonAsPossible: false);
34+
35+
expect(v1(9), errorGt);
36+
expect(v1(10), errorGt);
37+
expect(v1(11), isNull);
38+
39+
expect(v2(9), errorGt);
40+
expect(v2(10), errorGt);
41+
expect(v2(11), isNull);
42+
});
43+
test('Should validate using two validators', () {
44+
final Validator<num> v1 = and(<Validator<num>>[gt(10), ltE(19)]);
45+
final Validator<num> v2 = and(<Validator<num>>[ltE(19), gt(10)],
46+
printErrorAsSoonAsPossible: false);
47+
48+
expect(v1(9), errorGt);
49+
expect(v1(10), errorGt);
50+
expect(v1(11), isNull);
51+
expect(v1(18), isNull);
52+
expect(v1(19), isNull);
53+
expect(v1(20), errorLtE);
54+
55+
expect(v2(9), errorGt);
56+
expect(v2(10), errorGt);
57+
expect(v2(11), isNull);
58+
expect(v2(18), isNull);
59+
expect(v2(19), isNull);
60+
expect(v2(20), errorLtE);
61+
});
62+
63+
test(
64+
'Should validate if the input is even and a number greater than 4.6 and less than or equal to 9.0',
65+
() {
66+
final Validator<num> v1 =
67+
and([isEven(), gt(4.6), ltE(9.0)], printErrorAsSoonAsPossible: false);
68+
final Validator<num> v2 = and([ltE(9.0), gt(4.6), isEven()]);
69+
70+
expect(v1(3), '$errorIsEven$andSeparatorTemporary$errorGt');
71+
expect(v1(4), errorGt);
72+
expect(v1(5), errorIsEven);
73+
expect(v1(6.0), errorIsEven);
74+
expect(v1(6), isNull);
75+
expect(v1(10.9), '$errorIsEven$andSeparatorTemporary$errorLtE');
76+
77+
expect(v2(3), errorGt);
78+
expect(v2(4), errorGt);
79+
expect(v2(5), errorIsEven);
80+
expect(v2(6.0), errorIsEven);
81+
expect(v2(6), isNull);
82+
expect(v2(10.9), errorLtE);
83+
});
84+
85+
test(
86+
'Should validate if the input is even, greater than 5 and divisible by 37 with custom prefix, suffix and separator.',
87+
() {
88+
const String prefix = 'prefix';
89+
const String suffix = 'suffix';
90+
const String separator = ' aNd ';
91+
const String errorDivBy37 = 'not divisible by 37';
92+
final Validator<int> v = and(
93+
<Validator<int>>[
94+
isEven(),
95+
gt(5),
96+
(int v) => v % 37 == 0 ? null : errorDivBy37
97+
],
98+
printErrorAsSoonAsPossible: false,
99+
prefix: prefix,
100+
suffix: suffix,
101+
separator: separator,
102+
);
103+
104+
expect(
105+
v(1),
106+
equals('$prefix${[
107+
errorIsEven,
108+
errorGt,
109+
errorDivBy37
110+
].join(separator)}$suffix'));
111+
expect(v(2),
112+
equals('$prefix${[errorGt, errorDivBy37].join(separator)}$suffix'));
113+
expect(
114+
v(7),
115+
equals(
116+
'$prefix${[errorIsEven, errorDivBy37].join(separator)}$suffix'));
117+
expect(v(8), equals('$prefix${[errorDivBy37].join(separator)}$suffix'));
118+
expect(v(37), equals('$prefix${[errorIsEven].join(separator)}$suffix'));
119+
expect(v(74), isNull);
120+
});
121+
122+
test('Should throw AssertionError when the validators input is empty', () {
123+
expect(() => and(<Validator<Object?>>[]), throwsAssertionError);
124+
});
125+
});
126+
}

0 commit comments

Comments
 (0)