Bright start

This commit is contained in:
thosakwe 2016-12-25 16:26:55 -05:00
parent 09a638f48d
commit e6c0771e88
6 changed files with 340 additions and 1 deletions

3
.analysis-options Normal file
View file

@ -0,0 +1,3 @@
analyzer:
strong-mode: true
exclude: ./scripts-bin/**/*.dart

244
README.md
View file

@ -1,2 +1,244 @@
# validate # 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
View 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
View 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
View 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
View 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