6.3 KiB
validate
Validation library based on the matcher
library, with Angel support.
Why re-invent the wheel, when you can use the same validators you already
use for tests?
This library runs both on the server, and on the client. Thus, you can use the same validation rules for forms on the server, and on the frontend.
Examples
Creating a Validator
import 'package:angel_validate/angel_validate.dart';
main() {
var validator = new Validator({
'username': isAlphaNum,
'balance': [
greaterThanOrEqualTo(0),
lessThan(1000000)
]
});
}
Validating data
The Validator
will filter out fields that have no validation rules.
You can rest easy knowing that attackers cannot slip extra data into
your applications.
main() {
var result = validator.check(formData);
if (!result.errors.isNotEmpty) {
// Invalid data
} else {
// Safely handle filtered data
return someSecureOperation(result.data);
}
}
You can enforce
validation rules, and throw an error if validation fails.
main() {
try {
// `enforce` will return the filtered data.
var safeData = validator.enforce(formData);
} on ValidationException catch(e) {
print(e.errors);
}
}
Required Fields
Fields are optional by default.
Suffix a field name with a '*'
to mark it as required, and
to throw an error if it is not present.
main() {
var validator = new Validator({
'googleId*': isString
});
}
Default values
If not present, default values will be filled in before validation. This means that they can still be used with required fields.
final Validator todo = new Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
'completed': false
});
Default values can also be parameterless, synchronous functions that return a single value.
Custom Validator Functions
Creating a whole Matcher
class is sometimes cumbersome, but if
you pass a function to the constructor, it will be wrapped in a
Matcher
instance.
(It simply returns the value of calling
predicate
.)
The function must synchronously return a bool
.
main() {
var validator = new Validator({
'key*': (key) {
var file = new File('whitelist.txt');
return file.readFileSync().contains(key);
}
});
}
Extending Validators
You can add situation-specific rules within a child validator.
You can also use extend
to mark fields as required that originally
were not. Default value extension is also supported.
final Validator userValidator = new Validator({
'username': isString,
'age': [
isNum,
greaterThanOrEqualTo(18)
]
});
To mark a field as now optional, and no longer required,
suffix its name with a '?'
.
var ageIsOptional = userValidator.extend({
'age?': [
isNum,
greaterThanOrEqualTo(13)
]
});
Note that by default, new validation rules are simply prepended to
the existing list. To completely overwrite existing rules, set the
overwrite
flag to true
.
register(Map userData) {
var teenUser = userValidator.extend({
'age': lessThan(18)
}, overwrite: true);
}
Bundled Matchers
This library includes some Matcher
s for common validations,
including:
isAlphaDash
: Asserts aString
matches the Regular Expression/^[A-Za-z0-9_-]$/
.isAlphaNum
: Asserts aString
matches the Regular Expression/^[A-Za-z0-9]$/
isBool
: Asserts that a value either equalstrue
orfalse
.isEmail
: Asserts aString
complies to the RFC 5322 e-mail standard.isInt
: Asserts a value is anint
.isNegative
: Asserts anum
is less than0
.isNum
: Asserts a value is anum
.isPositive
: Asserts anum
is greater than0
.isString
: Asserts that a value is aString
.
The remaining functionality is
effectively implemented by the matcher
package.
Nested Validators
Very often, the data we validate contains other data within. You can pass
a Validator
instance to the constructor, and it will be wrapped within
a Matcher
instance.
The class also exposes a toMatcher()
method that creates a Matcher that
validates data using the instance.
main() {
var bio = new Validator({
'age*': [isInteger, greaterThanOrEqualTo(0)],
'birthYear*': isInteger,
'countryOfOrigin': isString
});
var book = new Validator({
'title*': isString,
'year*': [
isNum,
(year) {
return year <= new DateTime.now().year;
}
]
});
var author = new Validator({
'bio*': bio,
'books*': [
isList,
everyElement(book.toMatcher())
]
}, defaultValues: {
'books': []
});
}
Use with Angel
server.dart
exposes three helper middleware:
validate(validator)
: Validates and filtersreq.body
, and throws anAngelHttpException.BadRequest
if data is invalid.validateEvent(validator)
: Setse.data
to the result of validation on a service event.validateQuery(validator)
: Same asvalidate
, but operatesreq.query
.
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_validate/server.dart';
final Validator echo = new Validator({
'message*': (String message) => message.length >= 5
});
final Validator todo = new Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
'completed': false
});
main() async {
var app = new Angel();
app.chain(validate(echo)).post('/echo', (req, res) async {
res.write('You said: "${req.body["message"]}"');
});
app.service('api/todos')
..beforeCreated.listen(validateEvent(todo))
..beforeUpdated.listen(validateEvent(todo));
await app.startServer();
}