Files
google-apis-rs/google-apis-common/src/serde.rs
philippeitis 8cc2707563 Fix cargo check w.r.t. FieldMask
The serde traits are now directly implemented for FieldMask - this helps address potential serde issues with wrapper types, and simplifies the serde process somewhat.
2022-10-08 19:59:23 -07:00

331 lines
10 KiB
Rust

pub mod duration {
use std::fmt::Formatter;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serializer};
use chrono::Duration;
const MAX_SECONDS: i64 = 315576000000i64;
#[derive(Debug)]
enum ParseDurationError {
MissingSecondSuffix,
NanosTooSmall,
ParseIntError(std::num::ParseIntError),
SecondOverflow { seconds: i64, max_seconds: i64 },
SecondUnderflow { seconds: i64, min_seconds: i64 },
}
impl From<std::num::ParseIntError> for ParseDurationError {
fn from(pie: std::num::ParseIntError) -> Self {
ParseDurationError::ParseIntError(pie)
}
}
impl std::fmt::Display for ParseDurationError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ParseDurationError::MissingSecondSuffix => write!(f, "'s' suffix was not present"),
ParseDurationError::NanosTooSmall => {
write!(f, "more than 9 digits of second precision required")
}
ParseDurationError::ParseIntError(pie) => write!(f, "{:?}", pie),
ParseDurationError::SecondOverflow {
seconds,
max_seconds,
} => write!(
f,
"seconds overflow (got {}, maximum seconds possible {})",
seconds, max_seconds
),
ParseDurationError::SecondUnderflow {
seconds,
min_seconds,
} => write!(
f,
"seconds underflow (got {}, minimum seconds possible {})",
seconds, min_seconds
),
}
}
}
impl std::error::Error for ParseDurationError {}
fn parse_duration(s: &str) -> Result<Duration, ParseDurationError> {
// TODO: Test strings like -.s, -0.0s
let value = match s.strip_suffix('s') {
None => return Err(ParseDurationError::MissingSecondSuffix),
Some(v) => v,
};
let (seconds, nanoseconds) = if let Some((seconds, nanos)) = value.split_once('.') {
let is_neg = seconds.starts_with("-");
let seconds = i64::from_str(seconds)?;
let nano_magnitude = nanos.chars().filter(|c| c.is_digit(10)).count() as u32;
if nano_magnitude > 9 {
// not enough precision to model the remaining digits
return Err(ParseDurationError::NanosTooSmall);
}
// u32::from_str prevents negative nanos (eg '0.-12s) -> lossless conversion to i32
// 10_u32.pow(...) scales number to appropriate # of nanoseconds
let nanos = u32::from_str(nanos)? as i32;
let mut nanos = nanos * 10_i32.pow(9 - nano_magnitude);
if is_neg {
nanos = -nanos;
}
(seconds, nanos)
} else {
(i64::from_str(value)?, 0)
};
if seconds >= MAX_SECONDS {
Err(ParseDurationError::SecondOverflow {
seconds,
max_seconds: MAX_SECONDS,
})
} else if seconds <= -MAX_SECONDS {
Err(ParseDurationError::SecondUnderflow {
seconds,
min_seconds: -MAX_SECONDS,
})
} else {
Ok(Duration::seconds(seconds) + Duration::nanoseconds(nanoseconds.into()))
}
}
pub fn serialize<S>(x: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match x {
None => s.serialize_none(),
Some(x) => {
let seconds = x.num_seconds();
let nanoseconds = (*x - Duration::seconds(seconds))
.num_nanoseconds()
.expect("absolute number of nanoseconds is less than 1 billion")
as i32;
if nanoseconds != 0 {
if seconds == 0 && nanoseconds.is_negative() {
s.serialize_str(&format!("-0.{:0>9}s", nanoseconds.abs()))
} else {
s.serialize_str(&format!("{}.{:0>9}s", seconds, nanoseconds.abs()))
}
} else {
s.serialize_str(&format!("{}s", seconds))
}
}
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<&str> = Deserialize::deserialize(deserializer)?;
s.map(parse_duration)
.transpose()
.map_err(serde::de::Error::custom)
}
}
pub mod urlsafe_base64 {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(x: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match x {
None => s.serialize_none(),
Some(x) => s.serialize_some(&base64::encode_config(x, base64::URL_SAFE)),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<&str> = Deserialize::deserialize(deserializer)?;
s.map(|s| base64::decode_config(s, base64::URL_SAFE))
.transpose()
.map_err(serde::de::Error::custom)
}
}
pub mod str_like {
/// Implementation based on `https://chromium.googlesource.com/infra/luci/luci-go/+/23ea7a05c6a5/common/proto/fieldmasks.go#184`
use serde::{Deserialize, Deserializer, Serializer};
use std::str::FromStr;
pub fn serialize<S, T>(x: &Option<T>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: std::fmt::Display,
{
match x {
None => s.serialize_none(),
Some(num) => s.serialize_some(num.to_string().as_str()),
}
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
let s: Option<&str> = Deserialize::deserialize(deserializer)?;
s.map(T::from_str)
.transpose()
.map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod test {
use super::{duration, str_like, urlsafe_base64};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct DurationWrapper {
#[serde(default, with = "duration")]
duration: Option<chrono::Duration>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Base64Wrapper {
#[serde(default, with = "urlsafe_base64")]
bytes: Option<Vec<u8>>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct I64Wrapper {
#[serde(default, with = "str_like")]
num: Option<i64>,
}
#[test]
fn test_duration_de_success_cases() {
let durations = [
("-0.2s", -200_000_000),
("0.000000001s", 1),
("999.999999999s", 999_999_999_999),
("129s", 129_000_000_000),
("0.123456789s", 123_456_789),
];
for (repr, nanos) in durations.into_iter() {
let wrapper: DurationWrapper =
serde_json::from_str(&format!("{{\"duration\": \"{}\"}}", repr)).unwrap();
assert_eq!(
Some(nanos),
wrapper.duration.unwrap().num_nanoseconds(),
"parsed \"{}\" expecting Duration with {}ns",
repr,
nanos
);
}
}
#[test]
fn test_duration_de_failure_cases() {
let durations = ["1.-3s", "1.1111111111s", "1.2"];
for repr in durations.into_iter() {
assert!(
serde_json::from_str::<DurationWrapper>(&format!("{{\"duration\": \"{}\"}}", repr))
.is_err(),
"parsed \"{}\" expecting err",
repr
);
}
}
#[test]
fn test_duration_ser_success_cases() {
let durations = [
-200_000_000,
1,
999_999_999_999,
129_000_000_000,
123_456_789,
];
for nanos in durations.into_iter() {
let wrapper = DurationWrapper {
duration: Some(chrono::Duration::nanoseconds(nanos)),
};
let s = serde_json::to_string(&wrapper);
assert!(s.is_ok(), "Could not serialize {}ns", nanos);
let s = s.unwrap();
assert_eq!(
wrapper,
serde_json::from_str(&s).unwrap(),
"round trip should return same duration"
);
}
}
#[test]
fn urlsafe_base64_de_success_cases() {
let wrapper: Base64Wrapper =
serde_json::from_str(r#"{"bytes": "aGVsbG8gd29ybGQ="}"#).unwrap();
assert_eq!(
Some(b"hello world".as_slice()),
wrapper.bytes.as_ref().map(Vec::as_slice)
);
}
#[test]
fn urlsafe_base64_de_failure_cases() {
assert!(serde_json::from_str::<Base64Wrapper>(r#"{"bytes": "aGVsbG8gd29ybG+Q"}"#).is_err());
}
#[test]
fn urlsafe_base64_roundtrip() {
let wrapper = Base64Wrapper {
bytes: Some(b"Hello world!".to_vec()),
};
let s = serde_json::to_string(&wrapper).expect("serialization of bytes infallible");
assert_eq!(wrapper, serde_json::from_str::<Base64Wrapper>(&s).unwrap());
}
#[test]
fn num_roundtrip() {
let wrapper = I64Wrapper {
num: Some(i64::MAX),
};
let json_repr = &serde_json::to_string(&wrapper);
assert!(json_repr.is_ok(), "serialization should succeed");
assert_eq!(
wrapper,
serde_json::from_str(&format!("{{\"num\": \"{}\"}}", i64::MAX)).unwrap()
);
assert_eq!(
wrapper,
serde_json::from_str(json_repr.as_ref().unwrap()).unwrap(),
"round trip should succeed"
);
}
#[test]
fn test_empty_wrapper() {
assert_eq!(
DurationWrapper { duration: None },
serde_json::from_str("{}").unwrap()
);
assert_eq!(
Base64Wrapper { bytes: None },
serde_json::from_str("{}").unwrap()
);
assert_eq!(
I64Wrapper { num: None },
serde_json::from_str("{}").unwrap()
);
}
}