quokka-handler 0.3.0-beta.0

Handler helpers for Quokka
Documentation
use std::collections::HashMap;

use axum::{http::StatusCode, response::IntoResponse, Json};

///
/// A response object folling the [jsonapi format](https://jsonapi.org/format/).
///
/// # Note // TODO: Address these inconsistencies
///
/// This is mostly a scratch, relying in the [serde_json::Value] for certain scenarios
///
/// Also it does not restrict the [Self::data] variant (see <https://jsonapi.org/format/#document-top-level> for how it should look).
///
///
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct JsonResponse<D = serde_json::Value, M = DefaultMeta> {
    #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
    pub data: Option<D>,
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    pub errors: Vec<ErrorObject>,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub meta: Option<M>,
    #[serde(skip, default)]
    pub status_code: u16,
}

pub type DefaultMeta = HashMap<String, serde_json::Value>;

#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct ErrorObject<M = serde_json::Value> {
    #[serde(skip_serializing_if = "String::is_empty", default)]
    pub id: String,
    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
    pub links: HashMap<String, Link>,
    pub status: String,
    #[serde(skip_serializing_if = "String::is_empty", default)]
    pub title: String,
    #[serde(skip_serializing_if = "String::is_empty", default)]
    pub detail: String,
    pub source: Source,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub meta: Option<M>,
}

#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Source {
    #[serde(skip_serializing_if = "String::is_empty", default)]
    pub pointer: String,
    #[serde(skip_serializing_if = "String::is_empty", default)]
    pub parameter: String,
    #[serde(skip_serializing_if = "String::is_empty", default)]
    pub header: String,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub enum Link<M = DefaultMeta> {
    String(String),
    Link {
        href: String,
        #[serde(skip_serializing_if = "String::is_empty", default)]
        rel: String,
        #[serde(skip_serializing_if = "String::is_empty", default)]
        title: String,
        #[serde(rename = "type")]
        typ: String,
        #[serde(skip_serializing_if = "String::is_empty", default)]
        hreflang: String,
        #[serde(skip_serializing_if = "Option::is_none", default)]
        meta: Option<M>,
    },
}

impl JsonResponse<(), serde_json::Value> {
    ///
    /// Responds with no data, but a "message" in the "meta" section
    ///
    pub fn no_data(message: impl ToString) -> Self {
        Self {
            status_code: 204,
            meta: Some(serde_json::json! {{
                "message": message.to_string(),
            }}),
            ..Default::default()
        }
    }
}

impl<D> JsonResponse<D> {
    pub fn data(data: D) -> Self {
        Self {
            status_code: 200,
            data: Some(data),
            ..Default::default()
        }
    }
}

impl<D: serde::Serialize, M: serde::Serialize> IntoResponse for JsonResponse<D, M> {
    fn into_response(self) -> axum::response::Response {
        let status = self.status_code;

        let mut response = Json(self).into_response();

        if status >= 100 {
            *response.status_mut() = StatusCode::from_u16(status).unwrap_or_default();
        }

        response
    }
}

impl<D, M> JsonResponse<D, M> {
    pub fn error(status: u16, title: impl ToString, detail: impl ToString) -> Self {
        let mut me = Self {
            status_code: status,
            meta: None,
            data: None,
            ..Default::default()
        };
        me.add_error(ErrorObject {
            status: status.to_string(),
            title: title.to_string(),
            detail: detail.to_string(),
            ..Default::default()
        });

        me
    }

    pub fn add_error(&mut self, error: ErrorObject) -> &mut Self {
        self.errors.push(error);

        self
    }
}

impl<D, M> Default for JsonResponse<D, M> {
    fn default() -> Self {
        Self {
            data: Default::default(),
            errors: Default::default(),
            meta: Default::default(),
            status_code: Default::default(),
        }
    }
}