Skip to main content

sc_observability_types/
primitives.rs

1use 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/// Canonical millisecond duration type used across the workspace.
9#[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    /// Returns the raw millisecond count.
17    #[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/// Canonical UTC timestamp type used across the workspace.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct Timestamp(OffsetDateTime);
44
45impl Timestamp {
46    /// Canonical Unix epoch timestamp in UTC.
47    pub const UNIX_EPOCH: Self = Self(OffsetDateTime::UNIX_EPOCH);
48
49    /// Returns the current UTC timestamp.
50    #[must_use]
51    pub fn now_utc() -> Self {
52        Self(OffsetDateTime::now_utc())
53    }
54
55    /// Normalizes an arbitrary offset date-time into the canonical UTC timestamp.
56    #[must_use]
57    pub fn from_offset_date_time(value: OffsetDateTime) -> Self {
58        Self(value.to_offset(UtcOffset::UTC))
59    }
60
61    /// Returns the normalized inner UTC date-time value.
62    #[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/// Stable machine-readable error code used across diagnostics and error types.
141#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
142pub struct ErrorCode(Cow<'static, str>);
143
144impl ErrorCode {
145    /// Creates an error code from a `'static` string without allocating.
146    #[must_use]
147    pub const fn new_static(code: &'static str) -> Self {
148        Self(Cow::Borrowed(code))
149    }
150
151    /// Creates an error code from owned or borrowed string data by taking ownership.
152    #[must_use]
153    pub fn new_owned(code: impl Into<String>) -> Self {
154        Self(Cow::Owned(code.into()))
155    }
156
157    /// Returns the string representation of the error code.
158    #[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(&timestamp).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}