sc_observability_types/
primitives.rs1use std::borrow::Cow;
2use std::fmt;
3use std::ops::{Add, Sub};
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use time::{Duration, OffsetDateTime, UtcOffset, format_description::well_known::Rfc3339};
7
8#[derive(
10 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
11)]
12#[serde(transparent)]
13pub struct DurationMs(u64);
14
15impl DurationMs {
16 #[must_use]
18 pub const fn as_u64(self) -> u64 {
19 self.0
20 }
21}
22
23impl From<u64> for DurationMs {
24 fn from(value: u64) -> Self {
25 Self(value)
26 }
27}
28
29impl From<DurationMs> for u64 {
30 fn from(value: DurationMs) -> Self {
31 value.0
32 }
33}
34
35impl fmt::Display for DurationMs {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 write!(f, "{}ms", self.0)
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct Timestamp(OffsetDateTime);
44
45impl Timestamp {
46 pub const UNIX_EPOCH: Self = Self(OffsetDateTime::UNIX_EPOCH);
48
49 #[must_use]
51 pub fn now_utc() -> Self {
52 Self(OffsetDateTime::now_utc())
53 }
54
55 #[must_use]
57 pub fn from_offset_date_time(value: OffsetDateTime) -> Self {
58 Self(value.to_offset(UtcOffset::UTC))
59 }
60
61 #[must_use]
63 pub fn into_inner(self) -> OffsetDateTime {
64 self.0
65 }
66}
67
68impl From<OffsetDateTime> for Timestamp {
69 fn from(value: OffsetDateTime) -> Self {
70 Self::from_offset_date_time(value)
71 }
72}
73
74impl From<Timestamp> for OffsetDateTime {
75 fn from(value: Timestamp) -> Self {
76 value.0
77 }
78}
79
80impl Add<Duration> for Timestamp {
81 type Output = Self;
82
83 fn add(self, rhs: Duration) -> Self::Output {
84 Self::from_offset_date_time(self.0 + rhs)
85 }
86}
87
88impl Sub<Duration> for Timestamp {
89 type Output = Self;
90
91 fn sub(self, rhs: Duration) -> Self::Output {
92 Self::from_offset_date_time(self.0 - rhs)
93 }
94}
95
96impl Sub for Timestamp {
97 type Output = Duration;
98
99 fn sub(self, rhs: Self) -> Self::Output {
100 self.0 - rhs.0
101 }
102}
103
104impl fmt::Display for Timestamp {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 let rendered = self
107 .0
108 .to_offset(UtcOffset::UTC)
109 .format(&Rfc3339)
110 .map_err(|_| fmt::Error)?;
111 f.write_str(&rendered)
112 }
113}
114
115impl Serialize for Timestamp {
116 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117 where
118 S: Serializer,
119 {
120 let rendered = self
121 .0
122 .to_offset(UtcOffset::UTC)
123 .format(&Rfc3339)
124 .map_err(serde::ser::Error::custom)?;
125 serializer.serialize_str(&rendered)
126 }
127}
128
129impl<'de> Deserialize<'de> for Timestamp {
130 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131 where
132 D: Deserializer<'de>,
133 {
134 let value = String::deserialize(deserializer)?;
135 let parsed = OffsetDateTime::parse(&value, &Rfc3339).map_err(serde::de::Error::custom)?;
136 Ok(Self::from_offset_date_time(parsed))
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
142pub struct ErrorCode(Cow<'static, str>);
143
144impl ErrorCode {
145 #[must_use]
147 pub const fn new_static(code: &'static str) -> Self {
148 Self(Cow::Borrowed(code))
149 }
150
151 #[must_use]
153 pub fn new_owned(code: impl Into<String>) -> Self {
154 Self(Cow::Owned(code.into()))
155 }
156
157 #[must_use]
159 pub fn as_str(&self) -> &str {
160 self.0.as_ref()
161 }
162}
163
164impl fmt::Display for ErrorCode {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 f.write_str(self.as_str())
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use time::{OffsetDateTime, UtcOffset};
174
175 #[test]
176 fn timestamp_serde_round_trips_as_utc_rfc3339() {
177 let timestamp = Timestamp::from(
178 OffsetDateTime::UNIX_EPOCH.to_offset(UtcOffset::from_hms(2, 0, 0).expect("offset")),
179 );
180 let encoded = serde_json::to_string(×tamp).expect("serialize timestamp");
181 let decoded: Timestamp = serde_json::from_str(&encoded).expect("deserialize timestamp");
182
183 assert_eq!(encoded, "\"1970-01-01T00:00:00Z\"");
184 assert_eq!(decoded, timestamp);
185 }
186
187 #[test]
188 fn duration_ms_displays_in_milliseconds() {
189 assert_eq!(DurationMs::from(250).to_string(), "250ms");
190 }
191
192 #[test]
193 fn duration_ms_exposes_raw_millisecond_count() {
194 assert_eq!(DurationMs::from(250).as_u64(), 250);
195 }
196
197 #[test]
198 fn timestamp_arithmetic_preserves_utc_normalization() {
199 let start = Timestamp::UNIX_EPOCH;
200 let shifted = start + Duration::seconds(90);
201 let rewound = shifted - Duration::seconds(30);
202
203 assert_eq!(u64::from(DurationMs::from(250)), 250);
204 assert_eq!(shifted - start, Duration::seconds(90));
205 assert_eq!(rewound, start + Duration::seconds(60));
206 }
207
208 #[test]
209 fn timestamp_into_inner_and_from_timestamp_preserve_utc_value() {
210 let timestamp = Timestamp::from(
211 OffsetDateTime::UNIX_EPOCH.to_offset(UtcOffset::from_hms(-7, 0, 0).expect("offset")),
212 );
213
214 let inner = timestamp.into_inner();
215 let round_trip = OffsetDateTime::from(timestamp);
216
217 assert_eq!(inner.offset(), UtcOffset::UTC);
218 assert_eq!(round_trip, inner);
219 }
220
221 #[test]
222 fn error_code_displays_as_plain_code() {
223 assert_eq!(
224 ErrorCode::new_static("SC_TEST_ERROR_CODE").to_string(),
225 "SC_TEST_ERROR_CODE"
226 );
227 }
228
229 #[test]
230 fn error_code_new_owned_preserves_owned_value() {
231 let code = ErrorCode::new_owned("SC_OWNED_CODE");
232 assert_eq!(code.as_str(), "SC_OWNED_CODE");
233 }
234}