puzz 0.2.0

A simple and powerful web framework.
Documentation
use std::fmt;

use puzz_core::http::{header, Method};
use puzz_core::{BoxError, Request};
use serde::de::DeserializeOwned;

pub async fn form<T>(request: &mut Request) -> Result<T, ExtractFormError>
where
    T: DeserializeOwned,
{
    if request.method() == Method::GET {
        crate::extract::query(request).map_err(|e| ExtractFormError::FailedToDeserialize(e.0))
    } else {
        if !has_content_type(request, &mime::APPLICATION_WWW_FORM_URLENCODED) {
            return Err(ExtractFormError::UnsupportedContentType);
        }

        let bytes = crate::extract::bytes(request)
            .await
            .map_err(|e| ExtractFormError::FailedToReadBody(e.into()))?;

        serde_urlencoded::from_bytes(&bytes).map_err(ExtractFormError::FailedToDeserialize)
    }
}

fn has_content_type(request: &Request, expected_content_type: &mime::Mime) -> bool {
    let content_type = if let Some(content_type) = request.headers().get(header::CONTENT_TYPE) {
        content_type
    } else {
        return false;
    };

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

    content_type.starts_with(expected_content_type.as_ref())
}

#[derive(Debug)]
pub enum ExtractFormError {
    UnsupportedContentType,
    FailedToReadBody(BoxError),
    FailedToDeserialize(serde_urlencoded::de::Error),
}

impl fmt::Display for ExtractFormError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ExtractFormError::UnsupportedContentType => f.write_str("unsupported content type"),
            ExtractFormError::FailedToReadBody(e) => write!(f, "failed to read body ({})", e),
            ExtractFormError::FailedToDeserialize(e) => {
                write!(f, "failed to deserialize ({})", e)
            }
        }
    }
}

impl std::error::Error for ExtractFormError {}