openai-openapi-http 0.6.0

Client for OpenAI API generated from OpenAPI spec
Documentation
use crate::{ContentTypeError, Error, StatusError};
use http_body_util::BodyExt;
use std::pin::Pin;
use std::task::{Context, Poll, ready};

#[pin_project::pin_project(project = SendProj)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum Send<Fut, B, E>
where
    Fut: Future<Output = Result<http::Response<B>, E>>,
    B: http_body::Body,
{
    S0 {
        #[pin]
        f: Fut,
        expected: Option<(http::StatusCode, Option<mime::Mime>)>,
    },
    S1 {
        e: Option<Error<E, B::Error>>,
    },
    S2 {
        #[pin]
        f: http_body_util::combinators::Collect<B>,
        parts: Option<http::response::Parts>,
        kind: ErrorKind,
    },
}

pub(crate) enum ErrorKind {
    ContentType,
    Status,
}

impl<Fut, B, E> Send<Fut, B, E>
where
    Fut: Future<Output = Result<http::Response<B>, E>>,
    B: http_body::Body,
{
    pub(crate) fn new<C, R>(
        client: C,
        request: R,
        expected: (http::StatusCode, Option<mime::Mime>),
    ) -> Self
    where
        C: FnOnce(http::Request<String>) -> Fut,
        R: FnOnce() -> Result<http::Request<String>, Error<E, B::Error>>,
    {
        match request() {
            Ok(request) => {
                tracing::debug!(?request);
                Self::S0 {
                    f: client(request),
                    expected: Some(expected),
                }
            }
            Err(e) => Self::S1 { e: Some(e) },
        }
    }
}

impl<Fut, B, E> Future for Send<Fut, B, E>
where
    Fut: Future<Output = Result<http::Response<B>, E>>,
    B: http_body::Body,
{
    type Output = Result<http::Response<B>, Error<E, B::Error>>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        loop {
            match self.as_mut().project() {
                SendProj::S0 { f, expected } => {
                    let response = ready!(f.poll(cx)).map_err(Error::Client)?;
                    let expected = expected.take().unwrap();
                    let content_type = content_type(&response);
                    let kind = if response.status() != expected.0 {
                        ErrorKind::Status
                    } else if expected.1.is_some_and(|expected| {
                        content_type.as_ref().map(mime::Mime::essence_str)
                            != Some(expected.essence_str())
                    }) {
                        ErrorKind::ContentType
                    } else {
                        break Poll::Ready(Ok(response));
                    };
                    let (parts, body) = response.into_parts();
                    self.set(Self::S2 {
                        f: body.collect(),
                        parts: Some(parts),
                        kind,
                    });
                }
                SendProj::S1 { e } => {
                    break Poll::Ready(Err(e.take().unwrap()));
                }
                SendProj::S2 { f, parts, kind } => {
                    let body = ready!(f.poll(cx)).map_err(Error::Body)?;
                    let response =
                        http::Response::from_parts(parts.take().unwrap(), body.to_bytes());
                    tracing::debug!(?response);
                    break Poll::Ready(Err(match kind {
                        ErrorKind::ContentType => Error::ContentType(ContentTypeError(response)),
                        ErrorKind::Status => Error::Status(StatusError(response)),
                    }));
                }
            }
        }
    }
}

pub(crate) fn content_type<B>(response: &http::Response<B>) -> Option<mime::Mime> {
    response
        .headers()
        .get(http::header::CONTENT_TYPE)
        .and_then(|value| value.to_str().ok()?.parse().ok())
}