#combinator #guard #layer #axum-server #logical #tower-service #self

yanked axum_guard_combinator

Use logical combinator to combine types that implement from request

0.2.0 Aug 27, 2022
0.1.0 Aug 25, 2022

#29 in #axum-server

MIT license

22KB
436 lines

axum_guard_combinator

https://crates.io/crates/axum_guard_combinator

This library lets you write logical (OR, AND) combinators inside Tower Service Layers in Axum servers, which extract the given type T which implement Guard and checks the value of Type T against the input value inside the layer's combinator. Here's a struct which always evaluates to true.

#[derive(Clone,Copy,Debug,PartialEq)]
    pub struct Always;

    impl Guard for Always {
        fn check_guard(&self,_:&Self) -> bool {
            true
        }
    }
    #[async_trait::async_trait]
    impl<B:Send+Sync> FromRequest<B> for Always {
        type Rejection = ();

        async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
            Ok(Self)
        }
    }

Here's a test case combining said struct with a struct that always evaluates to false

 #[tokio::test]
    async fn test_or_happy_path() {
        let app = Router::new()
            .route("/",get(ok
                .layer(GuardLayer::with(Always.or(Never)))));
        let response = app
            .oneshot(
                Request::builder()
                    .uri("/")
                    .body(Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::OK);
    }

You can nest your combinators to arbitrary depths

 #[tokio::test]
    async fn test_happy_nested() {
        let app = Router::new()
            .route("/",get(ok
                .layer(GuardLayer::with(
                        Never.or(
                    Always.and(
                            Always.or(
                                Never)))))));
        let response = app
            .oneshot(
                Request::builder()
                    .uri("/")
                    .body(Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::OK);
    }

You can input data in the layer which will be evaluated in the Guard impl's check_guard method

#[derive(Clone,Debug,PartialEq)]
    pub struct ArbitraryData{
        data:String,
    }
    impl Guard for ArbitraryData{
        fn check_guard(&self, expected: &Self) -> bool {
            *self == *expected
        }
    }
    #[async_trait::async_trait]
    impl<B:Send+Sync> FromRequest<B> for ArbitraryData {
        type Rejection = ();

        async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
            Ok(Self{
                data: req.headers().get("data").unwrap()
                    .to_str().unwrap().to_string()
            })
        }
    }
  #[tokio::test]
    async fn test_or_happy_path_with_data() {
        let app = Router::new()
            .route("/",get(ok
                .layer(GuardLayer::with(ArbitraryData{
                    data:String::from("Hello World.")
                }))));
        let response = app
            .oneshot(
                Request::builder()
                    .uri("/")
                    .header("data","Hello World.")
                    .body(Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::OK);
    }

Dependencies

~6.5–9MB
~163K SLoC