Skip to content

Commit 3b89abc

Browse files
authored
Merge pull request #541 from awhitford/validator_type
Enhanced type safety with validations.
2 parents dbd8c54 + aa8f96d commit 3b89abc

File tree

3 files changed

+90
-146
lines changed

3 files changed

+90
-146
lines changed

lib/src/form_builder_field.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ abstract class FormBuilderField<T> extends FormField<T> {
5252
T initialValue,
5353
AutovalidateMode autovalidateMode = AutovalidateMode.onUserInteraction,
5454
bool enabled = true,
55-
FormFieldValidator validator,
55+
FormFieldValidator<T> validator,
5656
@required FormFieldBuilder<T> builder,
5757
@required this.name,
5858
this.valueTransformer,

lib/src/form_builder_validators.dart

Lines changed: 77 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ class FormBuilderValidators {
66
/// [FormFieldValidator] that is composed of other [FormFieldValidator]s.
77
/// Each validator is run against the [FormField] value and if any returns a
88
/// non-null result validation fails, otherwise, validation passes
9-
static FormFieldValidator compose(List<FormFieldValidator> validators) {
9+
static FormFieldValidator<T> compose<T>(
10+
List<FormFieldValidator<T>> validators) {
1011
return (valueCandidate) {
1112
for (var validator in validators) {
1213
final validatorResult = validator.call(valueCandidate);
@@ -38,19 +39,15 @@ class FormBuilderValidators {
3839

3940
/// [FormFieldValidator] that requires the field's value be true.
4041
/// Commonly used for required checkboxes.
41-
static FormFieldValidator equal(
42+
static FormFieldValidator<T> equal<T>(
4243
BuildContext context,
43-
dynamic value, {
44+
T value, {
4445
String errorText,
45-
}) {
46-
return (valueCandidate) {
47-
if (valueCandidate != value) {
48-
return errorText ??
49-
FormBuilderLocalizations.of(context).equalErrorText(value);
50-
}
51-
return null;
52-
};
53-
}
46+
}) =>
47+
(valueCandidate) => valueCandidate != value
48+
? errorText ??
49+
FormBuilderLocalizations.of(context).equalErrorText(value)
50+
: null;
5451

5552
// TODO(any): implement inclusive in l10n
5653
/// [FormFieldValidator] that requires the field's value to be greater than
@@ -104,64 +101,49 @@ class FormBuilderValidators {
104101

105102
/// [FormFieldValidator] that requires the length of the field's value to be
106103
/// greater than or equal to the provided minimum length.
107-
static FormFieldValidator minLength(
104+
static FormFieldValidator<String> minLength(
108105
BuildContext context,
109106
num minLength, {
110107
bool allowEmpty = false,
111108
String errorText,
112109
}) {
113110
assert(minLength > 0);
114111
return (valueCandidate) {
115-
assert(null == valueCandidate || valueCandidate is String);
116112
final valueLength = valueCandidate?.length ?? 0;
117-
if (valueLength < minLength && (!allowEmpty || valueLength > 0)) {
118-
return errorText ??
119-
FormBuilderLocalizations.of(context).minLengthErrorText(minLength);
120-
}
121-
return null;
113+
return valueLength < minLength && (!allowEmpty || valueLength > 0)
114+
? errorText ??
115+
FormBuilderLocalizations.of(context).minLengthErrorText(minLength)
116+
: null;
122117
};
123118
}
124119

125120
/// [FormFieldValidator] that requires the length of the field's value to be
126121
/// less than or equal to the provided maximum length.
127-
static FormFieldValidator maxLength(
122+
static FormFieldValidator<String> maxLength(
128123
BuildContext context,
129124
num maxLength, {
130125
String errorText,
131126
}) {
132127
assert(maxLength > 0);
133-
return (valueCandidate) {
134-
if (null != valueCandidate) {
135-
assert(valueCandidate is String);
136-
if (valueCandidate.length > maxLength) {
137-
return errorText ??
138-
FormBuilderLocalizations.of(context)
139-
.maxLengthErrorText(maxLength);
140-
}
141-
}
142-
return null;
143-
};
128+
return (valueCandidate) => null != valueCandidate &&
129+
valueCandidate.length > maxLength
130+
? errorText ??
131+
FormBuilderLocalizations.of(context).maxLengthErrorText(maxLength)
132+
: null;
144133
}
145134

146135
/// [FormFieldValidator] that requires the field's value to be a valid email address.
147-
static FormFieldValidator email(
136+
static FormFieldValidator<String> email(
148137
BuildContext context, {
149138
String errorText,
150-
}) {
151-
return (valueCandidate) {
152-
if (null != valueCandidate) {
153-
assert(valueCandidate is String);
154-
if (valueCandidate.isNotEmpty && !isEmail(valueCandidate.trim())) {
155-
return errorText ??
156-
FormBuilderLocalizations.of(context).emailErrorText;
157-
}
158-
}
159-
return null;
160-
};
161-
}
139+
}) =>
140+
(valueCandidate) =>
141+
true == valueCandidate?.isNotEmpty && !isEmail(valueCandidate.trim())
142+
? errorText ?? FormBuilderLocalizations.of(context).emailErrorText
143+
: null;
162144

163145
/// [FormFieldValidator] that requires the field's value to be a valid url.
164-
static FormFieldValidator url(
146+
static FormFieldValidator<String> url(
165147
BuildContext context, {
166148
String errorText,
167149
List<String> protocols = const ['http', 'https', 'ftp'],
@@ -170,130 +152,81 @@ class FormBuilderValidators {
170152
bool allowUnderscore = false,
171153
List<String> hostWhitelist = const [],
172154
List<String> hostBlacklist = const [],
173-
}) {
174-
return (valueCandidate) {
175-
if (null != valueCandidate) {
176-
assert(valueCandidate is String);
177-
if (valueCandidate.isNotEmpty &&
178-
!isURL(valueCandidate,
179-
protocols: protocols,
180-
requireTld: requireTld,
181-
requireProtocol: requireProtocol,
182-
allowUnderscore: allowUnderscore,
183-
hostWhitelist: hostWhitelist,
184-
hostBlacklist: hostBlacklist)) {
185-
return errorText ?? FormBuilderLocalizations.of(context).urlErrorText;
186-
}
187-
}
188-
return null;
189-
};
190-
}
155+
}) =>
156+
(valueCandidate) => true == valueCandidate?.isNotEmpty &&
157+
!isURL(valueCandidate,
158+
protocols: protocols,
159+
requireTld: requireTld,
160+
requireProtocol: requireProtocol,
161+
allowUnderscore: allowUnderscore,
162+
hostWhitelist: hostWhitelist,
163+
hostBlacklist: hostBlacklist)
164+
? errorText ?? FormBuilderLocalizations.of(context).urlErrorText
165+
: null;
191166

192167
/// [FormFieldValidator] that requires the field's value to match the provided regex pattern.
193-
static FormFieldValidator match(
168+
static FormFieldValidator<String> match(
194169
BuildContext context,
195170
Pattern pattern, {
196171
String errorText,
197-
}) {
198-
return (valueCandidate) {
199-
if (null != valueCandidate) {
200-
assert(valueCandidate is String);
201-
if (valueCandidate.isNotEmpty &&
202-
!RegExp(pattern).hasMatch(valueCandidate)) {
203-
return errorText ??
204-
FormBuilderLocalizations.of(context).matchErrorText;
205-
}
206-
}
207-
return null;
208-
};
209-
}
172+
}) =>
173+
(valueCandidate) => true == valueCandidate?.isNotEmpty &&
174+
!RegExp(pattern).hasMatch(valueCandidate)
175+
? errorText ?? FormBuilderLocalizations.of(context).matchErrorText
176+
: null;
210177

211178
/// [FormFieldValidator] that requires the field's value to be a valid number.
212-
static FormFieldValidator numeric(
179+
static FormFieldValidator<String> numeric(
213180
BuildContext context, {
214181
String errorText,
215-
}) {
216-
return (valueCandidate) {
217-
if (null != valueCandidate) {
218-
assert(valueCandidate is String);
219-
if (valueCandidate.isNotEmpty && num.tryParse(valueCandidate) == null) {
220-
return errorText ??
221-
FormBuilderLocalizations.of(context).numericErrorText;
222-
}
223-
}
224-
return null;
225-
};
226-
}
182+
}) =>
183+
(valueCandidate) => true == valueCandidate?.isNotEmpty &&
184+
null == num.tryParse(valueCandidate)
185+
? errorText ?? FormBuilderLocalizations.of(context).numericErrorText
186+
: null;
227187

228188
/// [FormFieldValidator] that requires the field's value to be a valid integer.
229-
static FormFieldValidator integer(
189+
static FormFieldValidator<String> integer(
230190
BuildContext context, {
231191
String errorText,
232192
int radix,
233-
}) {
234-
return (valueCandidate) {
235-
if (null != valueCandidate) {
236-
assert(valueCandidate is String);
237-
if (valueCandidate.isNotEmpty &&
238-
int.tryParse(valueCandidate, radix: radix) == null) {
239-
return errorText ??
240-
FormBuilderLocalizations.of(context).numericErrorText;
241-
}
242-
}
243-
return null;
244-
};
245-
}
193+
}) =>
194+
(valueCandidate) => true == valueCandidate?.isNotEmpty &&
195+
null == int.tryParse(valueCandidate, radix: radix)
196+
? errorText ?? FormBuilderLocalizations.of(context).numericErrorText
197+
: null;
246198

247199
/// [FormFieldValidator] that requires the field's value to be a valid credit card number.
248-
static FormFieldValidator creditCard(
200+
static FormFieldValidator<String> creditCard(
249201
BuildContext context, {
250202
String errorText,
251-
}) {
252-
return (valueCandidate) {
253-
if (null != valueCandidate) {
254-
assert(valueCandidate is String);
255-
if (valueCandidate.isNotEmpty && !isCreditCard(valueCandidate)) {
256-
return errorText ??
257-
FormBuilderLocalizations.of(context).creditCardErrorText;
258-
}
259-
}
260-
return null;
261-
};
262-
}
203+
}) =>
204+
(valueCandidate) =>
205+
true == valueCandidate?.isNotEmpty && !isCreditCard(valueCandidate)
206+
? errorText ??
207+
FormBuilderLocalizations.of(context).creditCardErrorText
208+
: null;
263209

264210
/// [FormFieldValidator] that requires the field's value to be a valid IP address.
265-
/// * [version] is a String or an `int`.
266-
// ignore: non_constant_identifier_names
267-
static FormFieldValidator IP(
211+
/// * [version] is a `String` or an `int`.
212+
static FormFieldValidator<String> IP(
268213
BuildContext context, {
269214
dynamic version,
270215
String errorText,
271-
}) {
272-
return (valueCandidate) {
273-
if (null != valueCandidate) {
274-
assert(valueCandidate is String);
275-
if (valueCandidate.isNotEmpty && !isIP(valueCandidate, version)) {
276-
return errorText ?? FormBuilderLocalizations.of(context).ipErrorText;
277-
}
278-
}
279-
return null;
280-
};
281-
}
216+
}) =>
217+
(valueCandidate) =>
218+
true == valueCandidate?.isNotEmpty && !isIP(valueCandidate, version)
219+
? errorText ?? FormBuilderLocalizations.of(context).ipErrorText
220+
: null;
282221

283222
/// [FormFieldValidator] that requires the field's value to be a valid date string.
284-
static FormFieldValidator dateString(
223+
static FormFieldValidator<String> dateString(
285224
BuildContext context, {
286225
String errorText,
287-
}) {
288-
return (valueCandidate) {
289-
if (null != valueCandidate) {
290-
assert(valueCandidate is String);
291-
if (valueCandidate.isNotEmpty && !isDate(valueCandidate)) {
292-
return errorText ??
293-
FormBuilderLocalizations.of(context).dateStringErrorText;
294-
}
295-
}
296-
return null;
297-
};
298-
}
226+
}) =>
227+
(valueCandidate) =>
228+
true == valueCandidate?.isNotEmpty && !isDate(valueCandidate)
229+
? errorText ??
230+
FormBuilderLocalizations.of(context).dateStringErrorText
231+
: null;
299232
}

test/form_builder_validators_test.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ void main() {
4444
expect(validator([]), isNotNull);
4545
}));
4646

47+
testWidgets(
48+
'FormBuilderValidators.equal',
49+
(WidgetTester tester) => testValidations(tester, (context) {
50+
final validator = FormBuilderValidators.equal(context, true);
51+
// Pass
52+
expect(validator(true), isNull);
53+
// Fail
54+
expect(validator(null), isNotNull);
55+
expect(validator(false), isNotNull);
56+
}));
57+
4758
testWidgets(
4859
'FormBuilderValidators.maxLength',
4960
(WidgetTester tester) => testValidations(tester, (context) {
@@ -221,7 +232,7 @@ void main() {
221232
testWidgets(
222233
'FormBuilderValidators.compose',
223234
(WidgetTester tester) => testValidations(tester, (context) {
224-
final validator = FormBuilderValidators.compose([
235+
final validator = FormBuilderValidators.compose<String>([
225236
FormBuilderValidators.required(context),
226237
FormBuilderValidators.numeric(context),
227238
FormBuilderValidators.minLength(context, 2),

0 commit comments

Comments
 (0)