axol 0.2.0

Axol Web Framework
use std::ops::{Deref, DerefMut};

use axol_http::{
    Body, mime::Mime, request::RequestPartsRef, response::Response, typed_headers::ContentType,
};
use serde::{Serialize, de::DeserializeOwned};

use crate::{Error, FromRequest, FromRequestParts, IntoResponse, Result, Typed};

#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct Form<T>(pub T);

impl<T> Deref for Form<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> DerefMut for Form<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[async_trait::async_trait]
impl<'a, T: DeserializeOwned + Send + Sync + 'a> FromRequest<'a> for Form<T> {
    async fn from_request(request: RequestPartsRef<'a>, body: Body) -> Result<Self> {
        let content_type: Mime = Typed::<ContentType>::from_request_parts(request)
            .await?
            .0
            .into();
        if content_type.essence_str()
            != axol_http::mime::APPLICATION_WWW_FORM_URLENCODED.essence_str()
        {
            return Err(Error::unsupported_media_type(
                "Expected request with `Content-Type: application/x-www-form-urlencoded`",
            ));
        }
        let bytes = body.collect().await?;
        let deserializer = serde_urlencoded::Deserializer::new(form_urlencoded::parse(&bytes));

        let value = match serde_path_to_error::deserialize(deserializer) {
            Ok(value) => value,
            Err(err) => {
                let rejection = Error::bad_request(format!(
                    "Failed to parse the request body as a form: {err}"
                ));
                return Err(rejection);
            }
        };

        Ok(Form(value))
    }
}

impl<T: Serialize> IntoResponse for Form<T> {
    fn into_response(self) -> Result<Response> {
        let mut out = Response::default();
        out.headers.append_typed(&ContentType::form_url_encoded());
        out.body = Body::Bytes(
            serde_urlencoded::to_string(&self.0)
                .map_err(Error::internal)?
                .into_bytes(),
        );
        Ok(out)
    }
}