Create async matchers
This commit is contained in:
parent
1c478f0b4a
commit
0ca81479ff
5 changed files with 139 additions and 3 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -29,3 +29,4 @@ pubspec.lock
|
|||
log.txt
|
||||
|
||||
.idea
|
||||
.dart_tool
|
|
@ -1,3 +1,6 @@
|
|||
# 1.0.5
|
||||
* Use `wrapMatcher` on explicit values instead of throwing.
|
||||
|
||||
# 1.0.4
|
||||
* `isNonEmptyString` trims strings.
|
||||
* `ValidationException` extends `AngelHttpException`.
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
library angel_validate.server;
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'src/async.dart';
|
||||
import 'angel_validate.dart';
|
||||
export 'src/async.dart';
|
||||
export 'angel_validate.dart';
|
||||
|
||||
/// Auto-parses numbers in `req.body`.
|
||||
|
|
131
lib/src/async.dart
Normal file
131
lib/src/async.dart
Normal file
|
@ -0,0 +1,131 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||
import 'package:matcher/matcher.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,
|
||||
[String description = 'satisfies function']) =>
|
||||
new _PredicateWithAngel(f, description);
|
||||
|
||||
/// Returns an [AngelMatcher] that applies an asynchronously-created [Matcher]
|
||||
/// to the input.
|
||||
///
|
||||
/// Use this to match values against configuration, injections, etc.
|
||||
AngelMatcher matchWithAngel(FutureOr<Matcher> Function(Object, Angel) f,
|
||||
[String description = 'satisfies asynchronously created matcher']) =>
|
||||
new _MatchWithAngel(f, description);
|
||||
|
||||
/// Calls [matchWithAngel] without the initial parameter.
|
||||
AngelMatcher matchWithAngelUnary(FutureOr<Matcher> Function(Angel) f,
|
||||
[String description = 'satisfies asynchronously created matcher']) =>
|
||||
matchWithAngel((_, app) => f(app));
|
||||
|
||||
/// 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));
|
||||
}
|
||||
|
||||
/// Returns an [AngelMatcher] that asynchronously resolves a [feature], builds a [matcher], and executes it.
|
||||
AngelMatcher matchAsync(
|
||||
FutureOr<Matcher> Function(Object) matcher, FutureOr Function() feature,
|
||||
[String description = 'satisfies asynchronously created matcher']) {
|
||||
return new _MatchAsync(matcher, feature, description);
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
AngelMatcher idExistsInService(String servicePath,
|
||||
{String idField: 'id', String description}) {
|
||||
return predicateWithAngel(
|
||||
(item, app) async {
|
||||
try {
|
||||
var result = await app.service(servicePath)?.read(item);
|
||||
return result != null;
|
||||
} on AngelHttpException catch (e) {
|
||||
if (e.statusCode == 404) {
|
||||
return false;
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
},
|
||||
description ?? 'exists in service $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);
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class _MatchWithAngel extends AngelMatcher {
|
||||
final FutureOr<Matcher> Function(Object, Angel) f;
|
||||
final String description;
|
||||
|
||||
_MatchWithAngel(this.f, this.description);
|
||||
|
||||
@override
|
||||
Description describe(Description description) => this.description == null
|
||||
? description
|
||||
: description.add(this.description);
|
||||
|
||||
@override
|
||||
Future<bool> matchesAsync(item, Map matchState, Angel app) {
|
||||
return new Future.sync(() => f(item, app)).then((result) {
|
||||
return result.matches(item, matchState);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _PredicateWithAngel extends AngelMatcher {
|
||||
final FutureOr<bool> Function(Object, Angel) predicate;
|
||||
final String description;
|
||||
|
||||
_PredicateWithAngel(this.predicate, this.description);
|
||||
|
||||
@override
|
||||
Description describe(Description description) => this.description == null
|
||||
? description
|
||||
: description.add(this.description);
|
||||
|
||||
@override
|
||||
Future<bool> matchesAsync(item, Map matchState, Angel app) {
|
||||
return new Future<bool>.sync(() => predicate(item, app));
|
||||
}
|
||||
}
|
||||
|
||||
class _MatchAsync extends AngelMatcher {
|
||||
final FutureOr<Matcher> Function(Object) matcher;
|
||||
final FutureOr Function() feature;
|
||||
final String description;
|
||||
|
||||
_MatchAsync(this.matcher, this.feature, this.description);
|
||||
|
||||
@override
|
||||
Description describe(Description description) => this.description == null
|
||||
? description
|
||||
: description.add(this.description);
|
||||
|
||||
@override
|
||||
Future<bool> matchesAsync(item, Map matchState, Angel app) async {
|
||||
var f = await feature();
|
||||
var m = await matcher(f);
|
||||
return m.matches(item, matchState);
|
||||
}
|
||||
}
|
|
@ -96,8 +96,7 @@ class Validator extends Matcher {
|
|||
} else if (rule is Filter) {
|
||||
addRule(fieldName, predicate(rule));
|
||||
} else {
|
||||
throw new ArgumentError(
|
||||
'Cannot use a(n) ${rule.runtimeType} as a validation rule.');
|
||||
addRule(fieldName, wrapMatcher(rule));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue