platform/packages/validate/README.md

300 lines
8.6 KiB
Markdown
Raw Normal View History

# Protevus Validate
2021-06-21 06:16:06 +00:00
![Pub Version (including pre-releases)](https://img.shields.io/pub/v/protevus_validate?include_prereleases)
2021-05-14 11:47:44 +00:00
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
2024-07-07 15:02:49 +00:00
[![Discord](https://img.shields.io/discord/1060322353214660698)](https://discord.gg/3X6bxTUdCM)
[![License](https://img.shields.io/github/license/dart-backend/protevus)](https://github.com/dart-backend/protevus/tree/master/packages/validate/LICENSE)
2016-12-25 21:26:55 +00:00
This validator library is based on the `matcher` library and comes with build in support for Protevus framework. It can be run on both server and client side. Thus, the same validation rules apply to forms on both backend and frontend code.
2021-02-14 05:22:25 +00:00
For convenience's sake, this library also exports `matcher`.
- [Protevus Validate](#protevus-validate)
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 Protevus](#use-with-protevus)
2021-06-21 06:16:06 +00:00
## Examples
### Creating a Validator
2016-12-25 21:26:55 +00:00
```dart
import 'package:protevus_validate/protevus_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.
```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);
}
}
```
2021-06-21 06:16:06 +00:00
### Required Fields
2023-10-27 15:52:29 +00:00
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.
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
2023-10-27 15:52:29 +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. (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
If these are not present, `protevus_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
2023-10-27 15:52:29 +00:00
Oftentimes, fields that we want to validate as numbers are passed as strings. Calling `autoParse` will correct this before validation.
2021-02-14 05:22:25 +00:00
```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': []
});
}
```
### Use with Protevus
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
import 'package:protevus_framework/protevus_framework.dart';
import 'package:protevus_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 {
var app = Protevus();
2021-02-14 05:22:25 +00:00
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();
}
```