axum-cbor 0.1.1

Library for sending and receiving CBOR data in Axum web applications
Documentation
use crate::cbor_rejecton::CborRejection;
use crate::errors::{FailedToParseCbor, MissingCBorContentType};
use axum::{
    body::Bytes,
    extract::{FromRequest, Request},
    http::{header, HeaderMap, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
};
use serde::{de::DeserializeOwned, Serialize};

#[must_use]
pub struct Cbor<T>(pub T);

fn is_valid_cbor_content_type(headers: &HeaderMap) -> bool {
    let Some(content_type) = headers.get(header::CONTENT_TYPE) else {
        return false;
    };

    let Ok(content_type) = content_type.to_str() else {
        return false;
    };

    let Ok(mime) = content_type.parse::<mime::Mime>() else {
        return false;
    };

    let is_cbor_content_type = mime.type_() == "application"
        && (mime.subtype() == "cbor" || mime.suffix().is_some_and(|name| name == "cbor"));

    is_cbor_content_type
}

impl<S, T> FromRequest<S> for Cbor<T>
where
    S: Send + Sync,
    T: DeserializeOwned,
{
    type Rejection = CborRejection;
    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        if !is_valid_cbor_content_type(req.headers()) {
            return Err(MissingCBorContentType.into());
        }
        let bytes = Bytes::from_request(req, state).await?;
        let value = ciborium::from_reader(&bytes as &[u8]);
        value.map(Cbor).map_err(|_| FailedToParseCbor.into())
    }
}

impl<T> IntoResponse for Cbor<T>
where
    T: Serialize,
{
    fn into_response(self) -> Response {
        let mut buf = Vec::new();
        match ciborium::into_writer(&self.0, &mut buf) {
            Err(_) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                [(
                    header::CONTENT_TYPE,
                    HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
                )],
                "Failed to serialize".to_string(),
            )
                .into_response(),
            Ok(()) => (
                [(
                    header::CONTENT_TYPE,
                    HeaderValue::from_static("application/cbor"),
                )],
                buf,
            )
                .into_response(),
        }
    }
}