1use core::fmt;
5
6use serde_core::de::{self, Unexpected, Visitor};
7use serde_core::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::{
10 BootUptime, RawUptime, TaiTimestamp, TickRate, Tickdelta, Tickstamp, Timedelta, Timestamp,
11 Uptime,
12};
13
14macro_rules! spelled {
19 ($type:ident, $expecting:literal $(,)?) => {
20 spelled!($type, $expecting, i64, |value: $type| value.0, |count| Ok($type(count)));
21 };
22 ($type:ident, $expecting:literal, $count:ty, $as_count:expr, $from_count:expr $(,)?) => {
23 impl<'de> Deserialize<'de> for $type {
24 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
25 struct Spelling;
27
28 impl Visitor<'_> for Spelling {
29 type Value = $type;
30
31 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32 formatter.write_str($expecting)
33 }
34
35 fn visit_str<E: de::Error>(self, text: &str) -> Result<$type, E> {
36 text.parse().map_err(E::custom)
37 }
38 }
39
40 if deserializer.is_human_readable() {
41 deserializer.deserialize_str(Spelling)
42 } else {
43 ($from_count)(<$count>::deserialize(deserializer)?)
44 }
45 }
46 }
47
48 impl Serialize for $type {
49 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
50 if serializer.is_human_readable() {
51 serializer.collect_str(self)
52 } else {
53 ($as_count)(*self).serialize(serializer)
54 }
55 }
56 }
57 };
58}
59
60spelled!(Timestamp, "an RFC 3339 instant in UTC, as \"2026-09-16T07:45:35Z\"");
61spelled!(TaiTimestamp, "an RFC 3339 date and time in TAI, as \"2026-09-16T07:46:12 TAI\"");
62spelled!(Uptime, "the span since the origin, as \"1m30s\"");
63spelled!(RawUptime, "the span since the origin, as \"1m30s\"");
64spelled!(BootUptime, "the span since boot, as \"1m30s\"");
65spelled!(Tickstamp, "a count of ticks, as \"24 ticks\"");
66spelled!(Timedelta, "a span, as \"1m30s\"");
67spelled!(Tickdelta, "a count of ticks, as \"24 ticks\"");
68spelled!(
70 TickRate,
71 "a count of hertz above zero, as \"24000000 Hz\"",
72 u64,
73 TickRate::as_hertz,
74 |hertz| {
75 TickRate::from_hertz(hertz).ok_or_else(|| {
76 de::Error::invalid_value(Unexpected::Unsigned(hertz), &"a count of hertz above zero")
77 })
78 },
79);
80
81#[cfg(test)]
82mod tests {
83 use serde_test::{
84 Compact, Configure as _, Readable, Token, assert_de_tokens_error, assert_tokens,
85 };
86
87 use crate::{
88 BootUptime, RawUptime, TaiTimestamp, TickRate, Tickdelta, Tickstamp, Timedelta, Timestamp,
89 Uptime,
90 };
91
92 #[test]
93 fn each_value_round_trips_as_its_spelling() {
94 let instant = Timestamp::from_nanos(1_789_544_735_123_456_789);
95 assert_tokens(&instant.readable(), &[Token::Str("2026-09-16T07:45:35.123456789Z")]);
96 let tai = TaiTimestamp::from_secs(1_789_544_772);
97 assert_tokens(&tai.readable(), &[Token::Str("2026-09-16T07:46:12.000000000 TAI")]);
98 assert_tokens(&Uptime::from_secs(90).readable(), &[Token::Str("1m30s")]);
99 assert_tokens(&RawUptime::from_secs(90).readable(), &[Token::Str("1m30s")]);
100 assert_tokens(&BootUptime::from_secs(90).readable(), &[Token::Str("1m30s")]);
101 assert_tokens(&Tickstamp::from_ticks(24).readable(), &[Token::Str("24 ticks")]);
102 assert_tokens(&Timedelta::from_mins(5).readable(), &[Token::Str("5m")]);
103 assert_tokens(&Tickdelta::from_ticks(-24).readable(), &[Token::Str("-24 ticks")]);
104 let rate = TickRate::from_hertz(24_000_000).expect("a nonzero rate");
105 assert_tokens(&rate.readable(), &[Token::Str("24000000 Hz")]);
106 }
107
108 #[test]
109 fn a_format_no_person_reads_takes_each_value_as_its_count() {
110 let instant = Timestamp::from_nanos(1_789_544_735_123_456_789);
111 assert_tokens(&instant.compact(), &[Token::I64(1_789_544_735_123_456_789)]);
112 assert_tokens(&TaiTimestamp::from_nanos(7).compact(), &[Token::I64(7)]);
113 assert_tokens(&Uptime::from_nanos(7).compact(), &[Token::I64(7)]);
114 assert_tokens(&RawUptime::from_nanos(7).compact(), &[Token::I64(7)]);
115 assert_tokens(&BootUptime::from_nanos(7).compact(), &[Token::I64(7)]);
116 assert_tokens(&Tickstamp::from_ticks(7).compact(), &[Token::I64(7)]);
117 assert_tokens(&Timedelta::from_nanos(-7).compact(), &[Token::I64(-7)]);
118 assert_tokens(&Tickdelta::from_ticks(-7).compact(), &[Token::I64(-7)]);
119 let rate = TickRate::from_hertz(24_000_000).expect("a nonzero rate");
120 assert_tokens(&rate.compact(), &[Token::U64(24_000_000)]);
121 }
122
123 #[test]
124 fn a_spelling_that_does_not_read_is_refused_with_its_reason() {
125 assert_de_tokens_error::<Readable<Timedelta>>(
126 &[Token::Str("5 minutes")],
127 "parse timedelta error: expected `0`, or counts with units from coarsest to finest, \
128 as `1m30s`",
129 );
130 assert_de_tokens_error::<Compact<TickRate>>(
131 &[Token::U64(0)],
132 "invalid value: integer `0`, expected a count of hertz above zero",
133 );
134 }
135}