use std::borrow::Borrow;
use std::fmt;
use std::marker::PhantomData;
#[doc(inline)]
pub use crate::all_of;
#[doc(inline)]
pub use crate::any_of;
pub mod request;
pub struct ExecutionContext {
stack_depth: usize,
}
impl ExecutionContext {
pub fn evaluate<M, I>(matcher: &mut M, input: &I) -> bool
where
M: Matcher<I> + ?Sized,
I: fmt::Debug + ?Sized,
{
let mut ctx = ExecutionContext { stack_depth: 0 };
log::debug!(
"Matching {:?} with input: {:?}",
matcher_name(matcher),
input
);
let x = matcher.matches(input, &mut ctx);
log::debug!(
"┗━ {}",
if x {
"✅ matches"
} else {
"❌ does not match"
}
);
x
}
pub fn chain<M, I>(&mut self, matcher: &mut M, input: &I) -> bool
where
M: Matcher<I> + ?Sized,
I: fmt::Debug + ?Sized,
{
self.stack_depth += 1;
log::debug!(
"{}Matching {:?} with input: {:?}",
VerticalLines {
num_lines: self.stack_depth
},
matcher_name(matcher),
input
);
let x = matcher.matches(input, self);
log::debug!(
"{}┗━ {}",
VerticalLines {
num_lines: self.stack_depth
},
x
);
self.stack_depth -= 1;
x
}
}
struct VerticalLines {
num_lines: usize,
}
impl fmt::Display for VerticalLines {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for _ in 0..self.num_lines {
write!(f, "┃ ")?;
}
Ok(())
}
}
pub trait Matcher<IN>: Send
where
IN: ?Sized,
{
fn matches(&mut self, input: &IN, ctx: &mut ExecutionContext) -> bool;
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
}
pub(crate) fn matcher_name<M, IN>(mapper: &M) -> MatcherName<'_, M, IN>
where
M: ?Sized,
IN: ?Sized,
{
MatcherName(mapper, PhantomData)
}
pub(crate) struct MatcherName<'a, M, IN>(&'a M, PhantomData<&'a IN>)
where
M: ?Sized,
IN: ?Sized;
impl<'a, M, IN> fmt::Debug for MatcherName<'a, M, IN>
where
M: Matcher<IN> + ?Sized,
IN: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
pub fn any() -> Any {
Any
}
#[derive(Debug)]
pub struct Any;
impl<IN> Matcher<IN> for Any
where
IN: ?Sized,
{
fn matches(&mut self, _input: &IN, _ctx: &mut ExecutionContext) -> bool {
true
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
pub fn eq<T>(value: T) -> Eq<T> {
Eq(value)
}
pub struct Eq<T>(T)
where
T: ?Sized;
impl<IN, T> Matcher<IN> for Eq<T>
where
T: Borrow<IN> + fmt::Debug + Send + ?Sized,
IN: PartialEq + ?Sized,
{
fn matches(&mut self, input: &IN, _ctx: &mut ExecutionContext) -> bool {
self.0.borrow() == input
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<T> fmt::Debug for Eq<T>
where
T: fmt::Debug + ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Eq({:?})", &self.0)
}
}
impl<IN> Matcher<IN> for &str
where
IN: AsRef<[u8]> + ?Sized,
{
fn matches(&mut self, input: &IN, _ctx: &mut ExecutionContext) -> bool {
self.as_bytes() == input.as_ref()
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<IN> Matcher<IN> for String
where
IN: AsRef<[u8]> + ?Sized,
{
fn matches(&mut self, input: &IN, _ctx: &mut ExecutionContext) -> bool {
self.as_bytes() == input.as_ref()
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<IN> Matcher<IN> for &[u8]
where
IN: AsRef<[u8]> + ?Sized,
{
fn matches(&mut self, input: &IN, _ctx: &mut ExecutionContext) -> bool {
*self == input.as_ref()
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<IN> Matcher<IN> for Vec<u8>
where
IN: AsRef<[u8]> + ?Sized,
{
fn matches(&mut self, input: &IN, _ctx: &mut ExecutionContext) -> bool {
self.as_slice() == input.as_ref()
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
pub trait IntoRegex {
fn into_regex(self) -> regex::bytes::Regex;
}
impl IntoRegex for &str {
fn into_regex(self) -> regex::bytes::Regex {
regex::bytes::Regex::new(self).expect("failed to create regex")
}
}
impl IntoRegex for String {
fn into_regex(self) -> regex::bytes::Regex {
regex::bytes::Regex::new(&self).expect("failed to create regex")
}
}
impl IntoRegex for &mut regex::bytes::RegexBuilder {
fn into_regex(self) -> regex::bytes::Regex {
self.build().expect("failed to create regex")
}
}
impl IntoRegex for regex::bytes::Regex {
fn into_regex(self) -> regex::bytes::Regex {
self
}
}
pub fn matches(value: impl IntoRegex) -> Matches {
Matches(value.into_regex())
}
#[derive(Debug)]
pub struct Matches(regex::bytes::Regex);
impl<IN> Matcher<IN> for Matches
where
IN: AsRef<[u8]> + ?Sized,
{
fn matches(&mut self, input: &IN, _ctx: &mut ExecutionContext) -> bool {
self.0.is_match(input.as_ref())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
pub fn not<M>(inner: M) -> Not<M> {
Not(inner)
}
pub struct Not<M>(M);
impl<M, IN> Matcher<IN> for Not<M>
where
M: Matcher<IN>,
IN: fmt::Debug + ?Sized,
{
fn matches(&mut self, input: &IN, ctx: &mut ExecutionContext) -> bool {
!ctx.chain(&mut self.0, input)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Not").field(&matcher_name(&self.0)).finish()
}
}
pub fn all_of<IN>(inner: Vec<Box<dyn Matcher<IN>>>) -> AllOf<IN>
where
IN: ?Sized,
{
AllOf(inner)
}
pub struct AllOf<IN>(Vec<Box<dyn Matcher<IN>>>)
where
IN: ?Sized;
impl<IN> Matcher<IN> for AllOf<IN>
where
IN: fmt::Debug + ?Sized,
{
fn matches(&mut self, input: &IN, ctx: &mut ExecutionContext) -> bool {
self.0
.iter_mut()
.all(|mapper| ctx.chain(mapper.as_mut(), input))
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<IN> fmt::Debug for AllOf<IN>
where
IN: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("AllOf")?;
f.debug_list()
.entries(self.0.iter().map(|x| matcher_name(&**x)))
.finish()
}
}
pub fn any_of<IN>(inner: Vec<Box<dyn Matcher<IN>>>) -> AnyOf<IN>
where
IN: ?Sized,
{
AnyOf(inner)
}
pub struct AnyOf<IN>(Vec<Box<dyn Matcher<IN>>>)
where
IN: ?Sized;
impl<IN> Matcher<IN> for AnyOf<IN>
where
IN: fmt::Debug + ?Sized,
{
fn matches(&mut self, input: &IN, ctx: &mut ExecutionContext) -> bool {
self.0
.iter_mut()
.any(|mapper| ctx.chain(mapper.as_mut(), input))
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<IN> fmt::Debug for AnyOf<IN>
where
IN: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("AnyOf")?;
f.debug_list()
.entries(self.0.iter().map(|x| matcher_name(&**x)))
.finish()
}
}
#[derive(Debug, PartialEq, PartialOrd)]
pub struct KV<K, V>
where
Self: Sized,
K: ToOwned + ?Sized,
V: ToOwned + ?Sized,
{
pub k: K::Owned,
pub v: V::Owned,
}
impl<K, V> KV<K, V>
where
K: ToOwned + ?Sized,
V: ToOwned + ?Sized,
{
pub fn new(k: &K, v: &V) -> Self {
KV {
k: k.to_owned(),
v: v.to_owned(),
}
}
}
pub fn url_decoded<M>(inner: M) -> UrlDecoded<M> {
UrlDecoded(inner)
}
#[derive(Debug)]
pub struct UrlDecoded<M>(M);
impl<IN, M> Matcher<IN> for UrlDecoded<M>
where
IN: AsRef<[u8]> + ?Sized,
M: Matcher<[KV<str, str>]>,
{
fn matches(&mut self, input: &IN, ctx: &mut ExecutionContext) -> bool {
let decoded: Vec<KV<str, str>> = form_urlencoded::parse(input.as_ref())
.into_owned()
.map(|(k, v)| KV { k, v })
.collect();
ctx.chain(&mut self.0, &decoded)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("UrlDecoded")
.field(&matcher_name(&self.0))
.finish()
}
}
pub fn json_decoded<T, M>(inner: M) -> JsonDecoded<T, M>
where
M: Matcher<T>,
{
JsonDecoded(PhantomData, inner)
}
#[derive(Debug)]
pub struct JsonDecoded<T, M>(PhantomData<T>, M);
impl<IN, T, M> Matcher<IN> for JsonDecoded<T, M>
where
IN: AsRef<[u8]> + ?Sized,
M: Matcher<T>,
T: serde::de::DeserializeOwned + fmt::Debug + Send,
{
fn matches(&mut self, input: &IN, ctx: &mut ExecutionContext) -> bool {
let value: T = match serde_json::from_slice(input.as_ref()) {
Ok(value) => value,
Err(_) => return false,
};
ctx.chain(&mut self.1, &value)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("JsonDecoded")
.field(&matcher_name(&self.1))
.finish()
}
}
pub fn lowercase<M>(inner: M) -> Lowercase<M> {
Lowercase(inner)
}
#[derive(Debug)]
pub struct Lowercase<M>(M);
impl<IN, M> Matcher<IN> for Lowercase<M>
where
IN: AsRef<[u8]> + ?Sized,
M: Matcher<[u8]>,
{
fn matches(&mut self, input: &IN, ctx: &mut ExecutionContext) -> bool {
use bstr::ByteSlice;
ctx.chain(&mut self.0, &input.as_ref().to_lowercase())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Lowercase")
.field(&matcher_name(&self.0))
.finish()
}
}
impl<IN, F> Matcher<IN> for F
where
F: Fn(&IN) -> bool + Send,
{
fn matches(&mut self, input: &IN, _ctx: &mut ExecutionContext) -> bool {
self(input)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "fn(&{}) -> bool", std::any::type_name::<IN>())
}
}
pub fn contains<M>(inner: M) -> Contains<M> {
Contains(inner)
}
#[derive(Debug)]
pub struct Contains<M>(M);
impl<M, E> Matcher<[E]> for Contains<M>
where
M: Matcher<E>,
E: fmt::Debug,
{
fn matches(&mut self, input: &[E], ctx: &mut ExecutionContext) -> bool {
input.iter().any(|x| ctx.chain(&mut self.0, x))
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Contains")
.field(&matcher_name(&self.0))
.finish()
}
}
pub fn key<M>(inner: M) -> Key<M> {
Key(inner)
}
#[derive(Debug)]
pub struct Key<M>(M);
impl<M, K, V> Matcher<KV<K, V>> for Key<M>
where
K: ToOwned + fmt::Debug + ?Sized,
V: ToOwned + ?Sized,
M: Matcher<K>,
{
fn matches(&mut self, input: &KV<K, V>, ctx: &mut ExecutionContext) -> bool {
ctx.chain(&mut self.0, input.k.borrow())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Key").field(&matcher_name(&self.0)).finish()
}
}
pub fn value<M>(inner: M) -> Value<M> {
Value(inner)
}
#[derive(Debug)]
pub struct Value<M>(M);
impl<M, K, V> Matcher<KV<K, V>> for Value<M>
where
K: ToOwned + ?Sized,
V: ToOwned + fmt::Debug + ?Sized,
M: Matcher<V>,
{
fn matches(&mut self, input: &KV<K, V>, ctx: &mut ExecutionContext) -> bool {
ctx.chain(&mut self.0, input.v.borrow())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Value")
.field(&matcher_name(&self.0))
.finish()
}
}
impl<K, V, KMatcher, VMatcher> Matcher<KV<K, V>> for (KMatcher, VMatcher)
where
K: ToOwned + fmt::Debug + ?Sized,
V: ToOwned + fmt::Debug + ?Sized,
KMatcher: Matcher<K>,
VMatcher: Matcher<V>,
{
fn matches(&mut self, input: &KV<K, V>, ctx: &mut ExecutionContext) -> bool {
ctx.chain(&mut self.0, input.k.borrow()) && ctx.chain(&mut self.1, input.v.borrow())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("")
.field(&matcher_name(&self.0))
.field(&matcher_name(&self.1))
.finish()
}
}
pub fn len<M>(inner: M) -> Len<M> {
Len(inner)
}
#[derive(Debug)]
pub struct Len<M>(M);
impl<M, T> Matcher<[T]> for Len<M>
where
M: Matcher<usize>,
{
fn matches(&mut self, input: &[T], ctx: &mut ExecutionContext) -> bool {
ctx.chain(&mut self.0, &input.len())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Len").field(&matcher_name(&self.0)).finish()
}
}
impl<M> Matcher<str> for Len<M>
where
M: Matcher<usize>,
{
fn matches(&mut self, input: &str, ctx: &mut ExecutionContext) -> bool {
ctx.chain(&mut self.0, &input.len())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Len").field(&matcher_name(&self.0)).finish()
}
}
impl<M> Matcher<bstr::BStr> for Len<M>
where
M: Matcher<usize>,
{
fn matches(&mut self, input: &bstr::BStr, ctx: &mut ExecutionContext) -> bool {
ctx.chain(&mut self.0, &input.len())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Len").field(&matcher_name(&self.0)).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn eval<M, I>(matcher: &mut M, input: &I) -> bool
where
M: Matcher<I> + ?Sized,
I: fmt::Debug + ?Sized,
{
ExecutionContext::evaluate(matcher, input)
}
#[test]
fn test_eq() {
let mut c = eq("foo");
assert_eq!(false, eval(&mut c, "foobar"));
assert_eq!(false, eval(&mut c, "bazfoobar"));
assert_eq!(false, eval(&mut c, "bar"));
assert_eq!(true, eval(&mut c, "foo"));
}
#[test]
fn test_matches() {
let mut c = matches(r#"^foo\d*bar$"#);
assert_eq!(true, eval(&mut c, "foobar"));
assert_eq!(true, eval(&mut c, "foo99bar"));
assert_eq!(false, eval(&mut c, "foo99barz"));
assert_eq!(false, eval(&mut c, "bat"));
let mut c = matches(r#"^foo\d*bar$"#.to_owned());
assert_eq!(true, eval(&mut c, "foobar"));
assert_eq!(true, eval(&mut c, "foo99bar"));
assert_eq!(false, eval(&mut c, "foo99barz"));
assert_eq!(false, eval(&mut c, "bat"));
let mut c = matches(regex::bytes::RegexBuilder::new("foobar").case_insensitive(true));
assert_eq!(true, eval(&mut c, "foobar"));
assert_eq!(true, eval(&mut c, "FOOBAR"));
assert_eq!(false, eval(&mut c, "FOO99BAR"));
let mut c = matches(
regex::bytes::RegexBuilder::new("foobar")
.case_insensitive(true)
.build()
.unwrap(),
);
assert_eq!(true, eval(&mut c, "foobar"));
assert_eq!(true, eval(&mut c, "FOOBAR"));
assert_eq!(false, eval(&mut c, "FOO99BAR"));
}
#[test]
fn test_not() {
let mut c = not(matches(r#"^foo\d*bar$"#));
assert_eq!(false, eval(&mut c, "foobar"));
assert_eq!(false, eval(&mut c, "foo99bar"));
assert_eq!(true, eval(&mut c, "foo99barz"));
assert_eq!(true, eval(&mut c, "bat"));
}
#[test]
fn test_all_of() {
let mut c = all_of![matches("foo"), matches("bar")];
assert_eq!(true, eval(&mut c, "foobar"));
assert_eq!(true, eval(&mut c, "barfoo"));
assert_eq!(false, eval(&mut c, "foo"));
assert_eq!(false, eval(&mut c, "bar"));
}
#[test]
fn test_any_of() {
let mut c = any_of![matches("foo"), matches("bar")];
assert_eq!(true, eval(&mut c, "foobar"));
assert_eq!(true, eval(&mut c, "barfoo"));
assert_eq!(true, eval(&mut c, "foo"));
assert_eq!(true, eval(&mut c, "bar"));
assert_eq!(false, eval(&mut c, "baz"));
}
#[test]
fn test_url_decoded() {
let expected = vec![KV::new("key 1", "value 1"), KV::new("key2", "")];
let mut c = request::query(url_decoded(eq(expected)));
let req = http::Request::get("https://example.com/path?key%201=value%201&key2")
.body("")
.unwrap();
assert_eq!(true, eval(&mut c, &req));
}
#[test]
fn test_json_decoded() {
let mut c = json_decoded(eq(serde_json::json!({
"foo": 1,
"bar": 99,
})));
assert_eq!(true, eval(&mut c, r#"{"foo": 1, "bar": 99}"#));
assert_eq!(true, eval(&mut c, r#"{"bar": 99, "foo": 1}"#));
assert_eq!(false, eval(&mut c, r#"{"foo": 1, "bar": 100}"#));
}
#[test]
fn test_lowercase() {
let mut c = lowercase(matches("foo"));
assert_eq!(true, eval(&mut c, "FOO"));
assert_eq!(true, eval(&mut c, "FoOBar"));
assert_eq!(true, eval(&mut c, "foobar"));
assert_eq!(false, eval(&mut c, "bar"));
}
#[test]
fn test_fn_mapper() {
let mut c = |input: &u64| input % 2 == 0;
assert_eq!(true, eval(&mut c, &6));
assert_eq!(true, eval(&mut c, &20));
assert_eq!(true, eval(&mut c, &0));
assert_eq!(false, eval(&mut c, &11));
}
#[test]
fn test_contains() {
let mut c = contains(eq(100));
assert_eq!(true, eval(&mut c, vec![100, 200, 300].as_slice()));
assert_eq!(false, eval(&mut c, vec![99, 200, 300].as_slice()));
}
#[test]
fn test_key() {
let kv = KV::new("key1", "value1");
assert_eq!(true, eval(&mut key("key1"), &kv));
assert_eq!(false, eval(&mut key("key2"), &kv));
}
#[test]
fn test_value() {
let kv = KV::new("key1", "value1");
assert_eq!(true, eval(&mut value("value1"), &kv));
assert_eq!(false, eval(&mut value("value2"), &kv));
}
#[test]
fn test_tuple() {
let kv = KV::new("key1", "value1");
assert_eq!(true, eval(&mut ("key1", any()), &kv));
assert_eq!(true, eval(&mut ("key1", "value1"), &kv));
assert_eq!(false, eval(&mut ("key1", "value2"), &kv));
assert_eq!(false, eval(&mut ("key2", "value1"), &kv));
}
#[test]
fn test_len() {
let mut c = len(eq(3));
assert_eq!(true, eval(&mut c, "foo"));
assert_eq!(false, eval(&mut c, "foobar"));
assert_eq!(true, eval(&mut c, &b"foo"[..]));
assert_eq!(false, eval(&mut c, &b"foobar"[..]));
let req = http::Request::get("/test?foo=bar").body("foobar").unwrap();
assert!(eval(&mut request::body(len(eq(6))), &req));
}
#[test]
fn test_fn() {
let mut c = len(|&len: &usize| len <= 3);
assert_eq!(true, eval(&mut c, "f"));
assert_eq!(true, eval(&mut c, "fo"));
assert_eq!(true, eval(&mut c, "foo"));
assert_eq!(false, eval(&mut c, "foob"));
assert_eq!(false, eval(&mut c, "fooba"));
assert_eq!(false, eval(&mut c, "foobar"));
}
}