serde_ext.rs (1696B)
1 #[cfg(feature = "serde")] 2 pub mod epoch_seconds { 3 use serde::{Deserialize, Deserializer, de::Error as DeError}; 4 5 pub fn de<'de, D>(de: D) -> Result<u32, D::Error> 6 where 7 D: Deserializer<'de>, 8 { 9 let v = u64::deserialize(de)?; 10 if v > u32::MAX as u64 { 11 return Err(D::Error::custom( 12 "timestamp must be epoch **seconds**, not ms", 13 )); 14 } 15 Ok(v as u32) 16 } 17 } 18 19 #[cfg(all(test, feature = "serde"))] 20 mod tests { 21 use super::epoch_seconds; 22 #[cfg(not(feature = "std"))] 23 use alloc::format; 24 #[cfg(not(feature = "std"))] 25 use alloc::string::ToString; 26 use serde::Deserialize; 27 #[cfg(feature = "std")] 28 use std::string::ToString; 29 30 #[derive(Debug, Deserialize)] 31 struct EpochSecondsFixture { 32 #[serde(deserialize_with = "epoch_seconds::de")] 33 ts: u32, 34 } 35 36 #[test] 37 fn epoch_seconds_accepts_u32_max() { 38 let fixture: EpochSecondsFixture = 39 serde_json::from_str(&format!(r#"{{"ts":{}}}"#, u32::MAX)).unwrap(); 40 assert_eq!(fixture.ts, u32::MAX); 41 } 42 43 #[test] 44 fn epoch_seconds_rejects_overflow() { 45 let err = serde_json::from_str::<EpochSecondsFixture>(&format!( 46 r#"{{"ts":{}}}"#, 47 u32::MAX as u64 + 1 48 )) 49 .unwrap_err(); 50 let msg = err.to_string(); 51 assert!(msg.contains("epoch **seconds**")); 52 } 53 54 #[test] 55 fn epoch_seconds_rejects_invalid_input_type() { 56 let err = 57 serde_json::from_str::<EpochSecondsFixture>(r#"{"ts":"1700000000"}"#).unwrap_err(); 58 let msg = err.to_string(); 59 assert!(msg.contains("invalid type")); 60 } 61 }