intrepid-core 0.2.0

Manage complex async business logic with ease
Documentation
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(())
}