embystream 0.0.36

Another Emby streaming application (frontend/backend separation) written in Rust.
Documentation
use std::{future::Future, pin::Pin, sync::Arc};

use async_trait::async_trait;
use hyper::{
    Request, Response,
    body::{self, Incoming},
};

use crate::gateway::{
    context::Context, request_id::generate_request_id, response::BoxBodyType,
};

pub type Handler = Arc<
    dyn Fn(Context, Option<Incoming>) -> Response<BoxBodyType> + Send + Sync,
>;

type ResponseFuture =
    Pin<Box<dyn Future<Output = Response<BoxBodyType>> + Send>>;

pub type Next =
    Box<dyn FnOnce(Context, Option<Incoming>) -> ResponseFuture + Send>;

#[async_trait]
pub trait Middleware: Send + Sync {
    async fn handle(
        &self,
        ctx: Context,
        body: Option<Incoming>,
        next: Next,
    ) -> Response<BoxBodyType>;
    fn clone_box(&self) -> Box<dyn Middleware>;
}

impl Clone for Box<dyn Middleware> {
    fn clone(&self) -> Self {
        self.clone_box()
    }
}

pub struct Chain {
    middlewares: Vec<Box<dyn Middleware>>,
    handler: Handler,
}

impl Chain {
    pub fn new(
        middlewares: Vec<Box<dyn Middleware>>,
        handler: Handler,
    ) -> Self {
        Self {
            middlewares,
            handler,
        }
    }

    pub fn add_middleware(mut self, middleware: Box<dyn Middleware>) -> Self {
        self.middlewares.push(middleware);
        self
    }

    pub async fn run(
        self,
        req: Request<body::Incoming>,
    ) -> Response<BoxBodyType> {
        let (parts, body) = req.into_parts();
        let request_id = generate_request_id();
        let ctx = Context::new(
            parts.uri,
            parts.method,
            parts.headers,
            std::time::Instant::now(),
            request_id,
        );

        let handler_action: Next = Box::new(move |ctx, body| {
            Box::pin(async move { (self.handler)(ctx, body) })
        });

        let chain_entry = self.middlewares.into_iter().rfold(
            handler_action,
            |next_action, middleware| {
                Box::new(move |ctx, body| {
                    Box::pin(async move {
                        middleware.handle(ctx, body, next_action).await
                    })
                })
            },
        );

        chain_entry(ctx, Some(body)).await
    }
}