Skip to main content

salvo_oapi/extract/payload/
json.rs

1use std::fmt::{self, Debug, Display, Formatter};
2use std::ops::{Deref, DerefMut};
3
4use salvo_core::extract::{Extractible, Metadata};
5use salvo_core::{Depot, Request, Writer};
6use serde::{Deserialize, Deserializer};
7
8use crate::endpoint::EndpointArgRegister;
9use crate::{Components, Content, Operation, RequestBody, ToRequestBody, ToSchema};
10
11/// Represents the parameters passed by the URI path.
12pub struct JsonBody<T>(pub T);
13impl<T> JsonBody<T> {
14    /// Consumes self and returns the value of the parameter.
15    pub fn into_inner(self) -> T {
16        self.0
17    }
18}
19
20impl<T> Deref for JsonBody<T> {
21    type Target = T;
22
23    fn deref(&self) -> &Self::Target {
24        &self.0
25    }
26}
27
28impl<T> DerefMut for JsonBody<T> {
29    fn deref_mut(&mut self) -> &mut Self::Target {
30        &mut self.0
31    }
32}
33
34impl<'de, T> ToRequestBody for JsonBody<T>
35where
36    T: Deserialize<'de> + ToSchema,
37{
38    fn to_request_body(components: &mut Components) -> RequestBody {
39        RequestBody::new()
40            .description("Extract json format data from request.")
41            .add_content("application/json", Content::new(T::to_schema(components)))
42    }
43}
44
45impl<T> fmt::Debug for JsonBody<T>
46where
47    T: Debug,
48{
49    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50        self.0.fmt(f)
51    }
52}
53
54impl<T: Display> Display for JsonBody<T> {
55    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56        self.0.fmt(f)
57    }
58}
59
60impl<'ex, T> Extractible<'ex> for JsonBody<T>
61where
62    T: Deserialize<'ex> + Send,
63{
64    fn metadata() -> &'static Metadata {
65        static METADATA: Metadata = Metadata::new("");
66        &METADATA
67    }
68    async fn extract(
69        req: &'ex mut Request,
70        _depot: &'ex mut Depot,
71    ) -> Result<Self, impl Writer + Send + fmt::Debug + 'static> {
72        req.parse_json().await
73    }
74    async fn extract_with_arg(
75        req: &'ex mut Request,
76        depot: &'ex mut Depot,
77        _arg: &str,
78    ) -> Result<Self, impl Writer + Send + fmt::Debug + 'static> {
79        Self::extract(req, depot).await
80    }
81}
82
83impl<'de, T> Deserialize<'de> for JsonBody<T>
84where
85    T: Deserialize<'de>,
86{
87    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88    where
89        D: Deserializer<'de>,
90    {
91        T::deserialize(deserializer).map(JsonBody)
92    }
93}
94
95impl<'de, T> EndpointArgRegister for JsonBody<T>
96where
97    T: Deserialize<'de> + ToSchema,
98{
99    fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
100        let request_body = Self::to_request_body(components);
101        let _ = <T as ToSchema>::to_schema(components);
102        operation.request_body = Some(request_body);
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use std::collections::BTreeMap;
109
110    use assert_json_diff::assert_json_eq;
111    use salvo_core::test::TestClient;
112    use serde_json::json;
113
114    use super::*;
115
116    #[test]
117    fn test_json_body_into_inner() {
118        let form = JsonBody::<String>("json_body".to_owned());
119        assert_eq!(form.into_inner(), "json_body".to_owned());
120    }
121
122    #[test]
123    fn test_json_body_deref() {
124        let form = JsonBody::<String>("json_body".to_owned());
125        assert_eq!(form.deref(), &"json_body".to_owned());
126    }
127
128    #[test]
129    fn test_json_body_deref_mut() {
130        let mut form = JsonBody::<String>("json_body".to_owned());
131        assert_eq!(form.deref_mut(), &mut "json_body".to_owned());
132    }
133
134    #[test]
135    fn test_json_body_to_request_body() {
136        let mut components = Components::default();
137        let request_body = JsonBody::<String>::to_request_body(&mut components);
138        assert_json_eq!(
139            request_body,
140            json!({
141                "description": "Extract json format data from request.",
142                "content": {
143                    "application/json": {
144                        "schema": {
145                            "type": "string"
146                        }
147                    }
148                }
149            })
150        );
151    }
152
153    #[test]
154    fn test_json_body_debug() {
155        let form = JsonBody::<String>("json_body".to_owned());
156        assert_eq!(format!("{form:?}"), r#""json_body""#);
157    }
158
159    #[test]
160    fn test_json_body_display() {
161        let form = JsonBody::<String>("json_body".to_owned());
162        assert_eq!(format!("{form}"), "json_body");
163    }
164
165    #[test]
166    fn test_json_body_metadata() {
167        let metadata = JsonBody::<String>::metadata();
168        assert_eq!("", metadata.name);
169    }
170
171    #[tokio::test]
172    async fn test_json_body_extract_with_arg() {
173        let map = BTreeMap::from_iter([("key", "value")]);
174        let mut req = TestClient::post("http://127.0.0.1:8698/")
175            .json(&map)
176            .build();
177        let mut depot = Depot::new();
178        let result =
179            JsonBody::<BTreeMap<&str, &str>>::extract_with_arg(&mut req, &mut depot, "key").await;
180        assert_eq!("value", result.unwrap().0["key"]);
181    }
182
183    #[test]
184    fn test_json_body_register() {
185        let mut components = Components::new();
186        let mut operation = Operation::new();
187        JsonBody::<String>::register(&mut components, &mut operation, "arg");
188
189        assert_json_eq!(
190            operation,
191            json!({
192                "requestBody": {
193                    "content": {
194                        "application/json": {
195                            "schema": {
196                                "type": "string"
197                            }
198                        }
199                    },
200                    "description": "Extract json format data from request."
201                },
202                "responses": {}
203            })
204        );
205    }
206}