1use serde::{Deserialize, Serialize};
7use std::borrow::Borrow;
8use std::fmt;
9use std::ops::Deref;
10
11macro_rules! string_newtype {
12 ($(#[$meta:meta])* $name:ident) => {
13 $(#[$meta])*
14 #[derive(
15 Debug,
16 Clone,
17 PartialEq,
18 Eq,
19 Hash,
20 PartialOrd,
21 Ord,
22 Serialize,
23 Deserialize,
24 )]
25 #[serde(transparent)]
26 pub struct $name(String);
27
28 impl $name {
29 #[must_use]
31 pub fn new(value: impl Into<String>) -> Self {
32 Self(value.into())
33 }
34
35 #[must_use]
37 pub fn as_str(&self) -> &str {
38 &self.0
39 }
40 }
41
42 impl Deref for $name {
43 type Target = str;
44
45 fn deref(&self) -> &Self::Target {
46 self.as_str()
47 }
48 }
49
50 impl AsRef<str> for $name {
51 fn as_ref(&self) -> &str {
52 self.as_str()
53 }
54 }
55
56 impl Borrow<str> for $name {
57 fn borrow(&self) -> &str {
58 self.as_str()
59 }
60 }
61
62 impl fmt::Display for $name {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.write_str(self.as_str())
65 }
66 }
67
68 impl From<&str> for $name {
69 fn from(value: &str) -> Self {
70 Self::new(value)
71 }
72 }
73
74 impl From<String> for $name {
75 fn from(value: String) -> Self {
76 Self::new(value)
77 }
78 }
79
80 impl From<&$name> for $name {
81 fn from(value: &$name) -> Self {
82 value.clone()
83 }
84 }
85
86 impl From<$name> for String {
87 fn from(value: $name) -> Self {
88 value.0
89 }
90 }
91
92 impl From<&$name> for String {
93 fn from(value: &$name) -> Self {
94 value.as_str().to_string()
95 }
96 }
97
98 impl PartialEq<&str> for $name {
99 fn eq(&self, other: &&str) -> bool {
100 self.as_str() == *other
101 }
102 }
103
104 impl PartialEq<str> for $name {
105 fn eq(&self, other: &str) -> bool {
106 self.as_str() == other
107 }
108 }
109
110 impl PartialEq<$name> for &str {
111 fn eq(&self, other: &$name) -> bool {
112 *self == other.as_str()
113 }
114 }
115
116 impl PartialEq<$name> for str {
117 fn eq(&self, other: &$name) -> bool {
118 self == other.as_str()
119 }
120 }
121
122 impl PartialEq<String> for $name {
123 fn eq(&self, other: &String) -> bool {
124 self.as_str() == other.as_str()
125 }
126 }
127
128 impl PartialEq<$name> for String {
129 fn eq(&self, other: &$name) -> bool {
130 self.as_str() == other.as_str()
131 }
132 }
133
134 impl PartialEq<&$name> for $name {
135 fn eq(&self, other: &&$name) -> bool {
136 self == *other
137 }
138 }
139 };
140}
141
142string_newtype!(
143 FactId
145);
146string_newtype!(
147 ProposalId
149);
150string_newtype!(
151 ObservationId
153);
154string_newtype!(
155 ApprovalId
157);
158string_newtype!(
159 ArtifactId
161);
162string_newtype!(
163 GateId
165);
166string_newtype!(
167 ActorId
169);
170string_newtype!(
171 ValidationCheckId
173);
174string_newtype!(
175 TraceId
177);
178string_newtype!(
179 SpanId
181);
182string_newtype!(
183 TraceSystemId
185);
186string_newtype!(
187 TraceReference
189);
190string_newtype!(
191 PrincipalId
193);
194string_newtype!(
195 EventId
197);
198string_newtype!(
199 TenantId
201);
202string_newtype!(
203 CorrelationId
205);
206string_newtype!(
207 ChainId
209);
210string_newtype!(
211 TraceLinkId
213);
214string_newtype!(
215 BackendId
217);
218string_newtype!(
219 PackId
221);
222string_newtype!(
223 TruthId
225);
226string_newtype!(
227 PolicyId
229);
230string_newtype!(
231 ApprovalPointId
233);
234string_newtype!(
235 CriterionId
237);
238string_newtype!(
239 ConstraintName
241);
242string_newtype!(
243 ConstraintValue
245);
246string_newtype!(
247 DomainId
249);
250string_newtype!(
251 PolicyVersionId
253);
254string_newtype!(
255 ResourceId
257);
258string_newtype!(
259 ResourceKind
261);
262
263#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
265#[serde(transparent)]
266pub struct ContentHash(#[serde(with = "hex_bytes")] [u8; 32]);
267
268impl ContentHash {
269 #[must_use]
271 pub fn new(bytes: [u8; 32]) -> Self {
272 Self(bytes)
273 }
274
275 #[must_use]
281 pub fn from_hex(hex: &str) -> Self {
282 let mut bytes = [0u8; 32];
283 hex::decode_to_slice(hex, &mut bytes).expect("invalid hex string");
284 Self(bytes)
285 }
286
287 #[must_use]
289 pub fn as_bytes(&self) -> &[u8; 32] {
290 &self.0
291 }
292
293 #[must_use]
295 pub fn to_hex(&self) -> String {
296 hex::encode(self.0)
297 }
298
299 #[must_use]
301 pub fn zero() -> Self {
302 Self([0u8; 32])
303 }
304}
305
306impl Default for ContentHash {
307 fn default() -> Self {
308 Self::zero()
309 }
310}
311
312impl fmt::Display for ContentHash {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 f.write_str(&self.to_hex())
315 }
316}
317
318mod hex_bytes {
319 use serde::{Deserialize, Deserializer, Serializer};
320
321 pub fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
322 where
323 S: Serializer,
324 {
325 serializer.serialize_str(&hex::encode(bytes))
326 }
327
328 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
329 where
330 D: Deserializer<'de>,
331 {
332 let raw = String::deserialize(deserializer)?;
333 let mut bytes = [0u8; 32];
334 hex::decode_to_slice(raw, &mut bytes).map_err(serde::de::Error::custom)?;
335 Ok(bytes)
336 }
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
341#[serde(transparent)]
342pub struct Timestamp(String);
343
344impl Timestamp {
345 #[must_use]
347 pub fn new(value: impl Into<String>) -> Self {
348 Self(value.into())
349 }
350
351 #[must_use]
353 pub fn as_str(&self) -> &str {
354 &self.0
355 }
356
357 #[must_use]
359 pub fn epoch() -> Self {
360 Self::new("1970-01-01T00:00:00Z")
361 }
362
363 #[must_use]
365 pub fn now() -> Self {
366 use std::time::{SystemTime, UNIX_EPOCH};
367
368 let duration = SystemTime::now()
369 .duration_since(UNIX_EPOCH)
370 .unwrap_or_default();
371 Self(format!("{}Z", duration.as_secs()))
372 }
373}
374
375impl Deref for Timestamp {
376 type Target = str;
377
378 fn deref(&self) -> &Self::Target {
379 self.as_str()
380 }
381}
382
383impl AsRef<str> for Timestamp {
384 fn as_ref(&self) -> &str {
385 self.as_str()
386 }
387}
388
389impl fmt::Display for Timestamp {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 f.write_str(self.as_str())
392 }
393}
394
395impl From<&str> for Timestamp {
396 fn from(value: &str) -> Self {
397 Self::new(value)
398 }
399}
400
401impl From<String> for Timestamp {
402 fn from(value: String) -> Self {
403 Self::new(value)
404 }
405}
406
407impl From<Timestamp> for String {
408 fn from(value: Timestamp) -> Self {
409 value.0
410 }
411}
412
413impl From<&Timestamp> for String {
414 fn from(value: &Timestamp) -> Self {
415 value.as_str().to_string()
416 }
417}
418
419impl PartialEq<&str> for Timestamp {
420 fn eq(&self, other: &&str) -> bool {
421 self.as_str() == *other
422 }
423}
424
425impl PartialEq<str> for Timestamp {
426 fn eq(&self, other: &str) -> bool {
427 self.as_str() == other
428 }
429}
430
431impl PartialEq<Timestamp> for &str {
432 fn eq(&self, other: &Timestamp) -> bool {
433 *self == other.as_str()
434 }
435}
436
437impl PartialEq<Timestamp> for str {
438 fn eq(&self, other: &Timestamp) -> bool {
439 self == other.as_str()
440 }
441}
442
443impl PartialEq<String> for Timestamp {
444 fn eq(&self, other: &String) -> bool {
445 self.as_str() == other.as_str()
446 }
447}
448
449impl PartialEq<Timestamp> for String {
450 fn eq(&self, other: &Timestamp) -> bool {
451 self.as_str() == other.as_str()
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn string_newtypes_compare_like_strings_without_erasing_type_identity() {
461 let fact_id = FactId::new("fact-1");
462 let proposal_id = ProposalId::new("fact-1");
463
464 assert_eq!(fact_id, "fact-1");
465 assert_eq!("fact-1", fact_id);
466 assert_ne!(fact_id.to_string(), "");
467 assert_ne!(fact_id.as_str(), "");
468 assert_eq!(proposal_id.as_str(), "fact-1");
469 }
470
471 #[test]
472 fn content_hash_hex_roundtrip() {
473 let hash = ContentHash::from_hex(
474 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
475 );
476 assert_eq!(
477 hash.to_hex(),
478 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
479 );
480 }
481
482 #[test]
483 fn timestamp_is_transparent() {
484 let timestamp = Timestamp::epoch();
485 let json = serde_json::to_string(×tamp).expect("timestamp should serialize");
486 assert_eq!(json, r#""1970-01-01T00:00:00Z""#);
487 }
488}