use std::borrow::Borrow; use std::fmt; use std::marker::PhantomData; // import the any_of and all_of macros from crate root so they are accessible if // people glob import this module. pub use crate::all_of; pub use crate::any_of; pub mod request; pub mod response; pub trait Mapper: Send + fmt::Debug where IN: ?Sized, { type Out; fn map(&mut self, input: &IN) -> Self::Out; } // Matcher is just a special case of Mapper that returns a boolean. Simply // provides the `matches` method rather than `map` as that reads a little // better. pub trait Matcher: Send + fmt::Debug where IN: ?Sized, { fn matches(&mut self, input: &IN) -> bool; } impl Matcher for T where T: Mapper, { fn matches(&mut self, input: &IN) -> bool { self.map(input) } } pub fn any() -> impl Mapper { Any } #[derive(Debug)] pub struct Any; impl Mapper for Any { type Out = bool; fn map(&mut self, _input: &IN) -> bool { true } } pub fn contains(value: T) -> impl Mapper where T: AsRef<[u8]> + fmt::Debug + Send, IN: AsRef<[u8]> + ?Sized, { Contains(value) } #[derive(Debug)] pub struct Contains(T); impl Mapper for Contains where T: AsRef<[u8]> + fmt::Debug + Send, IN: AsRef<[u8]> + ?Sized, { type Out = bool; fn map(&mut self, input: &IN) -> bool { use bstr::ByteSlice; input.as_ref().contains_str(self.0.as_ref()) } } pub fn eq(value: T) -> impl Mapper where T: Borrow + fmt::Debug + Send, IN: PartialEq + ?Sized, { Eq(value) } #[derive(Debug)] pub struct Eq(T); impl Mapper for Eq where T: Borrow + fmt::Debug + Send, IN: PartialEq + ?Sized, { type Out = bool; fn map(&mut self, input: &IN) -> bool { self.0.borrow() == input } } pub fn matches(value: &str) -> impl Mapper where IN: AsRef<[u8]> + ?Sized, { let regex = regex::bytes::Regex::new(value).expect("failed to create regex"); Matches(regex) } #[derive(Debug)] pub struct Matches(regex::bytes::Regex); impl Mapper for Matches where IN: AsRef<[u8]> + ?Sized, { type Out = bool; fn map(&mut self, input: &IN) -> bool { self.0.is_match(input.as_ref()) } } pub fn not(inner: C) -> impl Mapper where C: Mapper, IN: ?Sized, { Not(inner, PhantomData) } pub struct Not(C, PhantomData) where IN: ?Sized; impl Mapper for Not where C: Mapper, IN: ?Sized, { type Out = bool; fn map(&mut self, input: &IN) -> bool { !self.0.map(input) } } impl fmt::Debug for Not where C: Mapper, IN: ?Sized, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Not({:?})", &self.0) } } pub fn all_of(inner: Vec>>) -> impl Mapper where IN: fmt::Debug + ?Sized, { AllOf(inner) } #[derive(Debug)] pub struct AllOf(Vec>>) where IN: ?Sized; impl Mapper for AllOf where IN: fmt::Debug + ?Sized, { type Out = bool; fn map(&mut self, input: &IN) -> bool { self.0.iter_mut().all(|maper| maper.map(input)) } } pub fn any_of(inner: Vec>>) -> impl Mapper where IN: fmt::Debug + ?Sized, { AnyOf(inner) } #[derive(Debug)] pub struct AnyOf(Vec>>) where IN: ?Sized; impl Mapper for AnyOf where IN: fmt::Debug + ?Sized, { type Out = bool; fn map(&mut self, input: &IN) -> bool { self.0.iter_mut().any(|maper| maper.map(input)) } } pub fn uri_decoded(inner: C) -> impl Mapper where IN: AsRef<[u8]> + ?Sized, C: Mapper<[(String, String)]>, { UriDecoded(inner) } #[derive(Debug)] pub struct UriDecoded(C); impl Mapper for UriDecoded where IN: AsRef<[u8]> + ?Sized, C: Mapper<[(String, String)]>, { type Out = C::Out; fn map(&mut self, input: &IN) -> C::Out { let decoded: Vec<(String, String)> = url::form_urlencoded::parse(input.as_ref()) .into_owned() .collect(); self.0.map(&decoded) } } pub fn json_decoded(inner: C) -> impl Mapper where IN: AsRef<[u8]> + ?Sized, C: Mapper, { JsonDecoded(inner) } #[derive(Debug)] pub struct JsonDecoded(C); impl Mapper for JsonDecoded where IN: AsRef<[u8]> + ?Sized, C: Mapper, { type Out = C::Out; fn map(&mut self, input: &IN) -> C::Out { let json_value: serde_json::Value = serde_json::from_slice(input.as_ref()).unwrap_or(serde_json::Value::Null); self.0.map(&json_value) } } pub fn lowercase(inner: C) -> impl Mapper where IN: AsRef<[u8]> + ?Sized, C: Mapper<[u8]>, { Lowercase(inner) } #[derive(Debug)] pub struct Lowercase(C); impl Mapper for Lowercase where IN: AsRef<[u8]> + ?Sized, C: Mapper<[u8]>, { type Out = C::Out; fn map(&mut self, input: &IN) -> C::Out { use bstr::ByteSlice; self.0.map(&input.as_ref().to_lowercase()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_contains() { let mut c = contains("foo"); assert_eq!(true, c.map("foobar")); assert_eq!(true, c.map("bazfoobar")); assert_eq!(false, c.map("bar")); } #[test] fn test_eq() { let mut c = eq("foo"); assert_eq!(false, c.map("foobar")); assert_eq!(false, c.map("bazfoobar")); assert_eq!(false, c.map("bar")); assert_eq!(true, c.map("foo")); } #[test] fn test_matches() { let mut c = matches(r#"^foo\d*bar$"#); assert_eq!(true, c.map("foobar")); assert_eq!(true, c.map("foo99bar")); assert_eq!(false, c.map("foo99barz")); assert_eq!(false, c.map("bat")); } #[test] fn test_not() { let mut c = not(matches(r#"^foo\d*bar$"#)); assert_eq!(false, c.map("foobar")); assert_eq!(false, c.map("foo99bar")); assert_eq!(true, c.map("foo99barz")); assert_eq!(true, c.map("bat")); } #[test] fn test_all_of() { let mut c = all_of![contains("foo"), contains("bar")]; assert_eq!(true, c.map("foobar")); assert_eq!(true, c.map("barfoo")); assert_eq!(false, c.map("foo")); assert_eq!(false, c.map("bar")); } #[test] fn test_any_of() { let mut c = any_of![contains("foo"), contains("bar")]; assert_eq!(true, c.map("foobar")); assert_eq!(true, c.map("barfoo")); assert_eq!(true, c.map("foo")); assert_eq!(true, c.map("bar")); assert_eq!(false, c.map("baz")); } #[test] fn test_uri_decoded() { let expected = vec![ ("key 1".to_owned(), "value 1".to_owned()), ("key2".to_owned(), "".to_owned()), ]; let mut c = request::query(uri_decoded(eq(expected))); let req = http::Request::get("https://example.com/path?key%201=value%201&key2") .body(Vec::new()) .unwrap(); assert_eq!(true, c.map(&req)); } #[test] fn test_json_decoded() { let mut c = json_decoded(eq(serde_json::json!({ "foo": 1, "bar": 99, }))); assert_eq!(true, c.map(r#"{"foo": 1, "bar": 99}"#)); assert_eq!(true, c.map(r#"{"bar": 99, "foo": 1}"#)); assert_eq!(false, c.map(r#"{"foo": 1, "bar": 100}"#)); } #[test] fn test_lowercase() { let mut c = lowercase(contains("foo")); assert_eq!(true, c.map("FOO")); assert_eq!(true, c.map("FoOBar")); assert_eq!(true, c.map("foobar")); assert_eq!(false, c.map("bar")); } }