1pub mod duration {
2 use std::fmt::Formatter;
3 use std::str::FromStr;
4
5 use serde::{Deserialize, Deserializer};
6 use serde_with::{DeserializeAs, SerializeAs};
7
8 use chrono::Duration;
9
10 const MAX_SECONDS: i64 = 315576000000i64;
11
12 #[derive(Debug)]
13 enum ParseDurationError {
14 MissingSecondSuffix,
15 NanosTooSmall,
16 ParseIntError(std::num::ParseIntError),
17 SecondOverflow { seconds: i64, max_seconds: i64 },
18 SecondUnderflow { seconds: i64, min_seconds: i64 },
19 DurationSeconds { seconds: i64 },
20 }
21
22 impl From<std::num::ParseIntError> for ParseDurationError {
23 fn from(pie: std::num::ParseIntError) -> Self {
24 ParseDurationError::ParseIntError(pie)
25 }
26 }
27
28 impl std::fmt::Display for ParseDurationError {
29 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30 match self {
31 ParseDurationError::MissingSecondSuffix => write!(f, "'s' suffix was not present"),
32 ParseDurationError::NanosTooSmall => {
33 write!(f, "more than 9 digits of second precision required")
34 }
35 ParseDurationError::ParseIntError(pie) => write!(f, "{pie:?}"),
36 ParseDurationError::SecondOverflow {
37 seconds,
38 max_seconds,
39 } => write!(
40 f,
41 "seconds overflow (got {seconds}, maximum seconds possible {max_seconds})"
42 ),
43 ParseDurationError::SecondUnderflow {
44 seconds,
45 min_seconds,
46 } => write!(
47 f,
48 "seconds underflow (got {seconds}, minimum seconds possible {min_seconds})"
49 ),
50 ParseDurationError::DurationSeconds { seconds } => {
51 write!(f, "Could not create a duration from {seconds}")
52 }
53 }
54 }
55 }
56
57 impl std::error::Error for ParseDurationError {}
58
59 fn duration_from_str(s: &str) -> Result<Duration, ParseDurationError> {
60 let value = match s.strip_suffix('s') {
62 None => return Err(ParseDurationError::MissingSecondSuffix),
63 Some(v) => v,
64 };
65
66 let (seconds, nanoseconds) = if let Some((seconds, nanos)) = value.split_once('.') {
67 let is_neg = seconds.starts_with('-');
68 let seconds = i64::from_str(seconds)?;
69 let nano_magnitude = nanos.chars().filter(|c| c.is_ascii_digit()).count() as u32;
70 if nano_magnitude > 9 {
71 return Err(ParseDurationError::NanosTooSmall);
73 }
74
75 let nanos = u32::from_str(nanos)? as i32;
78
79 let mut nanos = nanos * 10_i32.pow(9 - nano_magnitude);
80 if is_neg {
81 nanos = -nanos;
82 }
83 (seconds, nanos)
84 } else {
85 (i64::from_str(value)?, 0)
86 };
87
88 if seconds >= MAX_SECONDS {
89 Err(ParseDurationError::SecondOverflow {
90 seconds,
91 max_seconds: MAX_SECONDS,
92 })
93 } else if seconds <= -MAX_SECONDS {
94 Err(ParseDurationError::SecondUnderflow {
95 seconds,
96 min_seconds: -MAX_SECONDS,
97 })
98 } else {
99 Ok(Duration::try_seconds(seconds)
100 .ok_or(ParseDurationError::DurationSeconds { seconds })?
101 + Duration::nanoseconds(nanoseconds.into()))
102 }
103 }
104
105 pub fn to_string(duration: &Duration) -> String {
106 let seconds = duration.num_seconds();
107 let nanoseconds = (*duration
108 - Duration::try_seconds(seconds).expect("Seconds in bounds to create Duration from"))
109 .num_nanoseconds()
110 .expect("absolute number of nanoseconds is less than 1 billion")
111 as i32;
112 if nanoseconds != 0 {
113 if seconds == 0 && nanoseconds.is_negative() {
114 format!("-0.{:0>9}s", nanoseconds.abs())
115 } else {
116 format!("{}.{:0>9}s", seconds, nanoseconds.abs())
117 }
118 } else {
119 format!("{seconds}s")
120 }
121 }
122
123 pub struct Wrapper;
124
125 impl SerializeAs<Duration> for Wrapper {
126 fn serialize_as<S>(value: &Duration, s: S) -> Result<S::Ok, S::Error>
127 where
128 S: serde::Serializer,
129 {
130 s.serialize_str(&to_string(value))
131 }
132 }
133
134 impl<'de> DeserializeAs<'de, Duration> for Wrapper {
135 fn deserialize_as<D>(deserializer: D) -> Result<Duration, D::Error>
136 where
137 D: Deserializer<'de>,
138 {
139 let s = Deserialize::deserialize(deserializer)?;
140 duration_from_str(s).map_err(serde::de::Error::custom)
141 }
142 }
143}
144
145pub mod standard_base64 {
146 use std::borrow::Cow;
147
148 use base64::Engine as _;
149 use serde::{Deserialize, Deserializer, Serializer};
150 use serde_with::{DeserializeAs, SerializeAs};
151
152 pub struct Wrapper;
153
154 pub fn to_string(bytes: &Vec<u8>) -> String {
155 base64::prelude::BASE64_STANDARD.encode(bytes)
156 }
157
158 impl SerializeAs<Vec<u8>> for Wrapper {
159 fn serialize_as<S>(value: &Vec<u8>, s: S) -> Result<S::Ok, S::Error>
160 where
161 S: Serializer,
162 {
163 s.serialize_str(&to_string(value))
164 }
165 }
166
167 impl<'de> DeserializeAs<'de, Vec<u8>> for Wrapper {
168 fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
169 where
170 D: Deserializer<'de>,
171 {
172 let s: Cow<str> = Deserialize::deserialize(deserializer)?;
173 match base64::prelude::BASE64_STANDARD.decode(s.as_ref()) {
174 Ok(decoded) => Ok(decoded),
175 Err(first_err) => match base64::prelude::BASE64_URL_SAFE.decode(s.as_ref()) {
176 Ok(decoded) => Ok(decoded),
177 Err(_) => Err(serde::de::Error::custom(first_err)),
178 },
179 }
180 }
181 }
182}
183
184pub mod urlsafe_base64 {
185 use std::borrow::Cow;
186
187 use base64::Engine as _;
188 use serde::{Deserialize, Deserializer, Serializer};
189 use serde_with::{DeserializeAs, SerializeAs};
190
191 pub struct Wrapper;
192
193 pub fn to_string(bytes: &Vec<u8>) -> String {
194 base64::prelude::BASE64_URL_SAFE.encode(bytes)
195 }
196
197 impl SerializeAs<Vec<u8>> for Wrapper {
198 fn serialize_as<S>(value: &Vec<u8>, s: S) -> Result<S::Ok, S::Error>
199 where
200 S: Serializer,
201 {
202 s.serialize_str(&to_string(value))
203 }
204 }
205
206 impl<'de> DeserializeAs<'de, Vec<u8>> for Wrapper {
207 fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
208 where
209 D: Deserializer<'de>,
210 {
211 let s: Cow<str> = Deserialize::deserialize(deserializer)?;
212 match base64::prelude::BASE64_URL_SAFE.decode(s.as_ref()) {
213 Ok(decoded) => Ok(decoded),
214 Err(first_err) => match base64::prelude::BASE64_STANDARD.decode(s.as_ref()) {
215 Ok(decoded) => Ok(decoded),
216 Err(_) => Err(serde::de::Error::custom(first_err)),
217 },
218 }
219 }
220 }
221}
222
223pub fn datetime_to_string(datetime: &chrono::DateTime<chrono::offset::Utc>) -> String {
224 datetime.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
225}
226
227#[cfg(test)]
228mod tests {
229 use serde::{Deserialize, Serialize};
230 use serde_with::{serde_as, DisplayFromStr};
231
232 use super::{duration, standard_base64, urlsafe_base64};
233
234 #[serde_as]
235 #[derive(Serialize, Deserialize, Debug, PartialEq)]
236 struct DurationWrapper {
237 #[serde_as(as = "Option<duration::Wrapper>")]
238 duration: Option<chrono::Duration>,
239 }
240
241 #[serde_as]
242 #[derive(Serialize, Deserialize, Debug, PartialEq)]
243 struct Base64URLSafeWrapper {
244 #[serde_as(as = "Option<urlsafe_base64::Wrapper>")]
245 bytes: Option<Vec<u8>>,
246 }
247
248 #[serde_as]
249 #[derive(Serialize, Deserialize, Debug, PartialEq)]
250 struct Base64StandardWrapper {
251 #[serde_as(as = "Option<standard_base64::Wrapper>")]
252 bytes: Option<Vec<u8>>,
253 }
254
255 #[serde_as]
256 #[derive(Serialize, Deserialize, Debug, PartialEq)]
257 struct I64Wrapper {
258 #[serde_as(as = "Option<DisplayFromStr>")]
259 num: Option<i64>,
260 }
261
262 #[test]
263 fn test_duration_de_success_cases() {
264 let durations = [
265 ("-0.2s", -200_000_000),
266 ("0.000000001s", 1),
267 ("999.999999999s", 999_999_999_999),
268 ("129s", 129_000_000_000),
269 ("0.123456789s", 123_456_789),
270 ];
271 for (repr, nanos) in durations.into_iter() {
272 let wrapper: DurationWrapper =
273 serde_json::from_str(&format!("{{\"duration\": \"{repr}\"}}")).unwrap();
274 assert_eq!(
275 Some(nanos),
276 wrapper.duration.unwrap().num_nanoseconds(),
277 "parsed \"{repr}\" expecting Duration with {nanos}ns",
278 );
279 }
280 }
281
282 #[test]
283 fn test_duration_de_failure_cases() {
284 let durations = ["1.-3s", "1.1111111111s", "1.2"];
285 for repr in durations.into_iter() {
286 assert!(
287 serde_json::from_str::<DurationWrapper>(&format!("{{\"duration\": \"{repr}\"}}"))
288 .is_err(),
289 "parsed \"{repr}\" expecting err",
290 );
291 }
292 }
293
294 #[test]
295 fn test_duration_ser_success_cases() {
296 let durations = [
297 -200_000_000,
298 1,
299 999_999_999_999,
300 129_000_000_000,
301 123_456_789,
302 ];
303
304 for nanos in durations.into_iter() {
305 let wrapper = DurationWrapper {
306 duration: Some(chrono::Duration::nanoseconds(nanos)),
307 };
308 let s = serde_json::to_string(&wrapper);
309 assert!(s.is_ok(), "Could not serialize {nanos}ns");
310 let s = s.unwrap();
311 assert_eq!(
312 wrapper,
313 serde_json::from_str(&s).unwrap(),
314 "round trip should return same duration"
315 );
316 }
317 }
318
319 #[test]
320 fn standard_base64_de_success_cases() {
321 let wrapper: Base64StandardWrapper =
322 serde_json::from_str(r#"{"bytes": "cVhabzk6U21uOkN+MylFWFRJMVFLdEh2MShmVHp9"}"#)
323 .unwrap();
324 assert_eq!(
325 Some(b"qXZo9:Smn:C~3)EXTI1QKtHv1(fTz}".as_slice()),
326 wrapper.bytes.as_deref()
327 );
328 }
329
330 #[test]
331 fn standard_base64_de_reader_success_cases() {
332 let standard: Base64StandardWrapper = serde_json::from_reader(
333 r#"{"bytes": "cVhabzk6U21uOkN+MylFWFRJMVFLdEh2MShmVHp9"}"#.as_bytes(),
334 )
335 .unwrap();
336 assert_eq!(
337 Some(b"qXZo9:Smn:C~3)EXTI1QKtHv1(fTz}".as_slice()),
338 standard.bytes.as_deref()
339 );
340 }
341
342 #[test]
343 fn urlsafe_base64_de_success_cases() {
344 let wrapper: Base64URLSafeWrapper =
345 serde_json::from_str(r#"{"bytes": "aGVsbG8gd29ybGQ="}"#).unwrap();
346 assert_eq!(Some(b"hello world".as_slice()), wrapper.bytes.as_deref());
347 }
348
349 #[test]
350 fn urlsafe_base64_de_reader_success_cases() {
351 let wrapper: Base64URLSafeWrapper =
352 serde_json::from_reader(r#"{"bytes": "aGVsbG8gd29ybGQ="}"#.as_bytes()).unwrap();
353 assert_eq!(Some(b"hello world".as_slice()), wrapper.bytes.as_deref());
354 }
355
356 #[test]
357 fn urlsafe_base64_de_standard_success_cases() {
358 let wrapper: Base64URLSafeWrapper = serde_json::from_reader(r#"{"bytes": "REE/P0V+Nz4oIWtH"}"#.as_bytes()).unwrap();
360 assert_eq!(Some(b"DA??E~7>(!kG".as_slice()), wrapper.bytes.as_deref());
361 }
362
363 #[test]
364 fn urlsafe_base64_de_failure_cases() {
365 assert!(
366 serde_json::from_str::<Base64URLSafeWrapper>(r#"{"bytes": "aGVsbG8gd29ybG&Q"}"#)
367 .is_err()
368 );
369 }
370
371 #[test]
372 fn standard_base64_de_urlsafe_success_cases() {
373 let wrapper: Base64URLSafeWrapper = serde_json::from_reader(r#"{"bytes": "REE_P0V-Nz4oIWtH"}"#.as_bytes()).unwrap();
375 assert_eq!(Some(b"DA??E~7>(!kG".as_slice()), wrapper.bytes.as_deref());
376 }
377
378 #[test]
379 fn standard_base64_de_failure_cases() {
380 assert!(serde_json::from_str::<Base64StandardWrapper>(r#"{"bytes": "%"}"#).is_err());
381 }
382
383 #[test]
384 fn urlsafe_base64_roundtrip() {
385 let wrapper = Base64URLSafeWrapper {
386 bytes: Some(b"Hello world!".to_vec()),
387 };
388 let s = serde_json::to_string(&wrapper).expect("serialization of bytes infallible");
389 assert_eq!(
390 wrapper,
391 serde_json::from_str::<Base64URLSafeWrapper>(&s).unwrap()
392 );
393 }
394
395 #[test]
396 fn standard_base64_roundtrip() {
397 let wrapper = Base64StandardWrapper {
398 bytes: Some(b"Hello world!".to_vec()),
399 };
400 let s = serde_json::to_string(&wrapper).expect("serialization of bytes infallible");
401 assert_eq!(
402 wrapper,
403 serde_json::from_str::<Base64StandardWrapper>(&s).unwrap()
404 );
405 }
406
407 #[test]
408 fn num_roundtrip() {
409 let wrapper = I64Wrapper {
410 num: Some(i64::MAX),
411 };
412
413 let json_repr = &serde_json::to_string(&wrapper);
414 assert!(json_repr.is_ok(), "serialization should succeed");
415 assert_eq!(
416 wrapper,
417 serde_json::from_str(&format!("{{\"num\": \"{}\"}}", i64::MAX)).unwrap()
418 );
419 assert_eq!(
420 wrapper,
421 serde_json::from_str(json_repr.as_ref().unwrap()).unwrap(),
422 "round trip should succeed"
423 );
424 }
425
426 #[test]
427 fn test_empty_wrapper() {
428 assert_eq!(
429 DurationWrapper { duration: None },
430 serde_json::from_str("{}").unwrap()
431 );
432 assert_eq!(
433 Base64URLSafeWrapper { bytes: None },
434 serde_json::from_str("{}").unwrap()
435 );
436 assert_eq!(
437 Base64StandardWrapper { bytes: None },
438 serde_json::from_str("{}").unwrap()
439 );
440
441 assert_eq!(
442 I64Wrapper { num: None },
443 serde_json::from_str("{}").unwrap()
444 );
445 }
446}