intrepid-core 0.1.6

Manage complex async business logic with ease
Documentation
use std::marker::PhantomData;

use axum::{
    async_trait,
    body::{Body, Bytes},
    extract::FromRequest,
    response::IntoResponse,
};
use futures::future::BoxFuture;
use http::{Request, Response, StatusCode};
use tower::Service;

use crate::{Error, Frame, FrameFuture, Handler, StatefulSystem};

/// An axum request being turned into an intrepid frame.
pub struct FrameRequest(Bytes);

impl From<FrameRequest> for Frame {
    fn from(frame: FrameRequest) -> Self {
        Frame::new(frame.0)
    }
}

#[async_trait]
impl<State> FromRequest<State> for FrameRequest
where
    Bytes: FromRequest<State>,
    State: Send + Sync,
{
    type Rejection = Response<Body>;

    async fn from_request(req: Request<Body>, state: &State) -> Result<Self, Self::Rejection> {
        let body = Bytes::from_request(req, state)
            .await
            .map_err(IntoResponse::into_response)?;

        Ok(Self(body))
    }
}

/// An intrepid frame being turned into an axum response.
pub struct FrameResponse(Frame);

impl From<Frame> for FrameResponse {
    fn from(frame: Frame) -> Self {
        Self(frame)
    }
}

impl IntoResponse for FrameResponse {
    fn into_response(self) -> Response<Body> {
        let body = self.0.into_bytes();
        (StatusCode::OK, body).into_response()
    }
}

/// An intrepid error being turned into an axum response.
#[derive(Debug)]
pub struct FrameResponseError(Error);

impl From<Error> for FrameResponseError {
    fn from(error: Error) -> Self {
        Self(error)
    }
}

impl IntoResponse for FrameResponseError {
    fn into_response(self) -> Response<Body> {
        (StatusCode::INTERNAL_SERVER_ERROR, self.0.to_string()).into_response()
    }
}

/// A struct that lets us bridge intrepid handlers to axum handlers.
#[derive(Clone)]
pub struct RequestHandler<GivenHandler, Args, State>
where
    GivenHandler: crate::Handler<Args, State> + Clone + Send + Sync + 'static,
    State: Clone + Send + Sync + 'static,
    Args: Clone + Send + Sync + 'static,
{
    handler: GivenHandler,
    _state: PhantomData<State>,
    _args: PhantomData<Args>,
}

impl<GivenHandler, Args, State> RequestHandler<GivenHandler, Args, State>
where
    GivenHandler: crate::Handler<Args, State> + Clone + Send + Sync + 'static,
    State: Clone + Send + Sync + 'static,
    Args: Clone + Send + Sync + 'static,
{
    /// Create a new request handler.
    pub fn new(handler: GivenHandler) -> Self {
        Self {
            handler,
            _state: PhantomData,
            _args: PhantomData,
        }
    }
}

impl<GivenHandler, Args, State> Handler<Args, State> for RequestHandler<GivenHandler, Args, State>
where
    GivenHandler: Handler<Args, State> + Clone + Send + Sync + 'static,
    State: Clone + Send + Sync + 'static,
    Args: Clone + Send + Sync + 'static,
{
    type Future = FrameFuture;

    fn invoke(&self, frame: impl Into<Frame>, state: State) -> Self::Future {
        let handler = self.handler.clone();
        let frame = frame.into();

        FrameFuture::from_async_block(async move { handler.invoke(frame, state).await })
    }
}

impl<GivenHandler, Args, State> axum::handler::Handler<Args, State>
    for RequestHandler<GivenHandler, Args, State>
where
    GivenHandler: Handler<Args, State> + Clone + Send + Sync + 'static,
    State: Clone + Send + Sync + 'static,
    Args: Clone + Send + Sync + 'static,
{
    type Future = std::pin::Pin<
        Box<dyn std::future::Future<Output = http::Response<axum::body::Body>> + Send>,
    >;

    fn call(self, request: http::Request<axum::body::Body>, state: State) -> Self::Future {
        use axum::extract::FromRequest;
        use axum::response::IntoResponse;

        let handler = self.handler.clone();

        Box::pin(async move {
            match crate::FrameRequest::from_request(request, &state).await {
                Ok(frame_request) => handler
                    .invoke(frame_request, state)
                    .await
                    .map(crate::FrameResponse::from)
                    .map_err(crate::FrameResponseError::from)
                    .into_response(),
                Err(rejection) => rejection.into_response(),
            }
        })
    }
}

