You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Given the current API state, there are some important aspects:
1. Composing validators may generate redundant computation
2. It is necessary to add a flag: checkNullOrEmpty for every validator
3. Composition error message may not be expressive
To address those points, it is possible to use a set of very specialized validators that will be composed to generate more general and robust ones. Basically, the composition will be made using and/otherwise class attributes for each elementary validator.
The dart compiler can be used to help adding expressiveness in such a way that some validators can generate restriction information that can be passed to the sucessive ones of the composition.
For example, it is possible to concatenate a validator that checks if the input is int with another that checks if the input is less than 15, without checking for int again. But it is not possible to have a composition like IsInt and IsBool.
With the implementation of this API change, we may not need checkNullOrEmpty flag anymore, because it would be only necessary to prefix any validator with the more generic one: IsRequired. The redundant computation may be avoided by composing disjoint validators. And the error message also may be improved for compositions, even though this could be done with the current API.
Alternatives you've considered
The implementation of a prototype idea implementation is in the section `Aditional information`
Aditional information
Related issues: #116#119
Example of redundant computation:
Ex. Composing numeric validators between and even will result in the program checking the type and potentially parsing to numeric value. The following two images show the code that would be executed twice if the two validators were composed.
Example of code:
import'dart:io';
// ignore_for_file: public_member_api_docs, always_specify_types/// ## Generic types/// - T: type of the input value, before any possible transformation. It should be more/// generic or equal to W./// - W: type of the transformed value. It should be more specific or equal to T.abstractbaseclassElementaryValidatorInterface<TextendsObject?,
WextendsObject?> {
constElementaryValidatorInterface({
this.and,
this.otherwise,
});
Stringget errorMsg;
/// Checks if [value] is valid and returns its transformed value if so. /// ## Return: a record of the form (isValid, transformedValue)
(bool, W?) transformValueIfValid(T value);
String?validate(T value) {
final (isValid, transformedValue) =transformValueIfValid(value);
if (isValid) {
final completeErrorMsg = [];
for (final validator
in and ??<ElementaryValidatorInterface<W, dynamic>>[]) {
final validation = validator.validate(transformedValue asW);
if (validation ==null) {
returnnull;
}
completeErrorMsg.add(validation);
}
if (completeErrorMsg.isEmpty) {
returnnull;
}
return completeErrorMsg.join(' or ');
}
final completeErrorMsg = [errorMsg];
for (final validator
in otherwise ??<ElementaryValidatorInterface<T, dynamic>>[]) {
final validation = validator.validate(value);
if (validation ==null) {
returnnull;
}
completeErrorMsg.add(validation);
}
return completeErrorMsg.join(' or ');
}
// Here we make the restrictions weaker. But we will get them strong when// overriding those getters.finalList<ElementaryValidatorInterface<W, dynamic>>? and;
finalList<ElementaryValidatorInterface<T, dynamic>>? otherwise;
}
finalclassRequiredValidator<TextendsObject>
extendsElementaryValidatorInterface<T?, T> {
constRequiredValidator({
super.and,
super.otherwise,
});
@overrideStringget errorMsg =>'Value is required.';
@override
(bool, T?) transformValueIfValid(T? value) {
if (value !=null&&
(value is!String|| value.trim().isNotEmpty) &&
(value is!Iterable|| value.isNotEmpty) &&
(value is!Map|| value.isNotEmpty)) {
return (true, value);
}
return (false, null);
}
}
finalclassNotRequiredValidator<TextendsObject>
extendsElementaryValidatorInterface<T?, T?> {
constNotRequiredValidator({
super.and,
// in this case, the or is more restricted, thus, we need to restrict its// type in the constructor.List<ElementaryValidatorInterface<T, dynamic>>? otherwise,
}) :super(otherwise: otherwise);
@overrideStringget errorMsg =>'Value must not be provided.';
@override
(bool, T?) transformValueIfValid(T? value) {
if (value ==null||
(value isString&& value.trim().isEmpty) ||
(value isIterable&& value.isEmpty) ||
(value isMap&& value.isEmpty)) {
return (true, value);
}
return (false, null);
}
}
finalclassIsBool<TextendsObject>
extendsElementaryValidatorInterface<T, bool> {
constIsBool({
super.and,
super.otherwise,
});
@overrideStringget errorMsg =>'Value must be true/false';
@override
(bool, bool?) transformValueIfValid(T value) {
if (value isString) {
final processedValue = value.trim().toLowerCase();
switch (processedValue) {
case'true':return (true, true);
case'false':return (true, false);
}
}
if (value isbool) {
return (true, value);
}
return (false, null);
}
}
finalclassIsInt<TextendsObject>
extendsElementaryValidatorInterface<T, int> {
constIsInt({
super.and,
super.otherwise,
});
@overrideStringget errorMsg =>'Value must be int';
@override
(bool, int?) transformValueIfValid(T value) {
if (value isString) {
final intCandidateValue =int.tryParse(value);
if (intCandidateValue !=null) {
return (true, intCandidateValue);
}
}
if (value isint) {
return (true, value);
}
return (false, null);
}
}
finalclassIsLessThan<Textendsnum>
extendsElementaryValidatorInterface<T, T> {
constIsLessThan(
this.reference, {
super.and,
super.otherwise,
});
finalT reference;
@overrideStringget errorMsg =>'Value must be less than $reference';
@override
(bool, T?) transformValueIfValid(T value) {
final isValid = value < reference;
return (isValid, isValid ? value :null);
}
}
finalclassIsGreaterThan<Textendsnum>
extendsElementaryValidatorInterface<T, T> {
constIsGreaterThan(
this.reference, {
super.and,
super.otherwise,
});
finalT reference;
@overrideStringget errorMsg =>'Value must be greater than $reference';
@override
(bool, T?) transformValueIfValid(T value) {
final isValid = value > reference;
return (isValid, isValid ? value :null);
}
}
finalclassStringLengthLessThanextendsElementaryValidatorInterface<String, String> {
constStringLengthLessThan({requiredthis.referenceValue})
:assert(referenceValue >0);
finalint referenceValue;
@overrideStringget errorMsg =>'Length must be less than $referenceValue';
@override
(bool, String?) transformValueIfValid(String value) {
final isValid = value.length < referenceValue;
return (isValid, isValid ? value :null);
}
}
voidmain() {
print('-------------New validation-------------------------');
print('Enter the value: ');
final value = stdin.readLineSync();
const requiredIntLessThan10Validator =RequiredValidator(
and: [
IsInt(
and: [IsLessThan(10)],
),
],
);
const requiredIntLessThan10OrGreaterThan13OrBool =RequiredValidator<String>(
and: [
IsInt(
and: [
IsGreaterThan(13, otherwise: [IsLessThan(10)])
],
),
IsBool(),
],
);
const optionalDescriptionText =NotRequiredValidator(
otherwise: [
StringLengthLessThan(referenceValue:10),
],
);
// this validator does not compile, because it does not make sense to compare// a bool with an integer/* const validator = RequiredValidator<String>( and: [ IsInt( and: [ IsBool( and: [IsGreaterThan(13)], ) ], ) ], otherwise: [ IsInt(), ], ); */final validation = optionalDescriptionText.validate(value);
print(validation ??'Valid value!');
}
The text was updated successfully, but these errors were encountered:
Is there an existing issue for this?
Package/Plugin version
11.0.0
What you'd like to happen
Alternatives you've considered
Aditional information
Related issues: #116 #119
Example of redundant computation:
Ex. Composing numeric validators
between
andeven
will result in the program checking the type and potentially parsing to numeric value. The following two images show the code that would be executed twice if the two validators were composed.Example of code:
The text was updated successfully, but these errors were encountered: