use futures::future::BoxFuture;
use tower::{layer::layer_fn, Layer, Service};
use crate::{errors::ParsingError, Frame, MessageFrame, Pattern};
#[derive(Clone)]
pub struct FullUriMatch<S> {
pattern: String,
inner: S,
}
impl<S> FullUriMatch<S> {
pub fn new(pattern: impl AsRef<str>, inner: S) -> Self {
Self {
pattern: pattern.as_ref().into(),
inner,
}
}
pub fn layer(pattern: impl AsRef<str>) -> impl Layer<S, Service = Self> + Clone
where
S: Clone + Send + 'static,
{
let pattern = pattern.as_ref().to_owned();
layer_fn(move |inner: S| Self::new(&pattern, inner))
}
}
impl<S, IntoFrame> Service<IntoFrame> for FullUriMatch<S>
where
S: Service<Frame, Response = Frame, Error = crate::Error> + Clone + Send + 'static,
S::Future: Send,
IntoFrame: Into<Frame>,
{
type Response = Frame;
type Error = crate::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, frame: IntoFrame) -> Self::Future {
let pattern_candidate = self.pattern.clone();
let mut inner_future = self.inner.clone();
let frame = frame.into();
Box::pin(async move {
let nothing = Ok(Frame::default());
if let Frame::Message(MessageFrame { uri, .. }) = &frame {
let pattern: Pattern = uri.parse().map_err(ParsingError)?;
let matches = pattern
.test(pattern_candidate.as_ref())
.map_err(ParsingError)?;
if matches {
inner_future.call(frame).await
} else {
nothing
}
} else {
nothing
}
})
}
}
#[tokio::test]
async fn allows_messages_that_fully_match() -> Result<(), tower::BoxError> {
use crate::Handler;
let string_id = |given_string: String| async { given_string };
let layer = FullUriMatch::layer("match");
let actionable = string_id.as_into_actionable().into_actionable(());
let message = Frame::message("match", "test".to_owned(), ());
let result = layer.layer(actionable).call(message).await?;
assert_eq!(Frame::from(result), "test".to_string().into());
Ok(())
}
#[tokio::test]
async fn prevents_messages_that_dont_match() -> Result<(), tower::BoxError> {
use crate::Handler;
let string_id = |given_string: String| async { given_string };
let layer = FullUriMatch::layer("match");
let actionable = string_id.as_into_actionable().into_actionable(());
let message = Frame::message("no-match", "test".to_owned(), ());
let result = layer.layer(actionable).call(message).await?;
assert_eq!(Frame::from(result), ().into());
Ok(())
}