Skip to content

Commit 8aadb28

Browse files
authored
Add serde helper for string format (Azure#2626)
* Add serde helper for string format The common case is to use this for numeric types that are to be encoded/decoded in string format. * improve changelog * silence clippy * move content to fmt module
1 parent b67b3fa commit 8aadb28

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

sdk/typespec/typespec_client_core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Features Added
66

77
- Added `#[safe]` attribute helper for `SafeDebug` derive macro to show or hide types and members as appropriate.
8+
- Added module `fmt::as_string` which is used to (de)serialize types in string format.
89

910
### Breaking Changes
1011

sdk/typespec/typespec_client_core/src/fmt.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,120 @@ fn test_to_ascii_lowercase() {
5656
Cow::Owned(expected) if expected == "hello, 🌎!"
5757
));
5858
}
59+
60+
/// Contains serde functions for types that are sent and received as strings but aren't surfaced as strings.
61+
pub mod as_string {
62+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
63+
64+
/// Deserializes a string into a T. T must implement [`std::str::FromStr`].
65+
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
66+
where
67+
D: Deserializer<'de>,
68+
T: std::str::FromStr,
69+
<T as std::str::FromStr>::Err: std::fmt::Display,
70+
{
71+
let to_deserialize = <Option<String>>::deserialize(deserializer)?;
72+
match to_deserialize {
73+
Some(to_deserialize) => Ok(Some(
74+
T::from_str(&to_deserialize).map_err(serde::de::Error::custom)?,
75+
)),
76+
None => Ok(None),
77+
}
78+
}
79+
80+
/// Serializes T in string format. T must implement [`std::string::ToString`].
81+
pub fn serialize<S, T>(to_serialize: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
82+
where
83+
S: Serializer,
84+
T: std::string::ToString,
85+
{
86+
match to_serialize {
87+
Some(to_serialize) => String::serialize(&to_serialize.to_string(), serializer),
88+
None => serializer.serialize_none(),
89+
}
90+
}
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use crate::json::{from_json, to_json};
96+
use serde::{Deserialize, Serialize};
97+
use serde_json::Number;
98+
99+
#[derive(Default, Deserialize, Serialize)]
100+
struct TestType {
101+
#[serde(
102+
default,
103+
skip_serializing_if = "Option::is_none",
104+
with = "super::as_string"
105+
)]
106+
a_bool: Option<bool>,
107+
108+
#[serde(
109+
default,
110+
skip_serializing_if = "Option::is_none",
111+
with = "super::as_string"
112+
)]
113+
json_number: Option<serde_json::Number>,
114+
115+
#[serde(
116+
default,
117+
skip_serializing_if = "Option::is_none",
118+
with = "super::as_string"
119+
)]
120+
some_float: Option<f64>,
121+
122+
#[serde(
123+
default,
124+
skip_serializing_if = "Option::is_none",
125+
with = "super::as_string"
126+
)]
127+
some_int: Option<i32>,
128+
}
129+
130+
#[test]
131+
fn test_deserialize_none() -> crate::Result<()> {
132+
let json_body = r#"{}"#;
133+
let test_type: TestType = from_json(json_body)?;
134+
assert!(test_type.a_bool.is_none());
135+
assert!(test_type.json_number.is_none());
136+
assert!(test_type.some_float.is_none());
137+
assert!(test_type.some_int.is_none());
138+
Ok(())
139+
}
140+
141+
#[test]
142+
fn test_deserialize_all() -> crate::Result<()> {
143+
let json_body = r#"{"a_bool":"true","json_number":"123456789","some_float":"9.87654321","some_int":"42"}"#;
144+
let test_type: TestType = from_json(json_body)?;
145+
assert_eq!(test_type.a_bool, Some(true));
146+
assert_eq!(test_type.json_number, Number::from_i128(123456789));
147+
assert_eq!(test_type.some_float, Some(9.87654321));
148+
assert_eq!(test_type.some_int, Some(42));
149+
Ok(())
150+
}
151+
152+
#[test]
153+
fn test_serialize_none() -> crate::Result<()> {
154+
let test_type = TestType::default();
155+
let json_body = to_json(&test_type)?;
156+
assert_eq!(json_body, r#"{}"#);
157+
Ok(())
158+
}
159+
160+
#[test]
161+
fn test_serialize_all() -> crate::Result<()> {
162+
let test_type = TestType {
163+
a_bool: Some(true),
164+
json_number: Number::from_i128(123456789),
165+
some_float: Some(9.87654321),
166+
some_int: Some(42),
167+
};
168+
let json_body = to_json(&test_type)?;
169+
assert_eq!(
170+
json_body,
171+
r#"{"a_bool":"true","json_number":"123456789","some_float":"9.87654321","some_int":"42"}"#
172+
);
173+
Ok(())
174+
}
175+
}

0 commit comments

Comments
 (0)