caco3_web/
json.rs

1use std::borrow::Cow;
2use std::marker::PhantomData;
3
4use serde::{Serialize, Serializer};
5
6type StrCow = Cow<'static, str>;
7
8const DEFAULT_SUCCESS_CODE: &str = "0";
9const DEFAULT_ERROR_CODE: &str = "-1";
10const DEFAULT_ERROR_MESSAGE: &str = "Internal server error";
11
12#[derive(Default)]
13pub struct ApiJsonErrorBuilder<T> {
14    code: Option<StrCow>,
15    error: Option<StrCow>,
16    _phantom: PhantomData<T>,
17}
18
19impl<T: Serialize> ApiJsonErrorBuilder<T> {
20    pub fn new() -> Self {
21        Self {
22            code: None,
23            error: None,
24            _phantom: PhantomData,
25        }
26    }
27
28    pub fn code(mut self, code: impl Into<StrCow>) -> Self {
29        self.code = Some(code.into());
30        self
31    }
32
33    pub fn error(mut self, error: impl Into<StrCow>) -> Self {
34        self.error = Some(error.into());
35        self
36    }
37
38    pub fn build(self) -> ApiJson<T> {
39        ApiJson::Error {
40            code: self.code.or_else(|| Some(DEFAULT_ERROR_CODE.into())),
41            error: self.error.or_else(|| Some(DEFAULT_ERROR_MESSAGE.into())),
42        }
43    }
44}
45
46/// Standard Api response formatter.
47pub enum ApiJson<T> {
48    Data {
49        code: Option<StrCow>,
50        data: Option<T>,
51    },
52    Error {
53        code: Option<StrCow>,
54        error: Option<StrCow>,
55    },
56}
57
58impl<T: Serialize> ApiJson<T> {
59    pub fn ok(data: T) -> Self {
60        Self::Data {
61            data: Some(data),
62            code: None,
63        }
64    }
65
66    pub fn data_with_code(data: T, code: StrCow) -> Self {
67        Self::Data {
68            data: Some(data),
69            code: Some(code),
70        }
71    }
72
73    pub fn error_builder() -> ApiJsonErrorBuilder<T> {
74        ApiJsonErrorBuilder::<T>::new()
75    }
76
77    fn as_serializable(&self) -> ApiJsonSerializable<'_, T> {
78        match *self {
79            Self::Data { ref code, ref data } => ApiJsonSerializable {
80                code: code.as_deref().unwrap_or(DEFAULT_SUCCESS_CODE),
81                error: None,
82                data: data.as_ref(),
83            },
84            Self::Error {
85                ref code,
86                ref error,
87            } => ApiJsonSerializable {
88                code: code.as_deref().unwrap_or(DEFAULT_ERROR_CODE),
89                error: error.as_deref(),
90                data: None,
91            },
92        }
93    }
94}
95
96impl ApiJson<()> {
97    /// Convenience method to build error without specifying generic type parameter
98    pub fn unit_error_builder() -> ApiJsonErrorBuilder<()> {
99        ApiJsonErrorBuilder::new()
100    }
101
102    pub fn no_content() -> Self {
103        Self::Data {
104            data: Some(()),
105            code: None,
106        }
107    }
108
109    pub const fn default_error() -> Self {
110        Self::Error {
111            code: Some(StrCow::Borrowed(DEFAULT_ERROR_CODE)),
112            error: Some(StrCow::Borrowed(DEFAULT_ERROR_MESSAGE)),
113        }
114    }
115}
116
117impl<T: Serialize> Serialize for ApiJson<T> {
118    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119        where
120            S: Serializer,
121    {
122        Serialize::serialize(&self.as_serializable(), serializer)
123    }
124}
125
126#[derive(Serialize)]
127struct ApiJsonSerializable<'a, T> {
128    code: &'a str,
129    #[serde(rename = "data", skip_serializing_if = "Option::is_none")]
130    data: Option<&'a T>,
131    #[serde(rename = "message", skip_serializing_if = "Option::is_none")]
132    error: Option<&'a str>,
133}
134
135#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
136pub enum JsonMode {
137    Normal,
138    Pretty,
139}
140
141impl JsonMode {
142    pub fn to_string<T>(self, value: &T) -> serde_json::Result<String>
143        where
144            T: ?Sized + Serialize,
145    {
146        match self {
147            Self::Normal => serde_json::to_string(value),
148            Self::Pretty => serde_json::to_string_pretty(value),
149        }
150    }
151
152    pub fn to_vec<T>(self, value: &T) -> serde_json::Result<Vec<u8>>
153        where
154            T: ?Sized + Serialize,
155    {
156        match self {
157            Self::Normal => serde_json::to_vec(value),
158            Self::Pretty => serde_json::to_vec_pretty(value),
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use serde_json::json;
166
167    use super::*;
168
169    #[derive(Serialize, Clone)]
170    struct TestData {
171        foo: String,
172    }
173
174    #[test]
175    fn test_ok_data() {
176        let data = TestData {
177            foo: "bar".to_owned(),
178        };
179
180        let json = ApiJson::ok(data);
181        let actual = serde_json::to_value(json).unwrap();
182
183        let expect = json!({
184            "code": DEFAULT_SUCCESS_CODE,
185            "data": {
186                "foo": "bar"
187            },
188        });
189
190        assert_eq!(actual, expect);
191    }
192
193    #[test]
194    fn test_ok_data_with_code() {
195        let data = TestData {
196            foo: "bar".to_owned(),
197        };
198
199        let json = ApiJson::data_with_code(data, "Nani!".into());
200        let actual = serde_json::to_value(json).unwrap();
201
202        let expect = json!({
203            "code": "Nani!",
204            "data": {
205                "foo": "bar"
206            },
207        });
208
209        assert_eq!(actual, expect);
210    }
211
212    #[test]
213    fn test_no_content() {
214        let json = ApiJson::no_content();
215        let actual = serde_json::to_value(json).unwrap();
216
217        let expect = json!({
218            "code": DEFAULT_SUCCESS_CODE,
219            "data": serde_json::Value::Null,
220        });
221
222        assert_eq!(actual, expect);
223    }
224
225    #[test]
226    fn test_build_default_error_message() {
227        let json = ApiJson::unit_error_builder().build();
228        let actual = serde_json::to_value(json).unwrap();
229        let expect = json!({
230            "code": DEFAULT_ERROR_CODE,
231            "message": DEFAULT_ERROR_MESSAGE,
232        });
233        assert_eq!(actual, expect);
234    }
235
236    #[test]
237    fn test_build_error_with_message() {
238        let json = ApiJson::unit_error_builder().error("foo").build();
239        let actual = serde_json::to_value(json).unwrap();
240        let expect = json!({
241            "code": DEFAULT_ERROR_CODE,
242            "message": "foo",
243        });
244        assert_eq!(actual, expect);
245    }
246
247    #[test]
248    fn test_build_error_with_code_and_message() {
249        let json = ApiJson::unit_error_builder()
250            .code("-1")
251            .error("bar")
252            .build();
253        let actual = serde_json::to_value(json).unwrap();
254        let expect = json!({
255            "code": "-1",
256            "message": "bar",
257        });
258        assert_eq!(actual, expect);
259    }
260
261    #[test]
262    fn test_api_json_is_send_and_sync() {
263        fn require_send(_: impl Send + Sync) {}
264        require_send(ApiJson::ok(()));
265    }
266}