Skip to content

Commit

Permalink
feat: Replace serde_json with sonic_rs for poem crate (#819)
Browse files Browse the repository at this point in the history
* replace serde_json with sonic_rs for poem crate

* add sonic-rs feature flag

* add feature flag sonic-rs for `poem-openapi` crate

* fix

* update README.md

* update docs
  • Loading branch information
Mr-Leshiy authored Sep 7, 2024
1 parent b2a307e commit 6d08604
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 32 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ quote = "1.0.9"
syn = { version = "2.0" }
tokio = "1.39.1"
serde_json = "1.0.68"
sonic-rs = "0.3.5"
serde = { version = "1.0.130", features = ["derive"] }
thiserror = "1.0.30"
regex = "1.5.5"
Expand Down
1 change: 1 addition & 0 deletions poem-openapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ hostname = ["hostname-validator"]
static-files = ["poem/static-files"]
websocket = ["poem/websocket"]
geo = ["dep:geo-types", "dep:geojson"]
sonic-rs = ["poem/sonic-rs"]

[dependencies]
poem-openapi-derive.workspace = true
Expand Down
1 change: 1 addition & 0 deletions poem-openapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ To avoid compiling unused dependencies, Poem gates certain features, some of whi
| prost-wkt-types | Integrate with the [`prost-wkt-types` crate](https://crates.io/crates/prost-wkt-types) |
| static-files | Support for static file response |
| websocket | Support for websocket |
|sonic-rs | Uses [`sonic-rs`](https://github.com/cloudwego/sonic-rs) instead of `serde_json`. Pls, checkout `sonic-rs` requirements to properly enable `sonic-rs` capabilities |

## Safety

Expand Down
1 change: 1 addition & 0 deletions poem-openapi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
//! | prost-wkt-types | Integrate with the [`prost-wkt-types` crate](https://crates.io/crates/prost-wkt-types) |
//! | static-files | Support for static file response |
//! | websocket | Support for websocket |
//! |sonic-rs | Uses [`sonic-rs`](https://github.com/cloudwego/sonic-rs) instead of `serde_json`. Pls, checkout `sonic-rs` requirements to properly enable `sonic-rs` capabilities |

#![doc(html_favicon_url = "https://raw.githubusercontent.com/poem-web/poem/master/favicon.ico")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/poem-web/poem/master/logo.png")]
Expand Down
2 changes: 2 additions & 0 deletions poem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ embed = ["rust-embed", "hex", "mime_guess"]
xml = ["quick-xml"]
yaml = ["serde_yaml"]
requestid = ["dep:uuid"]
sonic-rs = ["dep:sonic-rs"]

[dependencies]
poem-derive.workspace = true
Expand All @@ -80,6 +81,7 @@ http-body-util = "0.1.0"
tokio = { workspace = true, features = ["sync", "time", "macros", "net"] }
tokio-util = { version = "0.7.0", features = ["io"] }
serde.workspace = true
sonic-rs = { workspace = true, optional = true }
serde_json.workspace = true
serde_urlencoded.workspace = true
parking_lot = "0.12.0"
Expand Down
2 changes: 1 addition & 1 deletion poem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ which are disabled by default:
| xml | Integrate with [`quick-xml`](https://crates.io/crates/quick-xml) crate. |
| yaml | Integrate with [`serde-yaml`](https://crates.io/crates/serde-yaml) crate. |
|requestid |Associates an unique ID with each incoming request |

|sonic-rs | Uses [`sonic-rs`](https://github.com/cloudwego/sonic-rs) instead of `serde_json`. Pls, checkout `sonic-rs` requirements to properly enable `sonic-rs` capabilities |
## Safety

This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% Safe Rust.
Expand Down
15 changes: 14 additions & 1 deletion poem/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,16 @@ impl Body {
}

/// Create a body object from JSON.
#[cfg(not(feature = "sonic-rc"))]
pub fn from_json(body: impl Serialize) -> serde_json::Result<Self> {
Ok(serde_json::to_vec(&body)?.into())
}

#[cfg(feature = "sonic-rc")]
pub fn from_json(body: impl Serialize) -> sonic_rs::Result<Self> {
Ok(sonic_rs::to_vec(&body)?.into())
}

/// Create an empty body.
#[inline]
pub fn empty() -> Self {
Expand Down Expand Up @@ -234,7 +240,14 @@ impl Body {
/// - [`ReadBodyError`]
/// - [`ParseJsonError`]
pub async fn into_json<T: DeserializeOwned>(self) -> Result<T> {
Ok(serde_json::from_slice(&self.into_vec().await?).map_err(ParseJsonError::Parse)?)
#[cfg(not(feature = "sonic-rs"))]
{
Ok(serde_json::from_slice(&self.into_vec().await?).map_err(ParseJsonError::Parse)?)
}
#[cfg(feature = "sonic-rs")]
{
Ok(sonic_rs::from_slice(&self.into_vec().await?).map_err(ParseJsonError::Parse)?)
}
}

/// Consumes this body object and parse it as `T`.
Expand Down
12 changes: 12 additions & 0 deletions poem/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,13 @@ pub enum ParseCookieError {

/// Cookie value is illegal.
#[error("cookie is illegal: {0}")]
#[cfg(not(feature = "sonic-rs"))]
ParseJsonValue(#[from] serde_json::Error),

/// Cookie value is illegal.
#[error("cookie is illegal: {0}")]
#[cfg(feature = "sonic-rs")]
ParseJsonValue(#[from] sonic_rs::Error),
}

#[cfg(feature = "cookie")]
Expand Down Expand Up @@ -749,7 +755,13 @@ pub enum ParseJsonError {

/// Url decode error.
#[error("parse error: {0}")]
#[cfg(not(feature = "sonic-rs"))]
Parse(#[from] serde_json::Error),

/// Url decode error.
#[error("parse error: {0}")]
#[cfg(feature = "sonic-rs")]
Parse(#[from] sonic_rs::Error),
}

impl ResponseError for ParseJsonError {
Expand Down
1 change: 1 addition & 0 deletions poem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
//! | embed | Integrate with [`rust-embed`](https://crates.io/crates/rust-embed) crate. |
//! | xml | Integrate with [`quick-xml`](https://crates.io/crates/quick-xml) crate. |
//! | yaml | Integrate with [`serde-yaml`](https://crates.io/crates/serde-yaml) crate. |
//! |sonic-rs | Uses [`sonic-rs`](https://github.com/cloudwego/sonic-rs) instead of `serde_json`. Pls, checkout `sonic-rs` requirements to properly enable `sonic-rs` capabilities |

#![doc(html_favicon_url = "https://raw.githubusercontent.com/poem-web/poem/master/favicon.ico")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/poem-web/poem/master/logo.png")]
Expand Down
36 changes: 31 additions & 5 deletions poem/src/listener/acme/jose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ impl<'a> Protected<'a> {
nonce,
url,
};
#[cfg(not(feature = "sonic-rs"))]
let protected = serde_json::to_vec(&protected).map_err(|err| {
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
})?;
#[cfg(feature = "sonic-rs")]
let protected = sonic_rs::to_vec(&protected).map_err(|err| {
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
})?;
Ok(URL_SAFE_NO_PAD.encode(protected))
}
}
Expand Down Expand Up @@ -78,9 +83,14 @@ impl Jwk {
x: &self.x,
y: &self.y,
};
#[cfg(not(feature = "sonic-rs"))]
let json = serde_json::to_vec(&jwk_thumb).map_err(|err| {
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
})?;
#[cfg(feature = "sonic-rs")]
let json = sonic_rs::to_vec(&jwk_thumb).map_err(|err| {
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
})?;
let hash = sha256(json);
Ok(URL_SAFE_NO_PAD.encode(hash))
}
Expand Down Expand Up @@ -111,9 +121,17 @@ pub(crate) async fn request(
};
let protected = Protected::base64(jwk, kid, nonce, uri)?;
let payload = match payload {
Some(payload) => serde_json::to_vec(&payload).map_err(|err| {
IoError::new(ErrorKind::Other, format!("failed to encode payload: {err}"))
})?,
Some(payload) => {
#[cfg(not(feature = "sonic-rs"))]
let res = serde_json::to_vec(&payload).map_err(|err| {
IoError::new(ErrorKind::Other, format!("failed to encode payload: {err}"))
})?;
#[cfg(feature = "sonic-rs")]
let res = sonic_rs::to_vec(&payload).map_err(|err| {
IoError::new(ErrorKind::Other, format!("failed to encode payload: {err}"))
})?;
res
}
None => Vec::new(),
};
let payload = URL_SAFE_NO_PAD.encode(payload);
Expand Down Expand Up @@ -166,8 +184,16 @@ where
.text()
.await
.map_err(|_| IoError::new(ErrorKind::Other, "failed to read response"))?;
serde_json::from_str(&data)
.map_err(|err| IoError::new(ErrorKind::Other, format!("bad response: {err}")))
#[cfg(not(feature = "sonic-rs"))]
{
serde_json::from_str(&data)
.map_err(|err| IoError::new(ErrorKind::Other, format!("bad response: {err}")))
}
#[cfg(feature = "sonic-rs")]
{
sonic_rs::from_str(&data)
.map_err(|err| IoError::new(ErrorKind::Other, format!("bad response: {err}")))
}
}

pub(crate) fn key_authorization(key: &KeyPair, token: &str) -> IoResult<String> {
Expand Down
15 changes: 12 additions & 3 deletions poem/src/middleware/tokio_metrics_mw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,18 @@ impl TokioMetrics {
pub fn exporter(&self) -> impl Endpoint {
let metrics = self.metrics.clone();
RouteMethod::new().get(make_sync(move |_| {
serde_json::to_string(&*metrics.lock())
.unwrap()
.with_content_type("application/json")
#[cfg(not(feature = "sonic-rs"))]
{
serde_json::to_string(&*metrics.lock())
.unwrap()
.with_content_type("application/json")
}
#[cfg(feature = "sonic-rs")]
{
sonic_rs::to_string(&*metrics.lock())
.unwrap()
.with_content_type("application/json")
}
}))
}
}
Expand Down
25 changes: 20 additions & 5 deletions poem/src/session/cookie_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,16 @@ impl<E: Endpoint> Endpoint for CookieSessionEndpoint<E> {
let session = self
.config
.get_cookie_value(&cookie_jar)
.and_then(|value| serde_json::from_str::<BTreeMap<String, Value>>(&value).ok())
.and_then(|value| {
#[cfg(not(feature = "sonic-rs"))]
{
serde_json::from_str::<BTreeMap<String, Value>>(&value).ok()
}
#[cfg(feature = "sonic-rs")]
{
sonic_rs::from_str::<BTreeMap<String, Value>>(&value).ok()
}
})
.map(Session::new)
.unwrap_or_default();

Expand All @@ -59,10 +68,16 @@ impl<E: Endpoint> Endpoint for CookieSessionEndpoint<E> {

match session.status() {
SessionStatus::Changed | SessionStatus::Renewed => {
self.config.set_cookie_value(
&cookie_jar,
&serde_json::to_string(&session.entries()).unwrap_or_default(),
);
self.config.set_cookie_value(&cookie_jar, {
#[cfg(not(feature = "sonic-rs"))]
{
&serde_json::to_string(&session.entries()).unwrap_or_default()
}
#[cfg(feature = "sonic-rs")]
{
&sonic_rs::to_string(&session.entries()).unwrap_or_default()
}
});
}
SessionStatus::Purged => {
self.config.remove_cookie(&cookie_jar);
Expand Down
17 changes: 13 additions & 4 deletions poem/src/session/redis_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ impl<T: ConnectionLike + Clone + Sync + Send> SessionStorage for RedisStorage<T>
.map_err(RedisSessionError::Redis)?;

match data {
Some(data) => match serde_json::from_str::<BTreeMap<String, Value>>(&data) {
Ok(entries) => Ok(Some(entries)),
Err(_) => Ok(None),
},
Some(data) => {
#[cfg(not(feature = "sonic-rs"))]
let map = serde_json::from_str::<BTreeMap<String, Value>>(&data);
#[cfg(feature = "sonic-rs")]
let map = sonic_rs::from_str::<BTreeMap<String, Value>>(&data);
match map {
Ok(entries) => Ok(Some(entries)),
Err(_) => Ok(None),
}
}
None => Ok(None),
}
}
Expand All @@ -47,7 +53,10 @@ impl<T: ConnectionLike + Clone + Sync + Send> SessionStorage for RedisStorage<T>
entries: &'a BTreeMap<String, Value>,
expires: Option<Duration>,
) -> Result<()> {
#[cfg(not(feature = "sonic-rs"))]
let value = serde_json::to_string(entries).unwrap_or_default();
#[cfg(feature = "sonic-rs")]
let value = sonic_rs::to_string(entries).unwrap_or_default();
let cmd = match expires {
Some(expires) => Cmd::set_ex(session_id, value, expires.as_secs()),
None => Cmd::set(session_id, value),
Expand Down
34 changes: 28 additions & 6 deletions poem/src/web/cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,20 @@ impl Display for Cookie {
impl Cookie {
/// Creates a new Cookie with the given `name` and serialized `value`.
pub fn new(name: impl Into<String>, value: impl Serialize) -> Self {
Self(libcookie::Cookie::new(
name.into(),
serde_json::to_string(&value).unwrap_or_default(),
))
#[cfg(not(feature = "sonic-rs"))]
{
Self(libcookie::Cookie::new(
name.into(),
serde_json::to_string(&value).unwrap_or_default(),
))
}
#[cfg(feature = "sonic-rs")]
{
Self(libcookie::Cookie::new(
name.into(),
sonic_rs::to_string(&value).unwrap_or_default(),
))
}
}

/// Creates a new Cookie with the given `name` and `value`.
Expand Down Expand Up @@ -275,7 +285,12 @@ impl Cookie {

/// Sets the value of `self` to the serialized `value`.
pub fn set_value(&mut self, value: impl Serialize) {
if let Ok(value) = serde_json::to_string(&value) {
#[cfg(not(feature = "sonic-rs"))]
let json_string = serde_json::to_string(&value);
#[cfg(feature = "sonic-rs")]
let json_string = sonic_rs::to_string(&value);

if let Ok(value) = json_string {
self.0.set_value(value);
}
}
Expand All @@ -287,7 +302,14 @@ impl Cookie {

/// Returns the value of `self` to the deserialized `value`.
pub fn value<'de, T: Deserialize<'de>>(&'de self) -> Result<T, ParseCookieError> {
serde_json::from_str(self.0.value()).map_err(ParseCookieError::ParseJsonValue)
#[cfg(not(feature = "sonic-rs"))]
{
serde_json::from_str(self.0.value()).map_err(ParseCookieError::ParseJsonValue)
}
#[cfg(feature = "sonic-rs")]
{
sonic_rs::from_str(self.0.value()).map_err(ParseCookieError::ParseJsonValue)
}
}
}

Expand Down
Loading

0 comments on commit 6d08604

Please sign in to comment.