1#[cfg(feature = "salvo")]
4#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
5use salvo::http::HeaderValue;
6
7#[cfg(feature = "salvo")]
8#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
9use salvo::hyper::header::CONTENT_TYPE;
10
11#[cfg(feature = "salvo")]
12#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
13use salvo::oapi::{EndpointOutRegister, ToSchema};
14
15#[cfg(feature = "salvo")]
16#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
17use salvo::{Depot, Request, Response};
18
19#[cfg(feature = "salvo")]
20#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
21use salvo::Writer as ServerResponseWriter;
22
23#[cfg(feature = "salvo")]
24#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
25use salvo::fs::NamedFile;
26
27#[cfg(feature = "salvo")]
29#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
30#[macro_export]
31macro_rules! fn_name {
32 () => {{
33 fn f() {}
34 fn type_name_of<T>(_: T) -> &'static str {
35 std::any::type_name::<T>()
36 }
37 let name = type_name_of(f);
38
39 match name[..name.len() - 3].rsplit("::").nth(2) {
41 Some(el) => el,
42 None => &name[..name.len() - 3],
43 }
44 }};
45}
46
47#[cfg(feature = "salvo")]
49#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
50macro_rules! impl_oapi_endpoint_out {
51 ($t:tt, $c:expr) => {
52 #[cfg(feature = "salvo")]
53 impl EndpointOutRegister for $t {
54 #[inline]
55 fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
56 operation.responses.insert(
57 "200",
58 salvo::oapi::Response::new("Ok").add_content($c, String::to_schema(components)),
59 );
60 }
61 }
62 };
63}
64
65#[cfg(feature = "salvo")]
67#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
68macro_rules! impl_oapi_endpoint_out_t {
69 ($t:tt, $c:expr) => {
70 #[cfg(feature = "salvo")]
71 impl<T> EndpointOutRegister for $t<T> {
72 #[inline]
73 fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
74 operation.responses.insert(
75 "200",
76 salvo::oapi::Response::new("Ok").add_content($c, String::to_schema(components)),
77 );
78 }
79 }
80 };
81}
82
83#[cfg(feature = "salvo")]
84#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
85#[allow(async_fn_in_trait)]
86pub trait ExplicitServerWrite {
88 async fn explicit_write(self, res: &mut Response);
90}
91
92#[cfg(feature = "salvo")]
94#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
95pub struct OK(pub &'static str);
96
97#[cfg(feature = "salvo")]
98#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
99impl_oapi_endpoint_out!(OK, "text/plain");
100
101#[cfg(feature = "salvo")]
103#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
104#[macro_export]
105macro_rules! ok {
106 () => {
107 Ok::<impulse_utils::responses::OK, impulse_utils::errors::ServerError>(impulse_utils::responses::OK(
108 $crate::fn_name!(),
109 ))
110 };
111}
112
113#[cfg(feature = "salvo")]
114#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
115impl ExplicitServerWrite for OK {
116 async fn explicit_write(self, res: &mut Response) {
117 res.status_code(salvo::http::StatusCode::OK);
118 res.render("");
119 tracing::trace!("[{}] => Received and sent result 200", self.0);
120 }
121}
122
123#[cfg(feature = "salvo")]
124#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
125#[salvo::async_trait]
126impl ServerResponseWriter for OK {
127 async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
128 ExplicitServerWrite::explicit_write(self, res).await
129 }
130}
131
132#[cfg(feature = "salvo")]
134#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
135#[derive(Debug)]
136pub struct Plain(pub String, pub &'static str);
137
138#[cfg(feature = "salvo")]
139#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
140#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
141impl_oapi_endpoint_out!(Plain, "text/plain");
142
143#[cfg(feature = "salvo")]
145#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
146#[macro_export]
147macro_rules! plain {
148 ($plain_text:expr) => {
149 Ok::<impulse_utils::responses::Plain, impulse_utils::errors::ServerError>(impulse_utils::responses::Plain(
150 $plain_text,
151 $crate::fn_name!(),
152 ))
153 };
154}
155
156#[cfg(feature = "salvo")]
157#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
158impl ExplicitServerWrite for Plain {
159 async fn explicit_write(self, res: &mut Response) {
160 res.status_code(salvo::http::StatusCode::OK);
161 res.render(&self.0);
162 tracing::trace!("[{}] => Received and sent result 200 with text: {}", self.1, self.0);
163 }
164}
165
166#[cfg(feature = "salvo")]
167#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
168#[salvo::async_trait]
169impl ServerResponseWriter for Plain {
170 async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
171 ExplicitServerWrite::explicit_write(self, res).await
172 }
173}
174
175#[cfg(feature = "salvo")]
177#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
178#[derive(Debug)]
179pub struct Html(pub String, pub &'static str);
180
181#[cfg(feature = "salvo")]
182#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
183impl_oapi_endpoint_out!(Html, "text/html");
184
185#[cfg(feature = "salvo")]
187#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
188#[macro_export]
189macro_rules! html {
190 ($html_data:expr) => {
191 Ok::<impulse_utils::responses::Html, impulse_utils::errors::ServerError>(impulse_utils::responses::Html(
192 $html_data,
193 $crate::fn_name!(),
194 ))
195 };
196}
197
198#[cfg(feature = "salvo")]
199#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
200impl ExplicitServerWrite for Html {
201 async fn explicit_write(self, res: &mut Response) {
202 res.status_code(salvo::http::StatusCode::OK);
203 res.render(salvo::writing::Text::Html(&self.0));
204 tracing::trace!("[{}] => Received and sent result 200 with HTML", self.1);
205 }
206}
207
208#[cfg(feature = "salvo")]
209#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
210#[salvo::async_trait]
211impl ServerResponseWriter for Html {
212 async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
213 ExplicitServerWrite::explicit_write(self, res).await
214 }
215}
216
217#[cfg(feature = "salvo")]
219#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
220#[derive(Debug)]
221pub struct File(pub std::path::PathBuf, pub String, pub &'static str);
222
223#[cfg(feature = "salvo")]
224#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
225impl_oapi_endpoint_out!(File, "application/octet-stream");
226
227#[cfg(feature = "salvo")]
241#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
242#[macro_export]
243macro_rules! file_upload {
244 ($filepath:expr, $attached_filename:expr) => {
245 Ok::<impulse_utils::responses::File, impulse_utils::errors::ServerError>(impulse_utils::responses::File(
246 $filepath,
247 $attached_filename,
248 $crate::fn_name!(),
249 ))
250 };
251}
252
253#[cfg(feature = "salvo")]
254#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
255#[salvo::async_trait]
256impl ServerResponseWriter for File {
257 async fn write(self, req: &mut Request, _depot: &mut Depot, res: &mut Response) {
258 res.status_code(salvo::http::StatusCode::OK);
259 res.headers_mut().append(
260 "Cache-Control",
261 HeaderValue::from_static("public, max-age=0, must-revalidate"),
262 );
263 NamedFile::builder(&self.0)
264 .attached_name(&self.1)
265 .use_etag(true)
266 .use_last_modified(true)
267 .send(req.headers(), res)
268 .await;
269 tracing::trace!("[{}] => Received and sent result 200 with file {}", self.2, self.1);
270 }
271}
272
273#[cfg(feature = "salvo")]
275#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
276#[derive(Debug)]
277pub struct Json<T>(pub T, pub &'static str);
278
279#[cfg(feature = "salvo")]
280#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
281impl_oapi_endpoint_out_t!(Json, "application/json");
282
283#[cfg(feature = "salvo")]
285#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
286#[macro_export]
287macro_rules! json {
288 ($json_data:expr) => {
289 Ok::<impulse_utils::responses::Json<_>, impulse_utils::errors::ServerError>(impulse_utils::responses::Json(
290 $json_data,
291 $crate::fn_name!(),
292 ))
293 };
294}
295
296#[cfg(all(feature = "salvo", feature = "mresult"))]
297#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
298impl<T: serde::Serialize + Send> ExplicitServerWrite for Json<T> {
299 async fn explicit_write(self, res: &mut Response) {
300 res.status_code(salvo::http::StatusCode::OK);
301 match sonic_rs::to_string(&self.0) {
302 Ok(s) => {
303 res.headers_mut().insert(
304 CONTENT_TYPE,
305 HeaderValue::from_static("application/json; charset=utf-8"),
306 );
307 tracing::trace!("[{}] => Sending JSON: {:?}", self.1, s.as_str());
308 res.write_body(s).ok();
309 tracing::trace!("[{}] => Received and sent result 200 with JSON", self.1);
310 }
311 Err(e) => {
312 tracing::error!("[{}] => Failed to serialize data: {:?}", e, self.1);
313 crate::prelude::ServerError::from_private(e)
314 .with_public("Failed to serialize data.")
315 .with_500()
316 .explicit_write(res)
317 .await;
318 }
319 }
320 }
321}
322
323#[cfg(all(feature = "salvo", feature = "mresult"))]
324#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
325#[salvo::async_trait]
326impl<T: serde::Serialize + Send> ServerResponseWriter for Json<T> {
327 async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
328 ExplicitServerWrite::explicit_write(self, res).await
329 }
330}
331
332#[cfg(feature = "salvo")]
334#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
335#[derive(Debug)]
336pub struct MsgPack<T>(pub T, pub &'static str);
337
338#[cfg(feature = "salvo")]
339#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
340impl_oapi_endpoint_out_t!(MsgPack, "application/msgpack");
341
342#[cfg(feature = "salvo")]
344#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
345#[macro_export]
346macro_rules! msgpack {
347 ($msgpack_data:expr) => {
348 Ok::<impulse_utils::responses::MsgPack<_>, impulse_utils::errors::ServerError>(impulse_utils::responses::MsgPack(
349 $msgpack_data,
350 $crate::fn_name!(),
351 ))
352 };
353}
354
355#[cfg(all(feature = "salvo", feature = "mresult"))]
356#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
357impl<T: serde::Serialize + Send> ExplicitServerWrite for MsgPack<T> {
358 async fn explicit_write(self, res: &mut Response) {
359 res.status_code(salvo::http::StatusCode::OK);
360 match rmp_serde::to_vec(&self.0) {
361 Ok(bytes) => {
362 res.headers_mut().insert(
363 CONTENT_TYPE,
364 HeaderValue::from_static("application/msgpack; charset=utf-8"),
365 );
366 tracing::trace!("[{}] => Sending bytes: {:04X?}", self.1, bytes);
367 res.write_body(bytes).ok();
368 tracing::trace!("[{}] => Received and sent result 200 with MsgPack", self.1);
369 }
370 Err(e) => {
371 tracing::error!("[{}] => Failed to serialize data: {:?}", e, self.1);
372 crate::prelude::ServerError::from_private(e)
373 .with_public("Failed to serialize data.")
374 .with_500()
375 .explicit_write(res)
376 .await;
377 }
378 }
379 }
380}
381
382#[cfg(all(feature = "salvo", feature = "mresult"))]
383#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
384#[salvo::async_trait]
385impl<T: serde::Serialize + Send> ServerResponseWriter for MsgPack<T> {
386 async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
387 ExplicitServerWrite::explicit_write(self, res).await
388 }
389}
390
391#[cfg(all(feature = "reqwest", feature = "cresult"))]
393#[allow(async_fn_in_trait)]
394pub trait MsgPackResponse {
395 async fn msgpack<T: serde::de::DeserializeOwned>(self) -> crate::prelude::CResult<T>;
397}
398
399#[cfg(all(feature = "reqwest", feature = "cresult"))]
400impl MsgPackResponse for reqwest::Response {
401 async fn msgpack<T: serde::de::DeserializeOwned>(self) -> crate::prelude::CResult<T> {
402 use crate::errors::ClientError;
403
404 let full = self.bytes().await.map_err(ClientError::from)?;
405 rmp_serde::from_slice(&full).map_err(ClientError::from)
406 }
407}
408
409#[cfg(all(feature = "reqwest", feature = "cresult"))]
411#[allow(async_fn_in_trait)]
412pub trait CollectServerError
413where
414 Self: Sized,
415{
416 async fn collect_server_error(self) -> crate::prelude::CResult<Self>;
418}
419
420#[cfg(all(feature = "reqwest", feature = "cresult"))]
421impl CollectServerError for reqwest::Response {
422 async fn collect_server_error(self) -> crate::prelude::CResult<Self> {
423 use crate::errors::ClientError;
424
425 let status_code = self.status().as_u16();
426 if status_code >= 400 {
427 let ctype = self
428 .headers()
429 .get(reqwest::header::CONTENT_TYPE)
430 .and_then(|v| v.to_str().ok())
431 .and_then(|v| v.split(';').next())
432 .map(|v| v.to_string());
433
434 if ctype.as_ref().is_some_and(|ct| ct.as_str().eq("application/json")) {
435 let err_json = self
436 .json::<crate::errors::ErrorResponse>()
437 .await
438 .map_err(|_| ClientError::from_str(crate::errors::public_msg_from(&Some(status_code))))?;
439 return Err(ClientError::from_str(err_json.err));
440 }
441
442 Err(ClientError::from_str(crate::errors::public_msg_from(&Some(
443 status_code,
444 ))))
445 } else {
446 Ok(self)
447 }
448 }
449}
450
451#[cfg(all(feature = "reqwest", feature = "mresult"))]
453#[allow(async_fn_in_trait)]
454pub trait RedirectServerError
455where
456 Self: Sized,
457{
458 async fn redirect_server_error(self) -> crate::prelude::MResult<Self>;
460}
461
462#[cfg(all(feature = "reqwest", feature = "mresult"))]
463impl RedirectServerError for reqwest::Response {
464 async fn redirect_server_error(self) -> crate::prelude::MResult<Self> {
465 use crate::errors::ServerError;
466
467 let status_code = self.status();
468 if status_code.as_u16() >= 400 {
469 let ctype = self
470 .headers()
471 .get(reqwest::header::CONTENT_TYPE)
472 .and_then(|v| v.to_str().ok())
473 .and_then(|v| v.split(';').next())
474 .map(|v| v.to_string());
475
476 if ctype.as_ref().is_some_and(|ct| ct.as_str().eq("application/json")) {
477 let err_json = self.json::<crate::errors::ErrorResponse>().await.map_err(|e| {
478 ServerError::from_private(e)
479 .with_public(crate::errors::public_msg_from(&Some(status_code.as_u16())))
480 .with_code(status_code)
481 })?;
482 return Err(ServerError::from_public(err_json.err).with_code(status_code));
483 }
484
485 Err(ServerError::from_public(crate::errors::public_msg_from(&Some(status_code.as_u16()))).with_code(status_code))
486 } else {
487 Ok(self)
488 }
489 }
490}