2021-07-09 05:39:39 +00:00
# Angel3 Request Validator
2021-06-21 06:16:06 +00:00
2021-07-09 05:39:39 +00:00
[![version ](https://img.shields.io/badge/pub-v4.0.2-brightgreen )](https://pub.dartlang.org/packages/angel3_validate)
2021-05-14 11:47:44 +00:00
[![Null Safety ](https://img.shields.io/badge/null-safety-brightgreen )](https://dart.dev/null-safety)
2021-05-15 06:35:45 +00:00
[![Gitter ](https://img.shields.io/gitter/room/angel_dart/discussion )](https://gitter.im/angel_dart/discussion)
2021-05-14 11:47:44 +00:00
[![License ](https://img.shields.io/github/license/dukefirehawk/angel )](https://github.com/dukefirehawk/angel/tree/angel3/packages/validate/LICENSE)
2016-12-25 21:26:55 +00:00
2021-07-09 05:39:39 +00:00
Validation library based on the `matcher` library, with Angel3 support. Why re-invent the wheel, when you can use the same validators you already use for tests?
2016-12-26 01:09:24 +00:00
2021-07-09 05:39:39 +00:00
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.
2021-02-14 05:22:25 +00:00
For convenience's sake, this library also exports `matcher` .
2021-07-09 05:39:39 +00:00
- [Angel3 Request Validator ](#angel3-request-validator )
2021-06-21 06:16:06 +00:00
- [Examples ](#examples )
- [Creating a Validator ](#creating-a-validator )
- [Validating data ](#validating-data )
- [Required Fields ](#required-fields )
- [Forbidden Fields ](#forbidden-fields )
- [Default values ](#default-values )
- [Custom Validator Functions ](#custom-validator-functions )
- [Custom Error Messages ](#custom-error-messages )
- [autoParse ](#autoparse )
- [filter ](#filter )
- [Extending Validators ](#extending-validators )
- [Bundled Matchers ](#bundled-matchers )
- [Nested Validators ](#nested-validators )
- [Use with Angel ](#use-with-angel )
## Examples
### Creating a Validator
2016-12-25 21:26:55 +00:00
```dart
2021-05-14 11:47:44 +00:00
import 'package:angel3_validate/angel3_validate.dart';
2021-02-14 05:22:25 +00:00
main() {
var validator = Validator({
'username': isAlphaNum,
'multiple,keys,with,same,rules': [isString, isNotEmpty],
'balance': [
greaterThanOrEqualTo(0),
lessThan(1000000)
],
'nested': [
foo,
[bar, baz]
]
});
}
```
2021-06-21 06:16:06 +00:00
### Validating data
2021-02-14 05:22:25 +00:00
2021-07-09 05:39:39 +00:00
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.
2021-02-14 05:22:25 +00:00
```dart
main() {
var result = validator.check(formData);
if (!result.errors.isNotEmpty) {
// Invalid data
} else {
// Safely handle filtered data
return someSecureOperation(result.data);
}
}
2016-12-25 21:26:55 +00:00
```
2021-02-14 05:22:25 +00:00
You can `enforce` validation rules, and throw an error if validation fails.
2020-05-04 16:42:24 +00:00
```dart
2021-02-14 05:22:25 +00:00
main() {
try {
// `enforce` will return the filtered data.
var safeData = validator.enforce(formData);
} on ValidationException catch(e) {
print(e.errors);
}
}
2020-05-04 16:42:24 +00:00
```
2021-06-21 06:16:06 +00:00
### Required Fields
2021-02-14 05:22:25 +00:00
Fields are optional by default.
2021-07-09 05:39:39 +00:00
Suffix a field name with a `'*'` to mark it as required, and to throw an error if it is not present.
2020-05-04 18:35:16 +00:00
```dart
2021-02-14 05:22:25 +00:00
main() {
var validator = Validator({
'googleId*': isString,
// You can also use `requireField`
requireField('googleId'): isString,
});
}
```
2021-06-21 06:16:06 +00:00
### Forbidden Fields
2021-07-09 05:39:39 +00:00
To prevent a field from showing up in valid data, suffix it with a `'!'` .
2021-02-14 05:22:25 +00:00
2021-06-21 06:16:06 +00:00
### Default values
2021-02-14 05:22:25 +00:00
2021-07-09 05:39:39 +00:00
If not present, default values will be filled in *before* validation. This means that they can still be used with required fields.
2021-02-14 05:22:25 +00:00
```dart
final Validator todo = Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
'completed': false
2020-05-04 18:35:16 +00:00
});
```
2021-07-09 05:39:39 +00:00
Default values can also be parameterless, *synchronous* functions that return a single value.
2021-02-14 05:22:25 +00:00
2021-06-21 06:16:06 +00:00
### Custom Validator Functions
2021-07-09 05:39:39 +00:00
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.
2021-02-14 05:22:25 +00:00
2021-07-09 05:39:39 +00:00
(It simply returns the value of calling [`predicate` ](https://pub.dev/documentation/matcher/latest/matcher/predicate.html ).)
2021-02-14 05:22:25 +00:00
The function must *synchronously* return a `bool` .
2016-12-25 21:26:55 +00:00
```dart
2021-02-14 05:22:25 +00:00
main() {
var validator = Validator({
'key*': (key) {
var file = File('whitelist.txt');
return file.readFileSync().contains(key);
}
});
2016-12-26 13:04:42 +00:00
}
```
2021-06-21 06:16:06 +00:00
### Custom Error Messages
2021-07-09 05:39:39 +00:00
If these are not present, `angel3_validate` will *attempt* to generate a coherent error message on its own.
2021-02-14 05:22:25 +00:00
```dart
Validator({
'age': [greaterThanOrEqualTo(18)]
}, customErrorMessages: {
'age': 'You must be an adult to see this page.'
});
```
2021-06-21 06:16:06 +00:00
2021-02-14 05:22:25 +00:00
The string `{{value}}` will be replaced inside your error message automatically.
2021-06-21 06:16:06 +00:00
### autoParse
2021-02-14 05:22:25 +00:00
Oftentimes, fields that we want to validate as numbers are passed as strings.
Calling `autoParse` will correct this before validation.
```dart
main() {
var parsed = autoParse({
'age': '34',
'weight': '135.6'
}, ['age', 'weight']);
validator.enforce(parsed);
}
```
You can also call `checkParsed` or `enforceParsed` as a shorthand.
2021-06-21 06:16:06 +00:00
### filter
2021-02-14 05:22:25 +00:00
This is a helper function to extract only the desired keys from a `Map` .
```dart
var inputData = {'foo': 'bar', 'a': 'b', '1': 2};
var only = filter(inputData, ['foo']);
print(only); // { foo: bar }
```
2021-06-21 06:16:06 +00:00
### Extending Validators
2021-07-09 05:39:39 +00:00
You can add situation-specific rules within a child validator. You can also use `extend` to mark fields as required or forbidden that originally were not. Default value and custom error message extension is also supported.
2021-02-14 05:22:25 +00:00
```dart
final Validator userValidator = Validator({
'username': isString,
'age': [
isNum,
greaterThanOrEqualTo(18)
]
});
```
2021-07-09 05:39:39 +00:00
To mark a field as now optional, and no longer required, suffix its name with a `'?'` .
2021-02-14 05:22:25 +00:00
```dart
var ageIsOptional = userValidator.extend({
'age?': [
isNum,
greaterThanOrEqualTo(13)
]
});
```
2021-07-09 05:39:39 +00:00
Note that by default, validation rules are simply appended to the existing list. To completely overwrite existing rules, set the `overwrite` flag to `true` .
2021-02-14 05:22:25 +00:00
```dart
register(Map userData) {
var teenUser = userValidator.extend({
'age': lessThan(18)
}, overwrite: true);
}
```
2016-12-25 21:26:55 +00:00
2021-06-21 06:16:06 +00:00
### Bundled Matchers
2021-07-09 05:39:39 +00:00
This library includes some `Matcher` s for common validations, including:
2016-12-25 21:26:55 +00:00
2021-06-21 06:16:06 +00:00
- `isAlphaDash` : Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores.
- `isAlphaNum` : Asserts that a `String` is alphanumeric.
- `isBool` : Asserts that a value either equals `true` or `false` .
- `isEmail` : Asserts that a `String` complies to the RFC 5322 e-mail standard.
- `isInt` : Asserts that a value is an `int` .
- `isNum` : Asserts that a value is a `num` .
- `isString` : Asserts that a value is a `String` .
- `isNonEmptyString` : Asserts that a value is a non-empty `String` .
- `isUrl` : Asserts that a `String` is an HTTPS or HTTP URL.
2016-12-25 21:26:55 +00:00
2021-07-09 05:39:39 +00:00
The remaining functionality is [effectively implemented by the `matcher` package ](https://pub.dev/documentation/matcher/latest/matcher/matcher-library.html ).
2021-02-14 05:22:25 +00:00
2021-06-21 06:16:06 +00:00
### Nested Validators
2021-07-09 05:39:39 +00:00
Very often, the data we validate contains other data within. You can pass a `Validator` instance to the constructor, because it extends the `Matcher` class.
2021-02-14 05:22:25 +00:00
```dart
main() {
var bio = Validator({
'age*': [isInt, greaterThanOrEqualTo(0)],
'birthYear*': isInt,
'countryOfOrigin': isString
});
var book = Validator({
'title*': isString,
'year*': [
isNum,
(year) {
return year < = DateTime.now().year;
}
]
});
var author = Validator({
'bio*': bio,
'books*': [
isList,
everyElement(book)
]
}, defaultValues: {
'books': []
});
}
```
2021-06-21 06:16:06 +00:00
### Use with Angel
2021-02-14 05:22:25 +00:00
`server.dart` exposes seven helper middleware:
2021-06-21 06:16:06 +00:00
- `validate(validator)` : Validates and filters `req.bodyAsMap` , and throws an `AngelHttpException.BadRequest` if data is invalid.
- `validateEvent(validator)` : Sets `e.data` to the result of validation on a service event.
- `validateQuery(validator)` : Same as `validate` , but operates on `req.query` .
- `autoParseBody(fields)` : Auto-parses numbers in `req.bodyAsMap` .
- `autoParseQuery(fields)` : Same as `autoParseBody` , but operates on `req.query` .
- `filterBody(only)` : Filters unwanted data out of `req.bodyAsMap` .
- `filterQuery(only)` : Same as `filterBody` , but operates on `req.query` .
2021-02-14 05:22:25 +00:00
```dart
2021-05-14 11:47:44 +00:00
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_validate/server.dart';
2021-02-14 05:22:25 +00:00
final Validator echo = Validator({
'message*': (String message) => message.length >= 5
});
final Validator todo = Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
'completed': false
});
2021-05-14 11:47:44 +00:00
void main() async {
2021-02-14 05:22:25 +00:00
var app = Angel();
app.chain([validate(echo)]).post('/echo', (req, res) async {
res.write('You said: "${req.bodyAsMap["message"]}"');
});
app.service('api/todos')
..beforeCreated.listen(validateEvent(todo))
..beforeUpdated.listen(validateEvent(todo));
await app.startServer();
}
```