Skip to main content

quokka_handler/
response.rs

1use std::collections::HashMap;
2
3use axum::{http::StatusCode, response::IntoResponse, Json};
4
5///
6/// A response object folling the [jsonapi format](https://jsonapi.org/format/).
7///
8/// # Note // TODO: Address these inconsistencies
9///
10/// This is mostly a scratch, relying in the [serde_json::Value] for certain scenarios
11///
12/// Also it does not restrict the [Self::data] variant (see <https://jsonapi.org/format/#document-top-level> for how it should look).
13///
14///
15#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
16pub struct JsonResponse<D = serde_json::Value, M = DefaultMeta> {
17    #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
18    pub data: Option<D>,
19    #[serde(skip_serializing_if = "Vec::is_empty", default)]
20    pub errors: Vec<ErrorObject>,
21    #[serde(skip_serializing_if = "Option::is_none", default)]
22    pub meta: Option<M>,
23    #[serde(skip, default)]
24    pub status_code: u16,
25}
26
27pub type DefaultMeta = HashMap<String, serde_json::Value>;
28
29#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
30pub struct ErrorObject<M = serde_json::Value> {
31    #[serde(skip_serializing_if = "String::is_empty", default)]
32    pub id: String,
33    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
34    pub links: HashMap<String, Link>,
35    pub status: String,
36    #[serde(skip_serializing_if = "String::is_empty", default)]
37    pub title: String,
38    #[serde(skip_serializing_if = "String::is_empty", default)]
39    pub detail: String,
40    pub source: Source,
41    #[serde(skip_serializing_if = "Option::is_none", default)]
42    pub meta: Option<M>,
43}
44
45#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
46pub struct Source {
47    #[serde(skip_serializing_if = "String::is_empty", default)]
48    pub pointer: String,
49    #[serde(skip_serializing_if = "String::is_empty", default)]
50    pub parameter: String,
51    #[serde(skip_serializing_if = "String::is_empty", default)]
52    pub header: String,
53}
54
55#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
56pub enum Link<M = DefaultMeta> {
57    String(String),
58    Link {
59        href: String,
60        #[serde(skip_serializing_if = "String::is_empty", default)]
61        rel: String,
62        #[serde(skip_serializing_if = "String::is_empty", default)]
63        title: String,
64        #[serde(rename = "type")]
65        typ: String,
66        #[serde(skip_serializing_if = "String::is_empty", default)]
67        hreflang: String,
68        #[serde(skip_serializing_if = "Option::is_none", default)]
69        meta: Option<M>,
70    },
71}
72
73impl JsonResponse<(), serde_json::Value> {
74    ///
75    /// Responds with no data, but a "message" in the "meta" section
76    ///
77    pub fn no_data(message: impl ToString) -> Self {
78        Self {
79            status_code: 204,
80            meta: Some(serde_json::json! {{
81                "message": message.to_string(),
82            }}),
83            ..Default::default()
84        }
85    }
86}
87
88impl<D> JsonResponse<D> {
89    pub fn data(data: D) -> Self {
90        Self {
91            status_code: 200,
92            data: Some(data),
93            ..Default::default()
94        }
95    }
96}
97
98impl<D: serde::Serialize, M: serde::Serialize> IntoResponse for JsonResponse<D, M> {
99    fn into_response(self) -> axum::response::Response {
100        let status = self.status_code;
101
102        let mut response = Json(self).into_response();
103
104        if status >= 100 {
105            *response.status_mut() = StatusCode::from_u16(status).unwrap_or_default();
106        }
107
108        response
109    }
110}
111
112impl<D, M> JsonResponse<D, M> {
113    pub fn error(status: u16, title: impl ToString, detail: impl ToString) -> Self {
114        let mut me = Self {
115            status_code: status,
116            meta: None,
117            data: None,
118            ..Default::default()
119        };
120        me.add_error(ErrorObject {
121            status: status.to_string(),
122            title: title.to_string(),
123            detail: detail.to_string(),
124            ..Default::default()
125        });
126
127        me
128    }
129
130    pub fn add_error(&mut self, error: ErrorObject) -> &mut Self {
131        self.errors.push(error);
132
133        self
134    }
135}
136
137impl<D, M> Default for JsonResponse<D, M> {
138    fn default() -> Self {
139        Self {
140            data: Default::default(),
141            errors: Default::default(),
142            meta: Default::default(),
143            status_code: Default::default(),
144        }
145    }
146}