2018-06-28 15:07:04 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'package:angel_framework/angel_framework.dart';
|
|
|
|
import 'package:angel_http_exception/angel_http_exception.dart';
|
|
|
|
import 'package:matcher/matcher.dart';
|
2018-06-28 16:34:05 +00:00
|
|
|
import 'context_aware.dart';
|
2018-06-28 15:07:04 +00:00
|
|
|
|
|
|
|
/// Returns an [AngelMatcher] that uses an arbitrary function that returns
|
|
|
|
/// true or false for the actual value.
|
|
|
|
///
|
|
|
|
/// Analogous to the synchronous [predicate] matcher.
|
2018-06-28 16:34:05 +00:00
|
|
|
AngelMatcher predicateWithAngel(
|
|
|
|
FutureOr<bool> Function(String, Object, Angel) f,
|
2018-06-28 15:07:04 +00:00
|
|
|
[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.
|
2018-06-28 16:34:05 +00:00
|
|
|
AngelMatcher matchWithAngel(FutureOr<Matcher> Function(Object, Map, Angel) f,
|
2018-06-28 15:07:04 +00:00
|
|
|
[String description = 'satisfies asynchronously created matcher']) =>
|
|
|
|
new _MatchWithAngel(f, description);
|
|
|
|
|
|
|
|
/// Calls [matchWithAngel] without the initial parameter.
|
2018-06-28 16:34:05 +00:00
|
|
|
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.
|
2018-06-28 15:07:04 +00:00
|
|
|
AngelMatcher matchWithAngelUnary(FutureOr<Matcher> Function(Angel) f,
|
|
|
|
[String description = 'satisfies asynchronously created matcher']) =>
|
2018-06-28 16:34:05 +00:00
|
|
|
matchWithAngelBinary((_, app) => f(app));
|
|
|
|
|
|
|
|
/// Calls [matchWithAngel] without any parameters.
|
|
|
|
AngelMatcher matchWithAngelNullary(FutureOr<Matcher> Function() f,
|
|
|
|
[String description = 'satisfies asynchronously created matcher']) =>
|
|
|
|
matchWithAngelUnary((_) => f());
|
2018-06-28 15:07:04 +00:00
|
|
|
|
|
|
|
/// 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;
|
2018-06-28 16:34:05 +00:00
|
|
|
if (x is ContextAwareMatcher) return new _WrappedAngelMatcher(x);
|
|
|
|
return wrapAngelMatcher(wrapContextAwareMatcher(x));
|
2018-06-28 15:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an [AngelMatcher] that asynchronously resolves a [feature], builds a [matcher], and executes it.
|
2018-06-28 16:34:05 +00:00
|
|
|
AngelMatcher matchAsync(FutureOr<Matcher> Function(String, Object) matcher,
|
|
|
|
FutureOr Function() feature,
|
2018-06-28 15:07:04 +00:00
|
|
|
[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(
|
2018-06-28 16:34:05 +00:00
|
|
|
(key, item, app) async {
|
2018-06-28 15:07:04 +00:00
|
|
|
try {
|
2018-11-02 00:55:28 +00:00
|
|
|
var result = await app.findService(servicePath)?.read(item);
|
2018-06-28 15:07:04 +00:00
|
|
|
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.
|
2018-06-28 16:34:05 +00:00
|
|
|
abstract class AngelMatcher extends ContextAwareMatcher {
|
|
|
|
Future<bool> matchesWithAngel(
|
|
|
|
item, String key, Map context, Map matchState, Angel app);
|
2018-06-28 15:07:04 +00:00
|
|
|
|
|
|
|
@override
|
2018-06-28 16:34:05 +00:00
|
|
|
bool matchesWithContext(item, String key, Map context, Map matchState) {
|
2018-06-28 15:07:04 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-28 16:34:05 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-28 15:07:04 +00:00
|
|
|
class _MatchWithAngel extends AngelMatcher {
|
2018-06-28 16:34:05 +00:00
|
|
|
final FutureOr<Matcher> Function(Object, Map, Angel) f;
|
2018-06-28 15:07:04 +00:00
|
|
|
final String description;
|
|
|
|
|
|
|
|
_MatchWithAngel(this.f, this.description);
|
|
|
|
|
|
|
|
@override
|
|
|
|
Description describe(Description description) => this.description == null
|
|
|
|
? description
|
|
|
|
: description.add(this.description);
|
|
|
|
|
|
|
|
@override
|
2018-06-28 16:34:05 +00:00
|
|
|
Future<bool> matchesWithAngel(
|
|
|
|
item, String key, Map context, Map matchState, Angel app) {
|
|
|
|
return new Future.sync(() => f(item, context, app)).then((result) {
|
2018-06-28 15:07:04 +00:00
|
|
|
return result.matches(item, matchState);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PredicateWithAngel extends AngelMatcher {
|
2018-06-28 16:34:05 +00:00
|
|
|
final FutureOr<bool> Function(String, Object, Angel) predicate;
|
2018-06-28 15:07:04 +00:00
|
|
|
final String description;
|
|
|
|
|
|
|
|
_PredicateWithAngel(this.predicate, this.description);
|
|
|
|
|
|
|
|
@override
|
|
|
|
Description describe(Description description) => this.description == null
|
|
|
|
? description
|
|
|
|
: description.add(this.description);
|
|
|
|
|
|
|
|
@override
|
2018-06-28 16:34:05 +00:00
|
|
|
Future<bool> matchesWithAngel(
|
|
|
|
item, String key, Map context, Map matchState, Angel app) {
|
|
|
|
return new Future<bool>.sync(() => predicate(key, item, app));
|
2018-06-28 15:07:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MatchAsync extends AngelMatcher {
|
2018-06-28 16:34:05 +00:00
|
|
|
final FutureOr<Matcher> Function(String, Object) matcher;
|
2018-06-28 15:07:04 +00:00
|
|
|
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
|
2018-06-28 16:34:05 +00:00
|
|
|
Future<bool> matchesWithAngel(
|
|
|
|
item, String key, Map context, Map matchState, Angel app) async {
|
2018-06-28 15:07:04 +00:00
|
|
|
var f = await feature();
|
2018-06-28 16:34:05 +00:00
|
|
|
var m = await matcher(key, f);
|
|
|
|
var c = wrapAngelMatcher(m);
|
|
|
|
return await c.matchesWithAngel(item, key, context, matchState, app);
|
2018-06-28 15:07:04 +00:00
|
|
|
}
|
|
|
|
}
|