impulse_utils/
requests.rs

1//! Implementation of utilities for working with MessagePack with requests in `salvo` and `reqwest`.
2
3#[cfg(feature = "salvo")]
4#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
5use crate::prelude::*;
6
7#[cfg(feature = "salvo")]
8#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
9use serde::Deserialize;
10
11#[cfg(feature = "salvo")]
12#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
13use salvo::Request;
14
15/// MessagePack parser from `salvo::Request`.
16#[cfg(feature = "salvo")]
17#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
18#[allow(async_fn_in_trait)]
19pub trait MsgPackParser {
20  /// Parses `msgpack` body.
21  async fn parse_msgpack<'de, T: Deserialize<'de>>(&'de mut self) -> MResult<T>;
22  /// Parses `msgpack` body with size limit.
23  async fn parse_msgpack_with_max_size<'de, T: Deserialize<'de>>(&'de mut self, max_size: usize) -> MResult<T>;
24}
25
26#[cfg(feature = "salvo")]
27#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
28impl MsgPackParser for Request {
29  /// Parse MessagePack body as type `T` from request with default max size limit.
30  #[inline]
31  async fn parse_msgpack<'de, T: Deserialize<'de>>(&'de mut self) -> MResult<T> {
32    self
33      .parse_msgpack_with_max_size(salvo::http::request::global_secure_max_size())
34      .await
35  }
36
37  /// Parse MessagePack body as type `T` from request with max size limit.
38  #[inline]
39  async fn parse_msgpack_with_max_size<'de, T: Deserialize<'de>>(&'de mut self, max_size: usize) -> MResult<T> {
40    let ctype = self.content_type();
41    if ctype.is_some_and(|ct| ct.subtype() == salvo::http::mime::MSGPACK) {
42      let payload = self.payload_with_max_size(max_size).await.map_err(|e| {
43        ServerError::from_private(e)
44          .with_public("Payload parse error")
45          .with_400()
46      })?;
47      let payload = if payload.is_empty() {
48        "null".as_bytes()
49      } else {
50        payload.as_ref()
51      };
52      tracing::trace!("Got payload: {:?}", payload);
53      rmp_serde::from_slice::<T>(payload).map_err(|e| {
54        ServerError::from_private(e)
55          .with_public("Payload parse error")
56          .with_400()
57      })
58    } else {
59      Err(ServerError::from_public("Bad content type, must be `application/msgpack`.").with_400())
60    }
61  }
62}
63
64/// JSON parser from `salvo::Request` by `sonic-rs`.
65#[cfg(feature = "salvo")]
66#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
67#[allow(async_fn_in_trait)]
68pub trait SimdJsonParser {
69  /// Parses `json` body.
70  ///
71  /// To use actual SIMD parsing, compile your backend with option `-C target-cpu=native`.
72  async fn parse_json_simd<'de, T: Deserialize<'de>>(&'de mut self) -> MResult<T>;
73  /// Parses `json` body with size limit.
74  ///
75  /// To use actual SIMD parsing, compile your backend with option `-C target-cpu=native`.
76  async fn parse_json_simd_with_max_size<'de, T: Deserialize<'de>>(&'de mut self, max_size: usize) -> MResult<T>;
77}
78
79#[cfg(feature = "salvo")]
80#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
81impl SimdJsonParser for Request {
82  #[inline]
83  async fn parse_json_simd<'de, T: Deserialize<'de>>(&'de mut self) -> MResult<T> {
84    self
85      .parse_json_simd_with_max_size(salvo::http::request::global_secure_max_size())
86      .await
87  }
88
89  #[inline]
90  async fn parse_json_simd_with_max_size<'de, T: Deserialize<'de>>(&'de mut self, max_size: usize) -> MResult<T> {
91    let ctype = self.content_type();
92    if ctype.is_some_and(|ct| ct.subtype() == salvo::http::mime::JSON) {
93      let payload = self.payload_with_max_size(max_size).await.map_err(|e| {
94        ServerError::from_private(e)
95          .with_public("Payload parse error")
96          .with_400()
97      })?;
98      let payload = if payload.is_empty() {
99        "null".as_bytes()
100      } else {
101        payload.as_ref()
102      };
103      tracing::trace!("Got payload: {:?}", payload);
104      sonic_rs::from_slice::<T>(payload).map_err(|e| {
105        ServerError::from_private(e)
106          .with_public("Payload parse error")
107          .with_400()
108      })
109    } else {
110      Err(ServerError::from_public("Bad content type, must be `application/json`.").with_400())
111    }
112  }
113}
114
115/// MessagePack writer to `reqwest::RequestBuilder`.
116#[cfg(all(feature = "reqwest", feature = "cresult"))]
117pub trait MsgPackRequest {
118  /// Writes `T` as MsgPack binary data into RequestBuilder.
119  fn msgpack<T: serde::Serialize + ?Sized>(self, msgpack: &T) -> crate::prelude::CResult<Self>
120  where
121    Self: Sized;
122}
123
124#[cfg(all(feature = "reqwest", feature = "cresult"))]
125impl MsgPackRequest for reqwest::RequestBuilder {
126  fn msgpack<T: serde::Serialize + ?Sized>(self, msgpack: &T) -> crate::prelude::CResult<Self> {
127    use crate::errors::ClientError;
128
129    Ok(
130      self
131        .header(reqwest::header::CONTENT_TYPE, "application/msgpack")
132        .body(rmp_serde::to_vec(msgpack).map_err(|e| ClientError::from_str(format!("Can't serialize body: {e:?}")))?),
133    )
134  }
135}