salvo_oapi/extract/payload/
json.rs1use 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
11pub struct JsonBody<T>(pub T);
13impl<T> JsonBody<T> {
14 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}