New readme

This commit is contained in:
Tobe O 2019-10-16 21:58:24 -04:00
parent 2023cf221d
commit 7420a9e090
3 changed files with 51 additions and 283 deletions

322
README.md
View file

@ -2,225 +2,67 @@
[![Pub](https://img.shields.io/pub/v/angel_validate.svg)](https://pub.dartlang.org/packages/angel_validate) [![Pub](https://img.shields.io/pub/v/angel_validate.svg)](https://pub.dartlang.org/packages/angel_validate)
[![build status](https://travis-ci.org/angel-dart/validate.svg)](https://travis-ci.org/angel-dart/validate) [![build status](https://travis-ci.org/angel-dart/validate.svg)](https://travis-ci.org/angel-dart/validate)
[Live Example](https://angel-dart.github.io/validate) Strongly-typed form handlers and validators for Angel.
Validation library based on the `matcher` library, with Angel support. Validation library based on the `matcher` library, with Angel support.
Why re-invent the wheel, when you can use the same validators you already Why re-invent the wheel, when you can use the same validators you already
use for tests? 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.
For convenience's sake, this library also exports `matcher`. For convenience's sake, this library also exports `matcher`.
* [Examples](#examples) # Field
* [Creating a Validator](#creating-a-validator) The basic unit is the `Field` class, which is a type-safe way to read
* [Validating Data](#validating-data) values from a `RequestContext`. Here is a simple example of using a
* [Required Fields](#required-fields) `TextField` instance to read a value from the URL query parameters:
* [Forbidden Fields](#forbidden-fields)
* [Default Values](#default-values)
* [Custom Validator Functions](#custom-validator-functions)
* [Auto-parsing Numbers](#autoparse)
* [Filtering Maps](#filter)
* [Custom Error Messages](#custom-error-messages)
* [Extending Validators](#extending-validators)
* [Bundled Matchers](#bundled-matchers)
* [Nested Validators](#nested-validators)
* [Use with Angel](#use-with-angel)
# Examples
## Creating a Validator
```dart ```dart
import 'package:angel_validate/angel_validate.dart'; app.get('/hello', (req, res) async {
var nameField = TextField('name');
main() { var name = await nameField.getValue(req, query: true); // String
var validator = Validator({ return 'Hello, $name!';
'username': isAlphaNum,
'multiple,keys,with,same,rules': [isString, isNotEmpty],
'balance': [
greaterThanOrEqualTo(0),
lessThan(1000000)
],
'nested': [
foo,
[bar, baz]
]
});
}
```
## 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.
```dart
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.
```dart
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.
```dart
main() {
var validator = Validator({
'googleId*': isString,
// You can also use `requireField`
requireField('googleId'): isString,
});
}
```
## Forbidden Fields
To prevent a field from showing up in valid data, suffix it
with a `'!'`.
## Default values
If not present, default values will be filled in *before* validation.
This means that they can still be used with required fields.
```dart
final Validator todo = Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
'completed': false
}); });
``` ```
Default values can also be parameterless, *synchronous* functions There are several included field types:
that return a single value. * `TextField`
* `BoolField`
* `NumField`
* `DoubleField`
* `IntField`
* `DateTimeField`
* `FileField`
* `ImageField`
## Custom Validator Functions # Forms
Creating a whole `Matcher` class is sometimes cumbersome, but if The `Form` class lets you combine `Field` instances, and decode
you pass a function to the constructor, it will be wrapped in a request bodies into `Map<String, dynamic>`. Unrecognized fields are
`Matcher` instance. stripped out of the body, so a `Form` is effectively a whitelist.
(It simply returns the value of calling
[`predicate`](https://www.dartdocs.org/documentation/matcher/0.12.0%2B2/matcher/predicate.html).)
The function must *synchronously* return a `bool`.
```dart ```dart
main() { var todoForm = Form(fields: [
var validator = Validator({ TextField('text'),
'key*': (key) { BoolField('is_complete'),
var file = File('whitelist.txt'); ]);
return file.readFileSync().contains(key);
} // Validate a request body, and deserialize it immediately.
}); var todo = await todoForm.deserialize(req, TodoSerializer.fromMap);
// Same as above, but with a Codec<Todo, Map> (i.e. via `angel_serialize`).
var todo = await todoForm.decode(req, todoSerializer);
// Lower-level functionality, typically not called directly.
// Use it if you want to handle validation errors directly, without
// throwing exceptions.
@serializable
class _Todo {
String text;
bool isComplete;
} }
``` ```
# Custom Error Messages ## Form Rendering
If these are not present, `angel_validate` will *attempt* to generate TODO: Docs about this
a coherent error message on its own.
```dart
Validator({
'age': [greaterThanOrEqualTo(18)]
}, customErrorMessages: {
'age': 'You must be an adult to see this page.'
});
```
The string `{{value}}` will be replaced inside your error message automatically.
# autoParse
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.
# filter
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 }
```
# Extending Validators
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.
```dart
final Validator userValidator = Validator({
'username': isString,
'age': [
isNum,
greaterThanOrEqualTo(18)
]
});
```
To mark a field as now optional, and no longer required,
suffix its name with a `'?'`.
```dart
var ageIsOptional = userValidator.extend({
'age?': [
isNum,
greaterThanOrEqualTo(13)
]
});
```
Note that by default, validation rules are simply appended to
the existing list. To completely overwrite existing rules, set the
`overwrite` flag to `true`.
```dart
register(Map userData) {
var teenUser = userValidator.extend({
'age': lessThan(18)
}, overwrite: true);
}
```
# Bundled Matchers # Bundled Matchers
This library includes some `Matcher`s for common validations, This library includes some `Matcher`s for common validations,
@ -237,80 +79,4 @@ including:
* `isUrl`: Asserts that a `String` is an HTTPS or HTTP URL. * `isUrl`: Asserts that a `String` is an HTTPS or HTTP URL.
The remaining functionality is The remaining functionality is
[effectively implemented by the `matcher` package](https://www.dartdocs.org/documentation/matcher/0.12.0%2B2/matcher/matcher-library.html). [effectively implemented by the `matcher` package](https://www.dartdocs.org/documentation/matcher/latest/matcher/matcher-library.html).
# Nested Validators
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.
```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 Angel
`server.dart` exposes seven helper middleware:
* `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`.
```dart
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_validate/server.dart';
final Validator echo = Validator({
'message*': (String message) => message.length >= 5
});
final Validator todo = Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
'completed': false
});
main() async {
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();
}
```

View file

@ -1,5 +1,2 @@
export 'src/common_fields.dart'; export 'package:matcher/matcher.dart';
export 'src/field.dart'; export 'without_matcher.dart';
export 'src/form.dart';
export 'src/form_renderer.dart';
export 'src/matchers.dart';

5
lib/without_matcher.dart Normal file
View file

@ -0,0 +1,5 @@
export 'src/common_fields.dart';
export 'src/field.dart';
export 'src/form.dart';
export 'src/form_renderer.dart';
export 'src/matchers.dart';