Apply pedantic

This commit is contained in:
Tobe O 2019-09-27 09:28:20 -04:00
parent f7188f8a62
commit 5d8c2ae005
12 changed files with 106 additions and 94 deletions

View file

@ -36,7 +36,7 @@ For convenience's sake, this library also exports `matcher`.
import 'package:angel_validate/angel_validate.dart'; import 'package:angel_validate/angel_validate.dart';
main() { main() {
var validator = new Validator({ var validator = Validator({
'username': isAlphaNum, 'username': isAlphaNum,
'multiple,keys,with,same,rules': [isString, isNotEmpty], 'multiple,keys,with,same,rules': [isString, isNotEmpty],
'balance': [ 'balance': [
@ -91,7 +91,7 @@ to throw an error if it is not present.
```dart ```dart
main() { main() {
var validator = new Validator({ var validator = Validator({
'googleId*': isString, 'googleId*': isString,
// You can also use `requireField` // You can also use `requireField`
@ -111,7 +111,7 @@ If not present, default values will be filled in *before* validation.
This means that they can still be used with required fields. This means that they can still be used with required fields.
```dart ```dart
final Validator todo = new Validator({ final Validator todo = Validator({
'text*': isString, 'text*': isString,
'completed*': isBool 'completed*': isBool
}, defaultValues: { }, defaultValues: {
@ -134,9 +134,9 @@ The function must *synchronously* return a `bool`.
```dart ```dart
main() { main() {
var validator = new Validator({ var validator = Validator({
'key*': (key) { 'key*': (key) {
var file = new File('whitelist.txt'); var file = File('whitelist.txt');
return file.readFileSync().contains(key); return file.readFileSync().contains(key);
} }
}); });
@ -148,7 +148,7 @@ If these are not present, `angel_validate` will *attempt* to generate
a coherent error message on its own. a coherent error message on its own.
```dart ```dart
new Validator({ Validator({
'age': [greaterThanOrEqualTo(18)] 'age': [greaterThanOrEqualTo(18)]
}, customErrorMessages: { }, customErrorMessages: {
'age': 'You must be an adult to see this page.' 'age': 'You must be an adult to see this page.'
@ -189,7 +189,7 @@ You can also use `extend` to mark fields as required or forbidden that originall
were not. Default value and custom error message extension is also supported. were not. Default value and custom error message extension is also supported.
```dart ```dart
final Validator userValidator = new Validator({ final Validator userValidator = Validator({
'username': isString, 'username': isString,
'age': [ 'age': [
isNum, isNum,
@ -210,7 +210,7 @@ var ageIsOptional = userValidator.extend({
}); });
``` ```
Note that by default, new validation rules are simply appended to Note that by default, validation rules are simply appended to
the existing list. To completely overwrite existing rules, set the the existing list. To completely overwrite existing rules, set the
`overwrite` flag to `true`. `overwrite` flag to `true`.
@ -246,23 +246,23 @@ a `Validator` instance to the constructor, because it extends the
```dart ```dart
main() { main() {
var bio = new Validator({ var bio = Validator({
'age*': [isInt, greaterThanOrEqualTo(0)], 'age*': [isInt, greaterThanOrEqualTo(0)],
'birthYear*': isInt, 'birthYear*': isInt,
'countryOfOrigin': isString 'countryOfOrigin': isString
}); });
var book = new Validator({ var book = Validator({
'title*': isString, 'title*': isString,
'year*': [ 'year*': [
isNum, isNum,
(year) { (year) {
return year <= new DateTime.now().year; return year <= DateTime.now().year;
} }
] ]
}); });
var author = new Validator({ var author = Validator({
'bio*': bio, 'bio*': bio,
'books*': [ 'books*': [
isList, isList,
@ -289,11 +289,11 @@ main() {
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_validate/server.dart'; import 'package:angel_validate/server.dart';
final Validator echo = new Validator({ final Validator echo = Validator({
'message*': (String message) => message.length >= 5 'message*': (String message) => message.length >= 5
}); });
final Validator todo = new Validator({ final Validator todo = Validator({
'text*': isString, 'text*': isString,
'completed*': isBool 'completed*': isBool
}, defaultValues: { }, defaultValues: {
@ -301,7 +301,7 @@ final Validator todo = new Validator({
}); });
main() async { main() async {
var app = new Angel(); var app = Angel();
app.chain([validate(echo)]).post('/echo', (req, res) async { app.chain([validate(echo)]).post('/echo', (req, res) async {
res.write('You said: "${req.bodyAsMap["message"]}"'); res.write('You said: "${req.bodyAsMap["message"]}"');

View file

@ -1,3 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer: analyzer:
strong-mode: strong-mode:
implicit-casts: false implicit-casts: false

View file

@ -1,24 +1,24 @@
import 'package:angel_validate/angel_validate.dart'; import 'package:angel_validate/angel_validate.dart';
main() { main() {
var bio = new Validator({ var bio = Validator({
'age*': [isInt, greaterThanOrEqualTo(0)], 'age*': [isInt, greaterThanOrEqualTo(0)],
'birthYear*': isInt, 'birthYear*': isInt,
'countryOfOrigin': isString 'countryOfOrigin': isString
}); });
var book = new Validator({ var book = Validator({
'title*': isString, 'title*': isString,
'year*': [ 'year*': [
isNum, isNum,
(year) { (year) {
return year <= new DateTime.now().year; return year <= DateTime.now().year;
} }
] ]
}); });
// ignore: unused_local_variable // ignore: unused_local_variable
var author = new Validator({ var author = Validator({
'bio*': bio, 'bio*': bio,
'books*': [isList, everyElement(book)] 'books*': [isList, everyElement(book)]
}, defaultValues: { }, defaultValues: {

View file

@ -52,13 +52,13 @@ RequestHandler filterQuery(Iterable<String> only) {
/// Validates the data in `req.bodyAsMap`, and sets the body to /// Validates the data in `req.bodyAsMap`, and sets the body to
/// filtered data before continuing the response. /// filtered data before continuing the response.
RequestHandler validate(Validator validator, RequestHandler validate(Validator validator,
{String errorMessage: 'Invalid data.'}) { {String errorMessage = 'Invalid data.'}) {
return (RequestContext req, res) async { return (RequestContext req, res) async {
await req.parseBody(); await req.parseBody();
var result = await asyncApplyValidator(validator, req.bodyAsMap, req.app); var result = await asyncApplyValidator(validator, req.bodyAsMap, req.app);
if (result.errors.isNotEmpty) { if (result.errors.isNotEmpty) {
throw new AngelHttpException.badRequest( throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors); message: errorMessage, errors: result.errors);
} }
@ -73,13 +73,13 @@ RequestHandler validate(Validator validator,
/// Validates the data in `req.queryParameters`, and sets the query to /// Validates the data in `req.queryParameters`, and sets the query to
/// filtered data before continuing the response. /// filtered data before continuing the response.
RequestHandler validateQuery(Validator validator, RequestHandler validateQuery(Validator validator,
{String errorMessage: 'Invalid data.'}) { {String errorMessage = 'Invalid data.'}) {
return (RequestContext req, res) async { return (RequestContext req, res) async {
var result = var result =
await asyncApplyValidator(validator, req.queryParameters, req.app); await asyncApplyValidator(validator, req.queryParameters, req.app);
if (result.errors.isNotEmpty) { if (result.errors.isNotEmpty) {
throw new AngelHttpException.badRequest( throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors); message: errorMessage, errors: result.errors);
} }
@ -94,13 +94,13 @@ RequestHandler validateQuery(Validator validator,
/// Validates the data in `e.data`, and sets the data to /// Validates the data in `e.data`, and sets the data to
/// filtered data before continuing the service event. /// filtered data before continuing the service event.
HookedServiceEventListener validateEvent(Validator validator, HookedServiceEventListener validateEvent(Validator validator,
{String errorMessage: 'Invalid data.'}) { {String errorMessage = 'Invalid data.'}) {
return (HookedServiceEvent e) async { return (HookedServiceEvent e) async {
var result = await asyncApplyValidator( var result = await asyncApplyValidator(
validator, e.data as Map, (e.request?.app ?? e.service.app)); validator, e.data as Map, (e.request?.app ?? e.service.app));
if (result.errors.isNotEmpty) { if (result.errors.isNotEmpty) {
throw new AngelHttpException.badRequest( throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors); message: errorMessage, errors: result.errors);
} }
@ -120,7 +120,7 @@ Future<ValidationResult> asyncApplyValidator(
for (var key in result.data.keys) { for (var key in result.data.keys) {
var value = result.data[key]; var value = result.data[key];
var description = new StringDescription("'$key': expected "); var description = StringDescription("'$key': expected ");
for (var rule in validator.rules[key]) { for (var rule in validator.rules[key]) {
if (rule is AngelMatcher) { if (rule is AngelMatcher) {
@ -135,7 +135,7 @@ Future<ValidationResult> asyncApplyValidator(
} }
} }
var m = new Map<String, dynamic>.from(result.data); var m = Map<String, dynamic>.from(result.data);
for (var key in errantKeys) { for (var key in errantKeys) {
m.remove(key); m.remove(key);
} }

View file

@ -11,7 +11,7 @@ import 'context_aware.dart';
AngelMatcher predicateWithAngel( AngelMatcher predicateWithAngel(
FutureOr<bool> Function(String, Object, Angel) f, FutureOr<bool> Function(String, Object, Angel) f,
[String description = 'satisfies function']) => [String description = 'satisfies function']) =>
new _PredicateWithAngel(f, description); _PredicateWithAngel(f, description);
/// Returns an [AngelMatcher] that applies an asynchronously-created [Matcher] /// Returns an [AngelMatcher] that applies an asynchronously-created [Matcher]
/// to the input. /// to the input.
@ -19,7 +19,7 @@ AngelMatcher predicateWithAngel(
/// Use this to match values against configuration, injections, etc. /// Use this to match values against configuration, injections, etc.
AngelMatcher matchWithAngel(FutureOr<Matcher> Function(Object, Map, Angel) f, AngelMatcher matchWithAngel(FutureOr<Matcher> Function(Object, Map, Angel) f,
[String description = 'satisfies asynchronously created matcher']) => [String description = 'satisfies asynchronously created matcher']) =>
new _MatchWithAngel(f, description); _MatchWithAngel(f, description);
/// Calls [matchWithAngel] without the initial parameter. /// Calls [matchWithAngel] without the initial parameter.
AngelMatcher matchWithAngelBinary( AngelMatcher matchWithAngelBinary(
@ -42,7 +42,7 @@ AngelMatcher matchWithAngelNullary(FutureOr<Matcher> Function() f,
/// If [x] is an [AngelMatcher], then it is returned, unmodified. /// If [x] is an [AngelMatcher], then it is returned, unmodified.
AngelMatcher wrapAngelMatcher(x) { AngelMatcher wrapAngelMatcher(x) {
if (x is AngelMatcher) return x; if (x is AngelMatcher) return x;
if (x is ContextAwareMatcher) return new _WrappedAngelMatcher(x); if (x is ContextAwareMatcher) return _WrappedAngelMatcher(x);
return wrapAngelMatcher(wrapContextAwareMatcher(x)); return wrapAngelMatcher(wrapContextAwareMatcher(x));
} }
@ -50,13 +50,13 @@ AngelMatcher wrapAngelMatcher(x) {
AngelMatcher matchAsync(FutureOr<Matcher> Function(String, Object) matcher, AngelMatcher matchAsync(FutureOr<Matcher> Function(String, Object) matcher,
FutureOr Function() feature, FutureOr Function() feature,
[String description = 'satisfies asynchronously created matcher']) { [String description = 'satisfies asynchronously created matcher']) {
return new _MatchAsync(matcher, feature, description); return _MatchAsync(matcher, feature, description);
} }
/// Returns an [AngelMatcher] that verifies that an item with the given [idField] /// Returns an [AngelMatcher] that verifies that an item with the given [idField]
/// exists in the service at [servicePath], without throwing a `404` or returning `null`. /// exists in the service at [servicePath], without throwing a `404` or returning `null`.
AngelMatcher idExistsInService(String servicePath, AngelMatcher idExistsInService(String servicePath,
{String idField: 'id', String description}) { {String idField = 'id', String description}) {
return predicateWithAngel( return predicateWithAngel(
(key, item, app) async { (key, item, app) async {
try { try {
@ -115,7 +115,7 @@ class _MatchWithAngel extends AngelMatcher {
@override @override
Future<bool> matchesWithAngel( Future<bool> matchesWithAngel(
item, String key, Map context, Map matchState, Angel app) { item, String key, Map context, Map matchState, Angel app) {
return new Future.sync(() => f(item, context, app)).then((result) { return Future.sync(() => f(item, context, app)).then((result) {
return result.matches(item, matchState); return result.matches(item, matchState);
}); });
} }
@ -135,7 +135,7 @@ class _PredicateWithAngel extends AngelMatcher {
@override @override
Future<bool> matchesWithAngel( Future<bool> matchesWithAngel(
item, String key, Map context, Map matchState, Angel app) { item, String key, Map context, Map matchState, Angel app) {
return new Future<bool>.sync(() => predicate(key, item, app)); return Future<bool>.sync(() => predicate(key, item, app));
} }
} }

View file

@ -4,14 +4,14 @@ import 'package:matcher/matcher.dart';
ContextAwareMatcher predicateWithContext( ContextAwareMatcher predicateWithContext(
bool Function(Object, String, Map, Map) f, bool Function(Object, String, Map, Map) f,
[String description = 'satisfies function']) { [String description = 'satisfies function']) {
return new _PredicateWithContext(f, description); return _PredicateWithContext(f, description);
} }
/// Wraps [x] in a [ContextAwareMatcher]. /// Wraps [x] in a [ContextAwareMatcher].
ContextAwareMatcher wrapContextAwareMatcher(x) { ContextAwareMatcher wrapContextAwareMatcher(x) {
if (x is ContextAwareMatcher) if (x is ContextAwareMatcher) {
return x; return x;
else if (x is Matcher) return new _WrappedContextAwareMatcher(x); } else if (x is Matcher) return _WrappedContextAwareMatcher(x);
return wrapContextAwareMatcher(wrapMatcher(x)); return wrapContextAwareMatcher(wrapMatcher(x));
} }

View file

@ -2,11 +2,11 @@ import 'package:matcher/matcher.dart';
import 'context_aware.dart'; import 'context_aware.dart';
import 'context_validator.dart'; import 'context_validator.dart';
final RegExp _alphaDash = new RegExp(r'^[A-Za-z0-9_-]+$'); final RegExp _alphaDash = RegExp(r'^[A-Za-z0-9_-]+$');
final RegExp _alphaNum = new RegExp(r'^[A-Za-z0-9]+$'); final RegExp _alphaNum = RegExp(r'^[A-Za-z0-9]+$');
final RegExp _email = new RegExp( final RegExp _email = RegExp(
r"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"); r"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$");
final RegExp _url = new RegExp( final RegExp _url = RegExp(
r'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)'); r'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)');
/// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores. /// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores.
@ -126,8 +126,8 @@ ContextValidator requiredWithoutAll(Iterable<String> keys) =>
_require((ctx) => !keys.any(ctx.containsKey)); _require((ctx) => !keys.any(ctx.containsKey));
ContextValidator _require(bool Function(Map) f) { ContextValidator _require(bool Function(Map) f) {
return new ContextValidator( return ContextValidator(
(key, context) => f(context) && context.containsKey(key), (key, context) => f(context) && context.containsKey(key),
(desc, key, _) => new StringDescription('Missing required field "$key".'), (desc, key, _) => StringDescription('Missing required field "$key".'),
); );
} }

View file

@ -3,9 +3,9 @@ import 'package:matcher/matcher.dart';
import 'context_aware.dart'; import 'context_aware.dart';
import 'context_validator.dart'; import 'context_validator.dart';
final RegExp _asterisk = new RegExp(r'\*$'); final RegExp _asterisk = RegExp(r'\*$');
final RegExp _forbidden = new RegExp(r'!$'); final RegExp _forbidden = RegExp(r'!$');
final RegExp _optional = new RegExp(r'\?$'); final RegExp _optional = RegExp(r'\?$');
/// Returns a value based the result of a computation. /// Returns a value based the result of a computation.
typedef DefaultValueFunction(); typedef DefaultValueFunction();
@ -84,10 +84,11 @@ class Validator extends Matcher {
var iterable = []; var iterable = [];
_addTo(x) { _addTo(x) {
if (x is Iterable) if (x is Iterable) {
x.forEach(_addTo); x.forEach(_addTo);
else } else {
iterable.add(x); iterable.add(x);
}
} }
_iterable.forEach(_addTo); _iterable.forEach(_addTo);
@ -108,8 +109,8 @@ class Validator extends Matcher {
Validator.empty(); Validator.empty();
Validator(Map<String, dynamic> schema, Validator(Map<String, dynamic> schema,
{Map<String, dynamic> defaultValues: const {}, {Map<String, dynamic> defaultValues = const {},
Map<String, dynamic> customErrorMessages: const {}}) { Map<String, dynamic> customErrorMessages = const {}}) {
this.defaultValues.addAll(defaultValues ?? {}); this.defaultValues.addAll(defaultValues ?? {});
this.customErrorMessages.addAll(customErrorMessages ?? {}); this.customErrorMessages.addAll(customErrorMessages ?? {});
_importSchema(schema); _importSchema(schema);
@ -121,7 +122,7 @@ class Validator extends Matcher {
/// Validates, and filters input data. /// Validates, and filters input data.
ValidationResult check(Map inputData) { ValidationResult check(Map inputData) {
List<String> errors = []; List<String> errors = [];
var input = new Map.from(inputData); var input = Map.from(inputData);
Map<String, dynamic> data = {}; Map<String, dynamic> data = {};
for (String key in defaultValues.keys) { for (String key in defaultValues.keys) {
@ -133,20 +134,22 @@ class Validator extends Matcher {
for (String field in forbiddenFields) { for (String field in forbiddenFields) {
if (input.containsKey(field)) { if (input.containsKey(field)) {
if (!customErrorMessages.containsKey(field)) if (!customErrorMessages.containsKey(field)) {
errors.add("'$field' is forbidden."); errors.add("'$field' is forbidden.");
else } else {
errors.add(customError(field, input[field])); errors.add(customError(field, input[field]));
}
} }
} }
for (String field in requiredFields) { for (String field in requiredFields) {
if (!_hasContextValidators(rules[field] ?? [])) { if (!_hasContextValidators(rules[field] ?? [])) {
if (!input.containsKey(field)) { if (!input.containsKey(field)) {
if (!customErrorMessages.containsKey(field)) if (!customErrorMessages.containsKey(field)) {
errors.add("'$field' is required."); errors.add("'$field' is required.");
else } else {
errors.add(customError(field, 'none')); errors.add(customError(field, 'none'));
}
} }
} }
} }
@ -157,7 +160,7 @@ class Validator extends Matcher {
if (key is String && rules.containsKey(key)) { if (key is String && rules.containsKey(key)) {
var valid = true; var valid = true;
var value = input[key]; var value = input[key];
var description = new StringDescription("'$key': expected "); var description = StringDescription("'$key': expected ");
for (var matcher in rules[key]) { for (var matcher in rules[key]) {
if (matcher is ContextValidator) { if (matcher is ContextValidator) {
@ -192,8 +195,9 @@ class Validator extends Matcher {
} }
if (!result) { if (!result) {
if (!customErrorMessages.containsKey(key)) if (!customErrorMessages.containsKey(key)) {
errors.add(matcher.describe(description).toString().trim()); errors.add(matcher.describe(description).toString().trim());
}
valid = false; valid = false;
break; break;
} }
@ -215,10 +219,10 @@ class Validator extends Matcher {
} }
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
return new ValidationResult().._errors.addAll(errors); return ValidationResult().._errors.addAll(errors);
} }
return new ValidationResult().._data.addAll(data); return ValidationResult().._data.addAll(data);
} }
/// Validates, and filters input data after running [autoParse]. /// Validates, and filters input data after running [autoParse].
@ -227,29 +231,30 @@ class Validator extends Matcher {
/// Renders the given custom error. /// Renders the given custom error.
String customError(String key, value) { String customError(String key, value) {
if (!customErrorMessages.containsKey(key)) if (!customErrorMessages.containsKey(key)) {
throw new ArgumentError("No custom error message registered for '$key'."); throw ArgumentError("No custom error message registered for '$key'.");
}
var msg = customErrorMessages[key]; var msg = customErrorMessages[key];
if (msg is String) if (msg is String) {
return msg.replaceAll('{{value}}', value.toString()); return msg.replaceAll('{{value}}', value.toString());
else if (msg is CustomErrorMessageFunction) { } else if (msg is CustomErrorMessageFunction) {
return msg(value); return msg(value);
} }
throw new ArgumentError("Invalid custom error message '$key': $msg"); throw ArgumentError("Invalid custom error message '$key': $msg");
} }
/// Validates input data, and throws an error if it is invalid. /// Validates input data, and throws an error if it is invalid.
/// ///
/// Otherwise, the filtered data is returned. /// Otherwise, the filtered data is returned.
Map<String, dynamic> enforce(Map inputData, Map<String, dynamic> enforce(Map inputData,
{String errorMessage: 'Invalid data.'}) { {String errorMessage = 'Invalid data.'}) {
var result = check(inputData); var result = check(inputData);
if (result._errors.isNotEmpty) { if (result._errors.isNotEmpty) {
throw new ValidationException(errorMessage, errors: result._errors); throw ValidationException(errorMessage, errors: result._errors);
} }
return result.data; return result.data;
@ -263,11 +268,11 @@ class Validator extends Matcher {
/// Creates a copy with additional validation rules. /// Creates a copy with additional validation rules.
Validator extend(Map<String, dynamic> schema, Validator extend(Map<String, dynamic> schema,
{Map<String, dynamic> defaultValues: const {}, {Map<String, dynamic> defaultValues = const {},
Map<String, dynamic> customErrorMessages: const {}, Map<String, dynamic> customErrorMessages = const {},
bool overwrite: false}) { bool overwrite = false}) {
Map<String, dynamic> _schema = {}; Map<String, dynamic> _schema = {};
var child = new Validator.empty() var child = Validator.empty()
..defaultValues.addAll(this.defaultValues) ..defaultValues.addAll(this.defaultValues)
..defaultValues.addAll(defaultValues ?? {}) ..defaultValues.addAll(defaultValues ?? {})
..customErrorMessages.addAll(this.customErrorMessages) ..customErrorMessages.addAll(this.customErrorMessages)
@ -355,18 +360,18 @@ class ValidationResult {
final List<String> _errors = []; final List<String> _errors = [];
/// The successfully validated data, filtered from the original input. /// The successfully validated data, filtered from the original input.
Map<String, dynamic> get data => new Map<String, dynamic>.unmodifiable(_data); Map<String, dynamic> get data => Map<String, dynamic>.unmodifiable(_data);
/// A list of errors that resulted in the given data being marked invalid. /// A list of errors that resulted in the given data being marked invalid.
/// ///
/// This is empty if validation was successful. /// This is empty if validation was successful.
List<String> get errors => new List<String>.unmodifiable(_errors); List<String> get errors => List<String>.unmodifiable(_errors);
ValidationResult withData(Map<String, dynamic> data) => ValidationResult withData(Map<String, dynamic> data) =>
new ValidationResult().._data.addAll(data).._errors.addAll(_errors); ValidationResult().._data.addAll(data).._errors.addAll(_errors);
ValidationResult withErrors(Iterable<String> errors) => ValidationResult withErrors(Iterable<String> errors) =>
new ValidationResult().._data.addAll(_data).._errors.addAll(errors); ValidationResult().._data.addAll(_data).._errors.addAll(errors);
} }
/// Occurs when user-provided data is invalid. /// Occurs when user-provided data is invalid.
@ -377,8 +382,8 @@ class ValidationException extends AngelHttpException {
/// A descriptive message describing the error. /// A descriptive message describing the error.
final String message; final String message;
ValidationException(this.message, {List<String> errors: const []}) ValidationException(this.message, {List<String> errors = const []})
: super(new FormatException(message), : super(FormatException(message),
statusCode: 400, statusCode: 400,
errors: errors ?? [], errors: errors ?? [],
stackTrace: StackTrace.current) { stackTrace: StackTrace.current) {

View file

@ -15,4 +15,5 @@ dev_dependencies:
build_web_compilers: ^0.4.0 build_web_compilers: ^0.4.0
logging: ^0.11.0 logging: ^0.11.0
mock_request: mock_request:
pedantic: ^1.0.0
test: ^1.0.0 test: ^1.0.0

View file

@ -1,10 +1,10 @@
import 'package:angel_validate/angel_validate.dart'; import 'package:angel_validate/angel_validate.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
final Validator emailSchema = new Validator({'to': isEmail}, final Validator emailSchema =
customErrorMessages: {'to': 'Hello, world!'}); Validator({'to': isEmail}, customErrorMessages: {'to': 'Hello, world!'});
final Validator todoSchema = new Validator({ final Validator todoSchema = Validator({
'id': [isInt, isPositive], 'id': [isInt, isPositive],
'text*': isString, 'text*': isString,
'completed*': isBool, 'completed*': isBool,
@ -32,7 +32,7 @@ main() {
todoSchema todoSchema
.enforce({'id': 'fool', 'text': 'Hello, world!', 'completed': 4}); .enforce({'id': 'fool', 'text': 'Hello, world!', 'completed': 4});
// ignore: deprecated_member_use // ignore: deprecated_member_use
}, throwsA(new isInstanceOf<ValidationException>())); }, throwsA(isInstanceOf<ValidationException>()));
}); });
test('filter', () { test('filter', () {

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart'; import 'package:angel_framework/http.dart';
@ -7,7 +8,7 @@ import 'package:logging/logging.dart';
import 'package:mock_request/mock_request.dart'; import 'package:mock_request/mock_request.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
final Validator echoSchema = new Validator({'message*': isString}); final Validator echoSchema = Validator({'message*': isString});
void printRecord(LogRecord rec) { void printRecord(LogRecord rec) {
print(rec); print(rec);
@ -21,8 +22,8 @@ main() {
TestClient client; TestClient client;
setUp(() async { setUp(() async {
app = new Angel(); app = Angel();
http = new AngelHttp(app, useZone: false); http = AngelHttp(app, useZone: false);
app.chain([validate(echoSchema)]).post('/echo', app.chain([validate(echoSchema)]).post('/echo',
(RequestContext req, res) async { (RequestContext req, res) async {
@ -30,7 +31,7 @@ main() {
res.write('Hello, ${req.bodyAsMap['message']}!'); res.write('Hello, ${req.bodyAsMap['message']}!');
}); });
app.logger = new Logger('angel')..onRecord.listen(printRecord); app.logger = Logger('angel')..onRecord.listen(printRecord);
client = await connectTo(app); client = await connectTo(app);
}); });
@ -51,12 +52,15 @@ main() {
}); });
test('enforce', () async { test('enforce', () async {
var rq = new MockHttpRequest('POST', new Uri(path: '/echo')) var rq = MockHttpRequest('POST', Uri(path: '/echo'))
..headers.add('accept', '*/*') ..headers.add('accept', '*/*')
..headers.add('content-type', 'application/json') ..headers.add('content-type', 'application/json')
..write(json.encode({'foo': 'bar'})) ..write(json.encode({'foo': 'bar'}));
..close();
http.handleRequest(rq); scheduleMicrotask(() async {
await rq.close();
await http.handleRequest(rq);
});
var responseBody = await rq.response.transform(utf8.decoder).join(); var responseBody = await rq.response.transform(utf8.decoder).join();
print('Response: ${responseBody}'); print('Response: ${responseBody}');

View file

@ -5,7 +5,7 @@ final $errors = querySelector('#errors') as UListElement;
final $form = querySelector('#form') as FormElement; final $form = querySelector('#form') as FormElement;
final $blank = querySelector('[name="blank"]') as InputElement; final $blank = querySelector('[name="blank"]') as InputElement;
final Validator formSchema = new Validator({ final Validator formSchema = Validator({
'firstName*': [isString, isNotEmpty], 'firstName*': [isString, isNotEmpty],
'lastName*': [isString, isNotEmpty], 'lastName*': [isString, isNotEmpty],
'age*': [isInt, greaterThanOrEqualTo(18)], 'age*': [isInt, greaterThanOrEqualTo(18)],
@ -15,12 +15,13 @@ final Validator formSchema = new Validator({
'familySize': 1 'familySize': 1
}, customErrorMessages: { }, customErrorMessages: {
'age': (age) { 'age': (age) {
if (age is int && age < 18) if (age is int && age < 18) {
return 'Only adults can register for passports. Sorry, kid!'; return 'Only adults can register for passports. Sorry, kid!';
else if (age == null || (age is String && age.trim().isEmpty)) } else if (age == null || (age is String && age.trim().isEmpty)) {
return 'Age is required.'; return 'Age is required.';
else } else {
return 'Age must be a positive integer. Unless you are a monster...'; return 'Age must be a positive integer. Unless you are a monster...';
}
}, },
'blank': 'blank':
"I told you to leave that field blank, but instead you typed '{{value}}'..." "I told you to leave that field blank, but instead you typed '{{value}}'..."
@ -54,12 +55,12 @@ main() {
'Number of People in Family: ${passportInfo["familySize"]}')); 'Number of People in Family: ${passportInfo["familySize"]}'));
} on ValidationException catch (e) { } on ValidationException catch (e) {
$errors.children.addAll(e.errors.map((error) { $errors.children.addAll(e.errors.map((error) {
return new LIElement()..text = error; return LIElement()..text = error;
})); }));
} }
}); });
} }
LIElement success(String str) => new LIElement() LIElement success(String str) => LIElement()
..classes.add('success') ..classes.add('success')
..text = str; ..text = str;