axum-prometheus 0.10.0

A tower middleware to collect and export HTTP metrics for Axum
Documentation
use futures_core::ready;
use http::response::Response;
use http_body::Body;
use pin_project_lite::pin_project;
use std::{
    future::Future,
    pin::Pin,
    task::{Context, Poll},
};
use tower_http::classify::{ClassifiedResponse, ClassifyResponse};

use super::{body::ResponseBody, Callbacks, FailedAt, OnBodyChunk};

pin_project! {
pub struct ResponseFuture<F, C, Callbacks, OnBodyChunk, CallbackData> {
    #[pin]
    pub(super) inner: F,
    pub(super) classifier: Option<C>,
    pub(super) callbacks: Option<Callbacks>,
    pub(super) on_body_chunk: Option<OnBodyChunk>,
    pub(super) callbacks_data: Option<CallbackData>,
}}

impl<F, C, CallbacksData, ResBody, E, CallbacksT, OnBodyChunkT> Future
    for ResponseFuture<F, C, CallbacksT, OnBodyChunkT, CallbacksData>
where
    F: Future<Output = Result<Response<ResBody>, E>>,
    ResBody: Body,
    C: ClassifyResponse,
    CallbacksT: Callbacks<C::FailureClass, Data = CallbacksData>,
    E: std::fmt::Display + 'static,
    OnBodyChunkT: OnBodyChunk<ResBody::Data, Data = CallbacksData>,
    CallbacksData: Clone,
{
    type Output = Result<
        Response<ResponseBody<ResBody, C::ClassifyEos, CallbacksT, OnBodyChunkT, CallbacksT::Data>>,
        E,
    >;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        let result = ready!(this.inner.poll(cx));

        let classifier = this
            .classifier
            .take()
            .expect("polled future after completion");
        let mut callbacks = this
            .callbacks
            .take()
            .expect("polled future after completion");
        let mut callbacks_data = this
            .callbacks_data
            .take()
            .expect("polled future after completion");
        let on_body_chunk = this
            .on_body_chunk
            .take()
            .expect("polled future after completion");

        match result {
            Ok(res) => {
                let content_length = res.headers().get(http::header::CONTENT_LENGTH).cloned();
                let classification = classifier.classify_response(&res);

                match classification {
                    ClassifiedResponse::Ready(classification) => {
                        callbacks.on_response(
                            &res,
                            ClassifiedResponse::Ready(classification),
                            &mut callbacks_data,
                        );
                        let res = res.map(|body| ResponseBody {
                            inner: body,
                            parts: None,
                            on_body_chunk,
                            callbacks_data: callbacks_data.clone(),
                            content_length,
                        });
                        Poll::Ready(Ok(res))
                    }
                    ClassifiedResponse::RequiresEos(classify_eos) => {
                        callbacks.on_response(
                            &res,
                            ClassifiedResponse::RequiresEos(()),
                            &mut callbacks_data,
                        );
                        let res = res.map(|body| ResponseBody {
                            inner: body,
                            callbacks_data: callbacks_data.clone(),
                            on_body_chunk,
                            parts: Some((classify_eos, callbacks)),
                            content_length,
                        });
                        Poll::Ready(Ok(res))
                    }
                }
            }
            Err(err) => {
                let classification = classifier.classify_error(&err);
                callbacks.on_failure(FailedAt::Response, classification, &mut callbacks_data);
                Poll::Ready(Err(err))
            }
        }
    }
}