Bright start
This commit is contained in:
parent
09a638f48d
commit
e6c0771e88
6 changed files with 340 additions and 1 deletions
3
.analysis-options
Normal file
3
.analysis-options
Normal file
|
@ -0,0 +1,3 @@
|
|||
analyzer:
|
||||
strong-mode: true
|
||||
exclude: ./scripts-bin/**/*.dart
|
244
README.md
244
README.md
|
@ -1,2 +1,244 @@
|
|||
# validate
|
||||
Request validation middleware based on the `matcher` library.
|
||||
[![version 0.0.0](https://img.shields.io/badge/pub-v0.0.0-red.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)
|
||||
|
||||
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
|
||||
|
||||
```dart
|
||||
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.
|
||||
|
||||
```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 = 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.
|
||||
|
||||
```dart
|
||||
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`](https://www.dartdocs.org/documentation/matcher/0.12.0%2B2/matcher/predicate.html).)
|
||||
|
||||
The function must *synchronously* return a `bool`.
|
||||
|
||||
```dart
|
||||
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.
|
||||
|
||||
```dart
|
||||
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 `'?'`.
|
||||
|
||||
```dart
|
||||
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`.
|
||||
|
||||
```dart
|
||||
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 a `String` matches the Regular Expression ```/^[A-Za-z0-9_-]$/```.
|
||||
* `isAlphaNum`: Asserts a `String` matches the Regular Expression ```/^[A-Za-z0-9]$/```
|
||||
* `isBool`: Asserts that a value either equals `true` or `false`.
|
||||
* `isEmail`: Asserts a `String` complies to the RFC 5322 e-mail standard.
|
||||
* `isInt`: Asserts a value is an `int`.
|
||||
* `isNegative`: Asserts a `num` is less than `0`.
|
||||
* `isNum`: Asserts a value is a `num`.
|
||||
* `isPositive`: Asserts a `num` is greater than `0`.
|
||||
* `isString`: Asserts that a value is a `String`.
|
||||
|
||||
The remaining functionality is
|
||||
[effectively implemented by the `matcher` package](https://www.dartdocs.org/documentation/matcher/0.12.0%2B2/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, 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.
|
||||
|
||||
```dart
|
||||
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 filters `req.body`, 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 `req.query`.
|
||||
|
||||
```dart
|
||||
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();
|
||||
}
|
||||
```
|
4
lib/angel_validate.dart
Normal file
4
lib/angel_validate.dart
Normal file
|
@ -0,0 +1,4 @@
|
|||
/// Cross-platform validation library based on `matcher`.
|
||||
library angel_validate;
|
||||
|
||||
export 'src/validator.dart';
|
25
lib/server.dart
Normal file
25
lib/server.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
/// Support for using `angel_validate` with the Angel Framework.
|
||||
library angel_validate.server;
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'angel_validate.dart';
|
||||
export 'angel_validate.dart';
|
||||
|
||||
/// Validates the data in `req.body`, and sets the body to
|
||||
/// filtered data before continuing the response.
|
||||
RequestMiddleware validate(Validator validator, {String errorMessage}) {
|
||||
|
||||
}
|
||||
|
||||
/// Validates the data in `req.body`, and sets the query to
|
||||
/// filtered data before continuing the response.
|
||||
RequestMiddleware validateQuery(Validator validator, {String errorMessage}) {
|
||||
|
||||
}
|
||||
|
||||
/// Validates the data in `e.data`, and sets the data to
|
||||
/// filtered data before continuing the service event.
|
||||
HookedServiceEventListener validateEvent(Validator validator) {
|
||||
|
||||
}
|
||||
|
53
lib/src/validator.dart
Normal file
53
lib/src/validator.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
/// Enforces the validity of input data, according to [Matcher]s.
|
||||
class Validator {
|
||||
/// Validates, and filters input data.
|
||||
ValidationResult check(Map inputData) {}
|
||||
|
||||
/// Validates input data, and throws an error if it is invalid.
|
||||
///
|
||||
/// Otherwise, the filtered data is returned.
|
||||
Map enforce(Map inputData, {String errorMessage: 'Invalid data.'}) {}
|
||||
}
|
||||
|
||||
/// The result of attempting to validate input data.
|
||||
class ValidationResult {
|
||||
Map _data;
|
||||
final List<String> _errors = [];
|
||||
|
||||
/// The successfully validated data, filtered from the original input.
|
||||
Map get data => _data;
|
||||
|
||||
/// A list of errors that resulted in the given data being marked invalid.
|
||||
///
|
||||
/// This is empty if validation was successful.
|
||||
List<String> get errors => new List<String>.unmodifiable(_errors);
|
||||
}
|
||||
|
||||
/// Occurs when user-provided data is invalid.
|
||||
class ValidationException {
|
||||
/// A list of errors that resulted in the given data being marked invalid.
|
||||
final List<String> errors = [];
|
||||
|
||||
/// A descriptive message describing the error.
|
||||
final String message;
|
||||
|
||||
ValidationException(this.message, {List<String> errors: const []}) {
|
||||
if (errors != null) this.errors.addAll(errors);
|
||||
}
|
||||
|
||||
@override
|
||||
String get toString {
|
||||
if (errors.isEmpty) {
|
||||
return message;
|
||||
}
|
||||
|
||||
if (errors.length == 1) {
|
||||
return 'Validation error: ${errors.first}';
|
||||
}
|
||||
|
||||
var messages = ['${errors.length} validation errors:\n']
|
||||
..addAll(errors.map((error) => '* $error'));
|
||||
|
||||
return messages.join('\n');
|
||||
}
|
||||
}
|
12
pubspec.yaml
Normal file
12
pubspec.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: angel_validate
|
||||
description: Cross-platform validation library based on `matcher`.
|
||||
version: 0.0.0
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/validate
|
||||
environment:
|
||||
sdk: ">=1.19.0"
|
||||
dependencies:
|
||||
angel_framework: ^1.0.0-dev
|
||||
matcher: ^0.12.0
|
||||
dev_dependencies:
|
||||
test: ^0.12.18
|
Loading…
Reference in a new issue