beta
This commit is contained in:
parent
0ca81479ff
commit
eb38637a6b
12 changed files with 312 additions and 62 deletions
|
@ -1,5 +1,7 @@
|
|||
# 1.0.5
|
||||
# 1.0.5-beta
|
||||
* Use `wrapMatcher` on explicit values instead of throwing.
|
||||
* Add async matchers.
|
||||
* Add context-aware matchers.
|
||||
|
||||
# 1.0.4
|
||||
* `isNonEmptyString` trims strings.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
library angel_validate;
|
||||
|
||||
export 'package:matcher/matcher.dart';
|
||||
export 'src/context_aware.dart';
|
||||
export 'src/matchers.dart';
|
||||
export 'src/validator.dart';
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/// Support for using `angel_validate` with the Angel Framework.
|
||||
library angel_validate.server;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'src/async.dart';
|
||||
import 'angel_validate.dart';
|
||||
|
@ -50,7 +52,8 @@ RequestMiddleware filterQuery(Iterable<String> only) {
|
|||
RequestMiddleware validate(Validator validator,
|
||||
{String errorMessage: 'Invalid data.'}) {
|
||||
return (RequestContext req, res) async {
|
||||
var result = validator.check(await req.lazyBody());
|
||||
var result =
|
||||
await asyncApplyValidator(validator, await req.lazyBody(), req.app);
|
||||
|
||||
if (result.errors.isNotEmpty) {
|
||||
throw new AngelHttpException.badRequest(
|
||||
|
@ -70,7 +73,7 @@ RequestMiddleware validate(Validator validator,
|
|||
RequestMiddleware validateQuery(Validator validator,
|
||||
{String errorMessage: 'Invalid data.'}) {
|
||||
return (RequestContext req, res) async {
|
||||
var result = validator.check(req.query);
|
||||
var result = await asyncApplyValidator(validator, req.query, req.app);
|
||||
|
||||
if (result.errors.isNotEmpty) {
|
||||
throw new AngelHttpException.badRequest(
|
||||
|
@ -89,8 +92,9 @@ RequestMiddleware validateQuery(Validator validator,
|
|||
/// filtered data before continuing the service event.
|
||||
HookedServiceEventListener validateEvent(Validator validator,
|
||||
{String errorMessage: 'Invalid data.'}) {
|
||||
return (HookedServiceEvent e) {
|
||||
var result = validator.check(e.data as Map);
|
||||
return (HookedServiceEvent e) async {
|
||||
var result = await asyncApplyValidator(
|
||||
validator, e.data as Map, (e.request?.app ?? e.service.app) as Angel);
|
||||
|
||||
if (result.errors.isNotEmpty) {
|
||||
throw new AngelHttpException.badRequest(
|
||||
|
@ -102,3 +106,36 @@ HookedServiceEventListener validateEvent(Validator validator,
|
|||
..addAll(result.data);
|
||||
};
|
||||
}
|
||||
|
||||
/// Asynchronously apply a [validator], running any [AngelMatcher]s.
|
||||
Future<ValidationResult> asyncApplyValidator(
|
||||
Validator validator, Map data, Angel app) async {
|
||||
var result = validator.check(data);
|
||||
if (result.errors.isNotEmpty) return result;
|
||||
|
||||
var errantKeys = <String>[], errors = <String>[];
|
||||
|
||||
for (var key in result.data.keys) {
|
||||
var value = result.data[key];
|
||||
var description = new StringDescription("'$key': expected ");
|
||||
|
||||
for (var rule in validator.rules[key]) {
|
||||
if (rule is AngelMatcher) {
|
||||
var r = await rule.matchesWithAngel(value, key, result.data, {}, app);
|
||||
|
||||
if (!r) {
|
||||
errors.add(rule.describe(description).toString().trim());
|
||||
errantKeys.add(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var m = new Map<String, dynamic>.from(result.data);
|
||||
for (var key in errantKeys) {
|
||||
m.remove(key);
|
||||
}
|
||||
|
||||
return result.withData(m).withErrors(errors);
|
||||
}
|
||||
|
|
|
@ -2,16 +2,14 @@ import 'dart:async';
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'context_aware.dart';
|
||||
|
||||
/// Returns an [AngelMatcher] that uses an arbitrary function that returns
|
||||
/// true or false for the actual value.
|
||||
///
|
||||
/// Analogous to the synchronous [predicate] matcher.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// expect(v, predicate((x) => ((x % 2) == 0), "is even"))
|
||||
AngelMatcher predicateWithAngel(FutureOr<bool> Function(Object, Angel) f,
|
||||
AngelMatcher predicateWithAngel(
|
||||
FutureOr<bool> Function(String, Object, Angel) f,
|
||||
[String description = 'satisfies function']) =>
|
||||
new _PredicateWithAngel(f, description);
|
||||
|
||||
|
@ -19,26 +17,38 @@ AngelMatcher predicateWithAngel(FutureOr<bool> Function(Object, Angel) f,
|
|||
/// to the input.
|
||||
///
|
||||
/// Use this to match values against configuration, injections, etc.
|
||||
AngelMatcher matchWithAngel(FutureOr<Matcher> Function(Object, Angel) f,
|
||||
AngelMatcher matchWithAngel(FutureOr<Matcher> Function(Object, Map, Angel) f,
|
||||
[String description = 'satisfies asynchronously created matcher']) =>
|
||||
new _MatchWithAngel(f, description);
|
||||
|
||||
/// Calls [matchWithAngel] without the initial parameter.
|
||||
AngelMatcher matchWithAngelBinary(
|
||||
FutureOr<Matcher> Function(Map context, Angel) f,
|
||||
[String description = 'satisfies asynchronously created matcher']) =>
|
||||
matchWithAngel((_, context, app) => f(context, app));
|
||||
|
||||
/// Calls [matchWithAngel] without the initial two parameters.
|
||||
AngelMatcher matchWithAngelUnary(FutureOr<Matcher> Function(Angel) f,
|
||||
[String description = 'satisfies asynchronously created matcher']) =>
|
||||
matchWithAngel((_, app) => f(app));
|
||||
matchWithAngelBinary((_, app) => f(app));
|
||||
|
||||
/// Calls [matchWithAngel] without any parameters.
|
||||
AngelMatcher matchWithAngelNullary(FutureOr<Matcher> Function() f,
|
||||
[String description = 'satisfies asynchronously created matcher']) =>
|
||||
matchWithAngelUnary((_) => f());
|
||||
|
||||
/// Returns an [AngelMatcher] that represents [x].
|
||||
///
|
||||
/// If [x] is an [AngelMatcher], then it is returned, unmodified.
|
||||
AngelMatcher wrapAngelMatcher(x) {
|
||||
if (x is AngelMatcher) return x;
|
||||
return matchWithAngel((_, app) => wrapMatcher(x));
|
||||
if (x is ContextAwareMatcher) return new _WrappedAngelMatcher(x);
|
||||
return wrapAngelMatcher(wrapContextAwareMatcher(x));
|
||||
}
|
||||
|
||||
/// Returns an [AngelMatcher] that asynchronously resolves a [feature], builds a [matcher], and executes it.
|
||||
AngelMatcher matchAsync(
|
||||
FutureOr<Matcher> Function(Object) matcher, FutureOr Function() feature,
|
||||
AngelMatcher matchAsync(FutureOr<Matcher> Function(String, Object) matcher,
|
||||
FutureOr Function() feature,
|
||||
[String description = 'satisfies asynchronously created matcher']) {
|
||||
return new _MatchAsync(matcher, feature, description);
|
||||
}
|
||||
|
@ -48,7 +58,7 @@ AngelMatcher matchAsync(
|
|||
AngelMatcher idExistsInService(String servicePath,
|
||||
{String idField: 'id', String description}) {
|
||||
return predicateWithAngel(
|
||||
(item, app) async {
|
||||
(key, item, app) async {
|
||||
try {
|
||||
var result = await app.service(servicePath)?.read(item);
|
||||
return result != null;
|
||||
|
@ -65,17 +75,34 @@ AngelMatcher idExistsInService(String servicePath,
|
|||
}
|
||||
|
||||
/// An asynchronous [Matcher] that runs in the context of an [Angel] app.
|
||||
abstract class AngelMatcher extends Matcher {
|
||||
Future<bool> matchesAsync(item, Map matchState, Angel app);
|
||||
abstract class AngelMatcher extends ContextAwareMatcher {
|
||||
Future<bool> matchesWithAngel(
|
||||
item, String key, Map context, Map matchState, Angel app);
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
bool matchesWithContext(item, String key, Map context, Map matchState) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class _WrappedAngelMatcher extends AngelMatcher {
|
||||
final ContextAwareMatcher matcher;
|
||||
|
||||
_WrappedAngelMatcher(this.matcher);
|
||||
|
||||
@override
|
||||
Description describe(Description description) =>
|
||||
matcher.describe(description);
|
||||
|
||||
@override
|
||||
Future<bool> matchesWithAngel(
|
||||
item, String key, Map context, Map matchState, Angel app) async {
|
||||
return matcher.matchesWithContext(item, key, context, matchState);
|
||||
}
|
||||
}
|
||||
|
||||
class _MatchWithAngel extends AngelMatcher {
|
||||
final FutureOr<Matcher> Function(Object, Angel) f;
|
||||
final FutureOr<Matcher> Function(Object, Map, Angel) f;
|
||||
final String description;
|
||||
|
||||
_MatchWithAngel(this.f, this.description);
|
||||
|
@ -86,15 +113,16 @@ class _MatchWithAngel extends AngelMatcher {
|
|||
: description.add(this.description);
|
||||
|
||||
@override
|
||||
Future<bool> matchesAsync(item, Map matchState, Angel app) {
|
||||
return new Future.sync(() => f(item, app)).then((result) {
|
||||
Future<bool> matchesWithAngel(
|
||||
item, String key, Map context, Map matchState, Angel app) {
|
||||
return new Future.sync(() => f(item, context, app)).then((result) {
|
||||
return result.matches(item, matchState);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _PredicateWithAngel extends AngelMatcher {
|
||||
final FutureOr<bool> Function(Object, Angel) predicate;
|
||||
final FutureOr<bool> Function(String, Object, Angel) predicate;
|
||||
final String description;
|
||||
|
||||
_PredicateWithAngel(this.predicate, this.description);
|
||||
|
@ -105,13 +133,14 @@ class _PredicateWithAngel extends AngelMatcher {
|
|||
: description.add(this.description);
|
||||
|
||||
@override
|
||||
Future<bool> matchesAsync(item, Map matchState, Angel app) {
|
||||
return new Future<bool>.sync(() => predicate(item, app));
|
||||
Future<bool> matchesWithAngel(
|
||||
item, String key, Map context, Map matchState, Angel app) {
|
||||
return new Future<bool>.sync(() => predicate(key, item, app));
|
||||
}
|
||||
}
|
||||
|
||||
class _MatchAsync extends AngelMatcher {
|
||||
final FutureOr<Matcher> Function(Object) matcher;
|
||||
final FutureOr<Matcher> Function(String, Object) matcher;
|
||||
final FutureOr Function() feature;
|
||||
final String description;
|
||||
|
||||
|
@ -123,9 +152,11 @@ class _MatchAsync extends AngelMatcher {
|
|||
: description.add(this.description);
|
||||
|
||||
@override
|
||||
Future<bool> matchesAsync(item, Map matchState, Angel app) async {
|
||||
Future<bool> matchesWithAngel(
|
||||
item, String key, Map context, Map matchState, Angel app) async {
|
||||
var f = await feature();
|
||||
var m = await matcher(f);
|
||||
return m.matches(item, matchState);
|
||||
var m = await matcher(key, f);
|
||||
var c = wrapAngelMatcher(m);
|
||||
return await c.matchesWithAngel(item, key, context, matchState, app);
|
||||
}
|
||||
}
|
||||
|
|
53
lib/src/context_aware.dart
Normal file
53
lib/src/context_aware.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'package:matcher/matcher.dart';
|
||||
|
||||
/// Returns a [ContextAwareMatcher] for the given predicate.
|
||||
ContextAwareMatcher predicateWithContext(
|
||||
bool Function(Object, String, Map, Map) f,
|
||||
[String description = 'satisfies function']) {
|
||||
return new _PredicateWithContext(f, description);
|
||||
}
|
||||
|
||||
/// Wraps [x] in a [ContextAwareMatcher].
|
||||
ContextAwareMatcher wrapContextAwareMatcher(x) {
|
||||
if (x is ContextAwareMatcher)
|
||||
return x;
|
||||
else if (x is Matcher) return new _WrappedContextAwareMatcher(x);
|
||||
return wrapContextAwareMatcher(wrapMatcher(x));
|
||||
}
|
||||
|
||||
/// A special [Matcher] that is aware of the context in which it is being executed.
|
||||
abstract class ContextAwareMatcher extends Matcher {
|
||||
bool matchesWithContext(item, String key, Map context, Map matchState);
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) => true;
|
||||
}
|
||||
|
||||
class _WrappedContextAwareMatcher extends ContextAwareMatcher {
|
||||
final Matcher matcher;
|
||||
|
||||
_WrappedContextAwareMatcher(this.matcher);
|
||||
|
||||
@override
|
||||
Description describe(Description description) =>
|
||||
matcher.describe(description);
|
||||
|
||||
@override
|
||||
bool matchesWithContext(item, String key, Map context, Map matchState) =>
|
||||
matcher.matches(item, matchState);
|
||||
}
|
||||
|
||||
class _PredicateWithContext extends ContextAwareMatcher {
|
||||
final bool Function(Object, String, Map, Map) f;
|
||||
final String desc;
|
||||
|
||||
_PredicateWithContext(this.f, this.desc);
|
||||
|
||||
@override
|
||||
Description describe(Description description) =>
|
||||
desc == null ? description : description.add(desc);
|
||||
|
||||
@override
|
||||
bool matchesWithContext(item, String key, Map context, Map matchState) =>
|
||||
f(item, key, context, matchState);
|
||||
}
|
16
lib/src/context_validator.dart
Normal file
16
lib/src/context_validator.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:matcher/matcher.dart';
|
||||
|
||||
/// A [Matcher] directly invoked by `package:angel_serialize` to validate the context.
|
||||
class ContextValidator extends Matcher {
|
||||
final bool Function(String, Map) validate;
|
||||
final Description Function(Description, String, Map) errorMessage;
|
||||
|
||||
ContextValidator(this.validate, this.errorMessage);
|
||||
|
||||
@override
|
||||
Description describe(Description description) => description;
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) => true;
|
||||
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:matcher/matcher.dart';
|
||||
import 'context_aware.dart';
|
||||
import 'context_validator.dart';
|
||||
|
||||
final RegExp _alphaDash = new RegExp(r'^[A-Za-z0-9_-]+$');
|
||||
final RegExp _alphaNum = new RegExp(r'^[A-Za-z0-9]+$');
|
||||
|
@ -13,6 +15,7 @@ final Matcher isAlphaDash = predicate(
|
|||
'alphanumeric (dashes and underscores are allowed)');
|
||||
|
||||
/// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores.
|
||||
///
|
||||
final Matcher isAlphaNum = predicate(
|
||||
(value) => value is String && _alphaNum.hasMatch(value), 'alphanumeric');
|
||||
|
||||
|
@ -21,7 +24,8 @@ final Matcher isBool = predicate((value) => value is bool, 'a bool');
|
|||
|
||||
/// Asserts that a `String` complies to the RFC 5322 e-mail standard.
|
||||
final Matcher isEmail = predicate(
|
||||
(value) => value is String && _email.hasMatch(value), 'a valid e-mail');
|
||||
(value) => value is String && _email.hasMatch(value),
|
||||
'a valid e-mail address');
|
||||
|
||||
/// Asserts that a value is an `int`.
|
||||
final Matcher isInt = predicate((value) => value is int, 'an integer');
|
||||
|
@ -30,12 +34,28 @@ final Matcher isInt = predicate((value) => value is int, 'an integer');
|
|||
final Matcher isNum = predicate((value) => value is num, 'a number');
|
||||
|
||||
/// Asserts that a value is a `String`.
|
||||
final Matcher isString = predicate((value) => value is String, 'a String');
|
||||
final Matcher isString = predicate((value) => value is String, 'a string');
|
||||
|
||||
/// Asserts that a value is a non-empty `String`.
|
||||
final Matcher isNonEmptyString = predicate(
|
||||
(value) => value is String && value.trim().isNotEmpty,
|
||||
'a non-empty String');
|
||||
'a non-empty string');
|
||||
|
||||
/// Asserts that a value, presumably from a checkbox, is positive.
|
||||
final Matcher isChecked =
|
||||
isIn(const ['yes', 'checked', 'on', '1', 1, 1.0, true, 'true']);
|
||||
|
||||
/// Ensures that a string is an ISO-8601 date string.
|
||||
final Matcher isIso8601DateString = predicate(
|
||||
(x) {
|
||||
try {
|
||||
return x is String && DateTime.parse(x) != null;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'a valid ISO-8601 date string.',
|
||||
);
|
||||
|
||||
/// Asserts that a `String` is an `http://` or `https://` URL.
|
||||
///
|
||||
|
@ -60,3 +80,54 @@ Matcher minLength(int length) => predicate(
|
|||
Matcher maxLength(int length) => predicate(
|
||||
(value) => value is String && value.length >= length,
|
||||
'a string no longer than $length character(s) long');
|
||||
|
||||
/// Asserts that for a key `x`, the context contains an identical item `x_confirmed`.
|
||||
ContextAwareMatcher isConfirmed = predicateWithContext(
|
||||
(item, key, context, matchState) {
|
||||
return equals(item).matches(context['${key}_confirmed'], matchState);
|
||||
},
|
||||
'is confirmed',
|
||||
);
|
||||
|
||||
/// Asserts that for a key `x`, the value of `x` is **not equal to** the value for [key].
|
||||
ContextAwareMatcher differentFrom(String key) {
|
||||
return predicateWithContext(
|
||||
(item, key, context, matchState) {
|
||||
return !equals(item).matches(context[key], matchState);
|
||||
},
|
||||
'is different from the value of "$key"',
|
||||
);
|
||||
}
|
||||
|
||||
/// Asserts that for a key `x`, the value of `x` is **equal to** the value for [key].
|
||||
ContextAwareMatcher sameAs(String key) {
|
||||
return predicateWithContext(
|
||||
(item, key, context, matchState) {
|
||||
return equals(item).matches(context[key], matchState);
|
||||
},
|
||||
'is equal to the value of "$key"',
|
||||
);
|
||||
}
|
||||
|
||||
/// Assert that a key `x` is present, if *all* of the given [keys] are as well.
|
||||
ContextValidator requiredIf(Iterable<String> keys) =>
|
||||
_require((ctx) => keys.every(ctx.containsKey));
|
||||
|
||||
/// Assert that a key `x` is present, if *any* of the given [keys] are as well.
|
||||
ContextValidator requiredAny(Iterable<String> keys) =>
|
||||
_require((ctx) => keys.any(ctx.containsKey));
|
||||
|
||||
/// Assert that a key `x` is present, if *at least one* of the given [keys] is not.
|
||||
ContextValidator requiredWithout(Iterable<String> keys) =>
|
||||
_require((ctx) => !keys.every(ctx.containsKey));
|
||||
|
||||
/// Assert that a key `x` is present, if *none* of the given [keys] are.
|
||||
ContextValidator requiredWithoutAll(Iterable<String> keys) =>
|
||||
_require((ctx) => !keys.any(ctx.containsKey));
|
||||
|
||||
ContextValidator _require(bool Function(Map) f) {
|
||||
return new ContextValidator(
|
||||
(key, context) => f(context) && context.containsKey(key),
|
||||
(desc, key, _) => new StringDescription('Missing required field "$key".'),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'context_aware.dart';
|
||||
import 'context_validator.dart';
|
||||
|
||||
final RegExp _asterisk = new RegExp(r'\*$');
|
||||
final RegExp _forbidden = new RegExp(r'!$');
|
||||
|
@ -113,6 +115,9 @@ class Validator extends Matcher {
|
|||
_importSchema(schema);
|
||||
}
|
||||
|
||||
static bool _hasContextValidators(Iterable it) =>
|
||||
it.any((x) => x is ContextValidator);
|
||||
|
||||
/// Validates, and filters input data.
|
||||
ValidationResult check(Map inputData) {
|
||||
List<String> errors = [];
|
||||
|
@ -136,6 +141,7 @@ class Validator extends Matcher {
|
|||
}
|
||||
|
||||
for (String field in requiredFields) {
|
||||
if (!_hasContextValidators(rules[field] ?? [])) {
|
||||
if (!input.containsKey(field)) {
|
||||
if (!customErrorMessages.containsKey(field))
|
||||
errors.add("'$field' is required.");
|
||||
|
@ -143,6 +149,9 @@ class Validator extends Matcher {
|
|||
errors.add(customError(field, 'none'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run context validators.
|
||||
|
||||
for (var key in input.keys) {
|
||||
if (key is String && rules.containsKey(key)) {
|
||||
|
@ -150,6 +159,19 @@ class Validator extends Matcher {
|
|||
var value = input[key];
|
||||
var description = new StringDescription("'$key': expected ");
|
||||
|
||||
for (var matcher in rules[key]) {
|
||||
if (matcher is ContextValidator) {
|
||||
if (!matcher.validate(key, input)) {
|
||||
errors.add(matcher
|
||||
.errorMessage(description, key, input)
|
||||
.toString()
|
||||
.trim());
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
for (Matcher matcher in rules[key]) {
|
||||
try {
|
||||
if (matcher is Validator) {
|
||||
|
@ -161,7 +183,15 @@ class Validator extends Matcher {
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
if (!matcher.matches(value, {})) {
|
||||
bool result;
|
||||
|
||||
if (matcher is ContextAwareMatcher) {
|
||||
result = matcher.matchesWithContext(value, key, input, {});
|
||||
} else {
|
||||
result = matcher.matches(value, {});
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
if (!customErrorMessages.containsKey(key))
|
||||
errors.add(matcher.describe(description).toString().trim());
|
||||
valid = false;
|
||||
|
@ -174,6 +204,7 @@ class Validator extends Matcher {
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
data[key] = value;
|
||||
|
@ -187,7 +218,7 @@ class Validator extends Matcher {
|
|||
return new ValidationResult().._errors.addAll(errors);
|
||||
}
|
||||
|
||||
return new ValidationResult().._data = data;
|
||||
return new ValidationResult().._data.addAll(data);
|
||||
}
|
||||
|
||||
/// Validates, and filters input data after running [autoParse].
|
||||
|
@ -320,16 +351,23 @@ class Validator extends Matcher {
|
|||
|
||||
/// The result of attempting to validate input data.
|
||||
class ValidationResult {
|
||||
Map<String, dynamic> _data;
|
||||
final Map<String, dynamic> _data = {};
|
||||
final List<String> _errors = [];
|
||||
|
||||
/// The successfully validated data, filtered from the original input.
|
||||
Map<String, dynamic> get data => _data;
|
||||
Map<String, dynamic> get data => new Map<String, dynamic>.unmodifiable(_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);
|
||||
|
||||
ValidationResult withData(Map<String, dynamic> data) => new ValidationResult()
|
||||
.._data.addAll(data)
|
||||
.._errors.addAll(_errors);
|
||||
|
||||
ValidationResult withErrors(Iterable<String> errors) =>
|
||||
new ValidationResult().._data.addAll(_data).._errors.addAll(errors);
|
||||
}
|
||||
|
||||
/// Occurs when user-provided data is invalid.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: angel_validate
|
||||
description: Cross-platform validation library based on `matcher`.
|
||||
version: 1.0.4
|
||||
version: 1.0.5-beta
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/validate
|
||||
environment:
|
||||
|
|
0
test/async_test.dart
Normal file
0
test/async_test.dart
Normal file
|
@ -31,6 +31,7 @@ main() {
|
|||
expect(() {
|
||||
todoSchema
|
||||
.enforce({'id': 'fool', 'text': 'Hello, world!', 'completed': 4});
|
||||
// ignore: deprecated_member_use
|
||||
}, throwsA(new isInstanceOf<ValidationException>()));
|
||||
});
|
||||
|
||||
|
|
0
test/context_aware_test.dart
Normal file
0
test/context_aware_test.dart
Normal file
Loading…
Reference in a new issue