puzz 0.2.0

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

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

pub async fn json<T>(request: &mut Request) -> Result<T, ExtractJsonError>
where
    T: DeserializeOwned,
{
    if !is_json_content_type(request) {
        return Err(ExtractJsonError::UnsupportedContentType);
    }

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

    serde_json::from_slice(&bytes).map_err(ExtractJsonError::FailedToDeserialize)
}

fn is_json_content_type(request: &Request) -> 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;
    };

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

    let is_json_content_type = mime.type_() == "application"
        && (mime.subtype() == "json" || mime.suffix().filter(|name| *name == "json").is_some());

    is_json_content_type
}

#[derive(Debug)]
pub enum ExtractJsonError {
    UnsupportedContentType,
    FailedToReadBody(BoxError),
    FailedToDeserialize(serde_json::Error),
}

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

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