cc_utils/
responses.rs

1//! Implementation of utilities for working with responses in `salvo` and `reqwest`.
2
3use crate::prelude::*;
4
5#[cfg(feature = "salvo")]
6#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
7use salvo::http::HeaderValue;
8
9#[cfg(feature = "salvo")]
10#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
11use salvo::hyper::header::CONTENT_TYPE;
12
13#[cfg(feature = "salvo")]
14#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
15use salvo::oapi::{EndpointOutRegister, ToSchema};
16
17#[cfg(feature = "salvo")]
18#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
19use salvo::{Request, Response, Depot};
20
21#[cfg(feature = "salvo")]
22#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
23use salvo::Writer as ServerResponseWriter;
24
25#[cfg(feature = "salvo")]
26#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
27use salvo::fs::NamedFile;
28
29#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
30use serde::Serialize;
31
32#[cfg(feature = "reqwest")]
33#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
34use serde::de::DeserializeOwned;
35
36/// Macro to define the function that called the response.
37#[macro_export]
38macro_rules! fn_name {
39  () => {{
40    fn f() {}
41    fn type_name_of<T>(_: T) -> &'static str {
42      std::any::type_name::<T>()
43    }
44    let name = type_name_of(f);
45    
46    // For `#[endpoint]` path can be shortened as follows:
47    match name[..name.len() - 3].rsplit("::").nth(2) {
48      Some(el) => el,
49      None => &name[..name.len() - 3],
50    }
51  }};
52}
53
54/// Macro for automating `EndpointOutRegister` implementations (for simple types)
55#[cfg(feature = "salvo")]
56macro_rules! impl_oapi_endpoint_out {
57  ($t:tt, $c:expr) => {
58    impl EndpointOutRegister for $t {
59      #[inline]
60      fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
61        operation.responses.insert(
62          "200",
63          salvo::oapi::Response::new("Ok").add_content($c, String::to_schema(components)),
64        );
65      }
66    }
67  };
68}
69
70/// Macro for automating `EndpointOutRegister` implementations (for template types)
71#[cfg(feature = "salvo")]
72macro_rules! impl_oapi_endpoint_out_t {
73  ($t:tt, $c:expr) => {
74    impl<T> EndpointOutRegister for $t<T> {
75      #[inline]
76      fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
77        operation.responses.insert(
78          "200",
79          salvo::oapi::Response::new("Ok").add_content($c, String::to_schema(components)),
80        );
81      }
82    }
83  };
84}
85
86/// Sends 200 without data.
87#[cfg(feature = "salvo")]
88#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
89pub struct OK(pub &'static str);
90
91#[cfg(feature = "salvo")]
92#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
93impl_oapi_endpoint_out!(OK, "text/plain");
94
95#[cfg(feature = "salvo")]
96#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
97#[macro_export]
98macro_rules! ok { () => { Ok(OK(cc_utils::fn_name!())) }; }
99
100#[cfg(feature = "salvo")]
101#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
102#[salvo::async_trait]
103impl ServerResponseWriter for OK {
104  async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
105    res.status_code(StatusCode::OK);
106    res.render("");
107    log::debug!("[{}] => Received and sent result 200", self.0);
108  }
109}
110
111/// Sends 200 and plain text.
112#[cfg(feature = "salvo")]
113#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
114#[derive(Debug)]
115pub struct Plain(pub String, pub &'static str);
116
117#[cfg(feature = "salvo")]
118#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
119impl_oapi_endpoint_out!(Plain, "text/plain");
120
121#[cfg(feature = "salvo")]
122#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
123#[macro_export]
124macro_rules! plain { ($e:expr) => { Ok(Plain($e, cc_utils::fn_name!())) }; }
125
126#[cfg(feature = "salvo")]
127#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
128#[salvo::async_trait]
129impl ServerResponseWriter for Plain {
130  async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
131    res.status_code(StatusCode::OK);
132    res.render(&self.0);
133    log::debug!("[{}] => Received and sent result 200 with text: {}", self.1, self.0);
134  }
135}
136
137/// Sends 200 and HTML.
138#[cfg(feature = "salvo")]
139#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
140#[derive(Debug)]
141pub struct Html(pub String, pub &'static str);
142
143#[cfg(feature = "salvo")]
144#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
145impl_oapi_endpoint_out!(Html, "text/html");
146
147#[cfg(feature = "salvo")]
148#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
149#[macro_export]
150macro_rules! html { ($e:expr) => { Ok(Html($e, cc_utils::fn_name!())) }; }
151
152#[cfg(feature = "salvo")]
153#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
154#[salvo::async_trait]
155impl ServerResponseWriter for Html {
156  async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
157    res.status_code(StatusCode::OK);
158    res.render(salvo::writing::Text::Html(&self.0));
159    log::debug!("[{}] => Received and sent result 200 with HTML", self.1);
160  }
161}
162
163/// Sends 200 and file.
164#[cfg(feature = "salvo")]
165#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
166#[derive(Debug)]
167pub struct File(pub String, pub String, pub &'static str);
168
169#[cfg(feature = "salvo")]
170#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
171impl_oapi_endpoint_out!(File, "application/octet-stream");
172
173/// File response.
174///
175/// Usage:
176///
177/// ```rust
178/// use cc_utils::prelude::*;
179/// use salvo::prelude::*;
180///
181/// pub async fn some_endpoint() -> MResult<File> {
182///   file!("filepath", "Normal file name")
183/// }
184/// ```
185#[cfg(feature = "salvo")]
186#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
187#[macro_export]
188macro_rules! file { ($e1:expr, $e2:expr) => { Ok(File($e1, $e2, cc_utils::fn_name!())) }; }
189
190#[cfg(feature = "salvo")]
191#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
192#[salvo::async_trait]
193impl ServerResponseWriter for File {
194  async fn write(self, req: &mut Request, _depot: &mut Depot, res: &mut Response) {
195    res.status_code(StatusCode::OK);
196    NamedFile::builder(&self.0).attached_name(&self.1).use_last_modified(true).send(req.headers(), res).await;
197    log::debug!("[{}] => Received and sent result 200 with file {}", self.2, self.1);
198  }
199}
200
201/// Sends 200 and JSON.
202#[cfg(feature = "salvo")]
203#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
204#[derive(Debug)]
205pub struct Json<T>(pub T, pub &'static str);
206
207#[cfg(feature = "salvo")]
208#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
209impl_oapi_endpoint_out_t!(Json, "application/json");
210
211#[cfg(feature = "salvo")]
212#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
213#[macro_export]
214macro_rules! json { ($e:expr) => { Ok(Json($e, cc_utils::fn_name!())) }; }
215
216#[cfg(feature = "salvo")]
217#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
218#[salvo::async_trait]
219impl<T: Serialize + Send> ServerResponseWriter for Json<T> {
220  async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
221    res.status_code(StatusCode::OK);
222    res.render(salvo::writing::Json(self.0));
223    log::debug!("[{}] => Received and sent result 200 with JSON", self.1);
224  }
225}
226
227/// Sends 200 and MsgPack.
228#[cfg(feature = "salvo")]
229#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
230#[derive(Debug)]
231pub struct MsgPack<T>(pub T, pub &'static str);
232
233#[cfg(feature = "salvo")]
234#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
235impl_oapi_endpoint_out_t!(MsgPack, "application/msgpack");
236
237#[cfg(feature = "salvo")]
238#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
239#[macro_export]
240macro_rules! msgpack { ($e:expr) => { Ok(MsgPack($e, cc_utils::fn_name!())) }; }
241
242#[cfg(feature = "salvo")]
243#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
244#[salvo::async_trait]
245impl<T: Serialize + Send> ServerResponseWriter for MsgPack<T> {
246  async fn write(self, req: &mut Request, depot: &mut Depot, res: &mut Response) {
247    res.status_code(StatusCode::OK);
248    match rmp_serde::to_vec(&self.0) {
249      Ok(bytes) => {
250        res.headers_mut().insert(CONTENT_TYPE, HeaderValue::from_static("application/msgpack; charset=utf-8"));
251        log::debug!("[{}] => Sending bytes: {:?}", self.1, bytes);
252        res.write_body(bytes).ok();
253        log::debug!("[{}] => Received and sent result 200 with MsgPack", self.1);
254      }
255      Err(e) => {
256        log::error!("[{}] => Failed to serialize data: {:?}", e, self.1);
257        ErrorResponse::from("Failed to serialize data.").with_500().build().write(req, depot, res).await;
258      }
259    }
260  }
261}
262
263#[cfg(feature = "reqwest")]
264#[allow(async_fn_in_trait)]
265#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
266pub trait MsgPackResponse {
267  async fn msgpack<T: DeserializeOwned>(self) -> CResult<T>;
268}
269
270#[cfg(feature = "reqwest")]
271#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
272impl MsgPackResponse for reqwest::Response {
273  async fn msgpack<T: DeserializeOwned>(self) -> CResult<T> {
274    let full = self.bytes().await?;
275    rmp_serde::from_slice(&full).consider_cli(None)
276  }
277}