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::{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    ) -> Result<Self, impl Writer + Send + fmt::Debug + 'static> {
71        req.parse_json().await
72    }
73    async fn extract_with_arg(
74        req: &'ex mut Request,
75        _arg: &str,
76    ) -> Result<Self, impl Writer + Send + fmt::Debug + 'static> {
77        Self::extract(req).await
78    }
79}
80
81impl<'de, T> Deserialize<'de> for JsonBody<T>
82where
83    T: Deserialize<'de>,
84{
85    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86    where
87        D: Deserializer<'de>,
88    {
89        T::deserialize(deserializer).map(JsonBody)
90    }
91}
92
93impl<'de, T> EndpointArgRegister for JsonBody<T>
94where
95    T: Deserialize<'de> + ToSchema,
96{
97    fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
98        let request_body = Self::to_request_body(components);
99        let _ = <T as ToSchema>::to_schema(components);
100        operation.request_body = Some(request_body);
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use std::collections::BTreeMap;
107
108    use assert_json_diff::assert_json_eq;
109    use salvo_core::test::TestClient;
110    use serde_json::json;
111
112    use super::*;
113
114    #[test]
115    fn test_json_body_into_inner() {
116        let form = JsonBody::<String>("json_body".to_owned());
117        assert_eq!(form.into_inner(), "json_body".to_owned());
118    }
119
120    #[test]
121    fn test_json_body_deref() {
122        let form = JsonBody::<String>("json_body".to_owned());
123        assert_eq!(form.deref(), &"json_body".to_owned());
124    }
125
126    #[test]
127    fn test_json_body_deref_mut() {
128        let mut form = JsonBody::<String>("json_body".to_owned());
129        assert_eq!(form.deref_mut(), &mut "json_body".to_owned());
130    }
131
132    #[test]
133    fn test_json_body_to_request_body() {
134        let mut components = Components::default();
135        let request_body = JsonBody::<String>::to_request_body(&mut components);
136        assert_json_eq!(
137            request_body,
138            json!({
139                "description": "Extract json format data from request.",
140                "content": {
141                    "application/json": {
142                        "schema": {
143                            "type": "string"
144                        }
145                    }
146                }
147            })
148        );
149    }
150
151    #[test]
152    fn test_json_body_debug() {
153        let form = JsonBody::<String>("json_body".to_owned());
154        assert_eq!(format!("{form:?}"), r#""json_body""#);
155    }
156
157    #[test]
158    fn test_json_body_display() {
159        let form = JsonBody::<String>("json_body".to_owned());
160        assert_eq!(format!("{form}"), "json_body");
161    }
162
163    #[test]
164    fn test_json_body_metadata() {
165        let metadata = JsonBody::<String>::metadata();
166        assert_eq!("", metadata.name);
167    }
168
169    #[tokio::test]
170    async fn test_json_body_extract_with_arg() {
171        let map = BTreeMap::from_iter([("key", "value")]);
172        let mut req = TestClient::post("http://127.0.0.1:8698/")
173            .json(&map)
174            .build();
175        let result = JsonBody::<BTreeMap<&str, &str>>::extract_with_arg(&mut req, "key").await;
176        assert_eq!("value", result.unwrap().0["key"]);
177    }
178
179    #[test]
180    fn test_json_body_register() {
181        let mut components = Components::new();
182        let mut operation = Operation::new();
183        JsonBody::<String>::register(&mut components, &mut operation, "arg");
184
185        assert_json_eq!(
186            operation,
187            json!({
188                "requestBody": {
189                    "content": {
190                        "application/json": {
191                            "schema": {
192                                "type": "string"
193                            }
194                        }
195                    },
196                    "description": "Extract json format data from request."
197                },
198                "responses": {}
199            })
200        );
201    }
202}