ugi 0.2.1

Runtime-agnostic Rust request client with HTTP/1.1, HTTP/2, HTTP/3, H2C, WebSocket, SSE, and gRPC support
Documentation
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use async_trait::async_trait;

use crate::client::{RedirectPolicy, Transport};
use crate::error::Result;
use crate::request::Request;
use crate::response::Response;

/// Middleware hook that intercepts outbound requests and/or inbound responses.
///
/// Implement this trait to add cross-cutting behaviour such as authentication,
/// logging, or caching.  Each middleware receives the request and a [`Next`]
/// handle; call `next.run(req).await` to forward to the next layer.
///
/// # Example
/// ```no_run
/// use ugi::{Middleware, Next, Request, Response, Result};
///
/// struct AddHeader;
///
/// #[async_trait::async_trait]
/// impl Middleware for AddHeader {
///     async fn handle(&self, mut req: Request, next: Next<'_>) -> Result<Response> {
///         req.headers_mut().insert("x-custom", "value")?;
///         next.run(req).await
///     }
/// }
/// ```
#[async_trait]
pub trait Middleware: Send + Sync {
    async fn handle(&self, req: Request, next: Next<'_>) -> Result<Response>;
}

/// A handle to the remaining middleware chain and underlying transport.
///
/// Call [`run`](Self::run) to forward the request to the next middleware or,
/// when there are no more middlewares, to the transport.
pub struct Next<'a> {
    middlewares: &'a [Arc<dyn Middleware>],
    transport: Arc<dyn Transport>,
    redirect_policy: RedirectPolicy,
}

impl<'a> Next<'a> {
    pub(crate) fn new(
        middlewares: &'a [Arc<dyn Middleware>],
        transport: Arc<dyn Transport>,
        redirect_policy: RedirectPolicy,
    ) -> Self {
        Self {
            middlewares,
            transport,
            redirect_policy,
        }
    }

    pub fn run(self, req: Request) -> Pin<Box<dyn Future<Output = Result<Response>> + Send + 'a>> {
        Box::pin(async move {
            if let Some((current, rest)) = self.middlewares.split_first() {
                current
                    .handle(
                        req,
                        Next {
                            middlewares: rest,
                            transport: Arc::clone(&self.transport),
                            redirect_policy: self.redirect_policy,
                        },
                    )
                    .await
            } else {
                self.transport
                    .execute_with_redirect(req, self.redirect_policy)
                    .await
            }
        })
    }
}