// impl<GivenBody> Service<Request<GivenBody>> for StatelessSystem
// where
//     GivenBody: HttpBody<Data = Bytes> + Send + 'static,
//     GivenBody::Error: Into<BoxError>,
// {
//     type Response = Response<Body>;
//     type Error = Infallible;
//     type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>

//     fn poll_ready(
//         &mut self,
//         _: &mut std::task::Context<'_>,
//     ) -> std::task::Poll<Result<(), Self::Error>> {
//         std::task::Poll::Ready(Ok(()))
//     }

//     fn call(&mut self, request: Request<GivenBody>) -> Self::Future {
//         let req = request.map(Body::new);
//         Box::pin(async { self.handle_http(req, ()) })
//     }
// }

impl<State> Service<FrameRequest> for StatefulSystem<State>
where
    State: Clone + Send + Sync + 'static,
{
    type Response = FrameResponse;
    type Error = FrameResponseError;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(
        &mut self,
        _: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        std::task::Poll::Ready(Ok(()))
    }

    fn call(&mut self, request: FrameRequest) -> Self::Future {
        let instance = self.clone();

        Box::pin(async move {
            instance
                .handle_frame(request.into())
                .await
                .map(Into::into)
                .map_err(Into::into)
        })
    }
}

// fn wat() -> BoxCloneService<Request<Body>, Response<Body>, Infallible> {
//     use std::{iter::once, sync::Arc};
//     use tower::ServiceBuilder;
//     use tower_http::{
//         add_extension::AddExtensionLayer, compression::CompressionLayer,
//         propagate_header::PropagateHeaderLayer, sensitive_headers::SetSensitiveRequestHeadersLayer,
//         trace::TraceLayer, validate_request::ValidateRequestHeaderLayer,
//     };
//     let service = ServiceBuilder::new()
//         .boxed_clone()
//         .layer(SetSensitiveRequestHeadersLayer::new(once(AUTHORIZATION)))
//         .layer(TraceLayer::new_for_http())
//         .layer(AddExtensionLayer::new(Arc::new(())))
//         .layer(CompressionLayer::new())
//         .layer(PropagateHeaderLayer::new(HeaderName::from_static(
//             "x-request-id",
//         )))
//         .layer(ValidateRequestHeaderLayer::bearer("passwordlol"))
//         .layer(ValidateRequestHeaderLayer::accept("application/json"))
//         .service_fn(|_| async { Ok("hay gusy  lol".to_string().into_response()) });

//     service
// }

// mod wut {
//     use std::{convert::Infallible, iter::once, sync::Arc};

//     use axum::{
//         body::Body,
//         extract::Request,
//         response::{IntoResponse, Response},
//     };
//     use http::{
//         header::{AUTHORIZATION, CONTENT_TYPE},
//         HeaderName,
//     };
//     use tower::{util::BoxService, ServiceBuilder};
//     use tower_http::{
//         add_extension::AddExtensionLayer, compression::CompressionLayer,
//         propagate_header::PropagateHeaderLayer, sensitive_headers::SetSensitiveRequestHeadersLayer,
//         set_header::SetResponseHeaderLayer, trace::TraceLayer,
//         validate_request::ValidateRequestHeaderLayer,
//     };

//     fn wat() -> BoxService<Request, Response, Infallible> {
//         let service = ServiceBuilder::new()
//             .boxed()
//             // Mark the `Authorization` request header as sensitive so it doesn't show in logs
//             .layer(SetSensitiveRequestHeadersLayer::new(once(AUTHORIZATION)))
//             // High level logging of requests and responses
//             .layer(TraceLayer::new_for_http())
//             // Share an `Arc<State>` with all requests
//             .layer(AddExtensionLayer::new(Arc::new(())))
//             // Compress responses
//             .layer(CompressionLayer::new())
//             // Propagate `X-Request-Id`s from requests to responses
//             .layer(PropagateHeaderLayer::new(HeaderName::from_static(
//                 "x-request-id",
//             )))
//             // If the response has a known size set the `Content-Length` header
//             // Authorize requests using a token
//             .layer(ValidateRequestHeaderLayer::bearer("passwordlol"))
//             // Accept only application/json, application/* and */* in a request's ACCEPT header
//             .layer(ValidateRequestHeaderLayer::accept("application/json"))
//             // Wrap a `Service` in our middleware stack
//             .service_fn(|_| async { Ok("hay gusy  lol".to_string().into_response()) });

//         service
//     }
// }