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 VoteId
237);
238string_newtype!(
239 VoteTopicId
241);
242string_newtype!(
243 DisagreementId
245);
246string_newtype!(
247 CriterionId
249);
250string_newtype!(
251 ConstraintName
253);
254string_newtype!(
255 ConstraintValue
257);
258string_newtype!(
259 DomainId
261);
262string_newtype!(
263 PolicyVersionId
265);
266string_newtype!(
267 ResourceId
269);
270string_newtype!(
271 ResourceKind
273);
274
275#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
277#[serde(transparent)]
278pub struct ContentHash(#[serde(with = "hex_bytes")] [u8; 32]);
279
280impl ContentHash {
281 #[must_use]
283 pub fn new(bytes: [u8; 32]) -> Self {
284 Self(bytes)
285 }
286
287 #[must_use]
293 pub fn from_hex(hex: &str) -> Self {
294 let mut bytes = [0u8; 32];
295 hex::decode_to_slice(hex, &mut bytes).expect("invalid hex string");
296 Self(bytes)
297 }
298
299 #[must_use]
301 pub fn as_bytes(&self) -> &[u8; 32] {
302 &self.0
303 }
304
305 #[must_use]
307 pub fn to_hex(&self) -> String {
308 hex::encode(self.0)
309 }
310
311 #[must_use]
313 pub fn zero() -> Self {
314 Self([0u8; 32])
315 }
316}
317
318impl Default for ContentHash {
319 fn default() -> Self {
320 Self::zero()
321 }
322}
323
324impl fmt::Display for ContentHash {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 f.write_str(&self.to_hex())
327 }
328}
329
330mod hex_bytes {
331 use serde::{Deserialize, Deserializer, Serializer};
332
333 pub fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
334 where
335 S: Serializer,
336 {
337 serializer.serialize_str(&hex::encode(bytes))
338 }
339
340 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
341 where
342 D: Deserializer<'de>,
343 {
344 let raw = String::deserialize(deserializer)?;
345 let mut bytes = [0u8; 32];
346 hex::decode_to_slice(raw, &mut bytes).map_err(serde::de::Error::custom)?;
347 Ok(bytes)
348 }
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
353#[serde(transparent)]
354pub struct Timestamp(String);
355
356impl Timestamp {
357 #[must_use]
359 pub fn new(value: impl Into<String>) -> Self {
360 Self(value.into())
361 }
362
363 #[must_use]
365 pub fn as_str(&self) -> &str {
366 &self.0
367 }
368
369 #[must_use]
371 pub fn epoch() -> Self {
372 Self::new("1970-01-01T00:00:00Z")
373 }
374
375 #[must_use]
377 pub fn now() -> Self {
378 use std::time::{SystemTime, UNIX_EPOCH};
379
380 let duration = SystemTime::now()
381 .duration_since(UNIX_EPOCH)
382 .unwrap_or_default();
383 Self(format!("{}Z", duration.as_secs()))
384 }
385}
386
387impl Deref for Timestamp {
388 type Target = str;
389
390 fn deref(&self) -> &Self::Target {
391 self.as_str()
392 }
393}
394
395impl AsRef<str> for Timestamp {
396 fn as_ref(&self) -> &str {
397 self.as_str()
398 }
399}
400
401impl fmt::Display for Timestamp {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 f.write_str(self.as_str())
404 }
405}
406
407impl From<&str> for Timestamp {
408 fn from(value: &str) -> Self {
409 Self::new(value)
410 }
411}
412
413impl From<String> for Timestamp {
414 fn from(value: String) -> Self {
415 Self::new(value)
416 }
417}
418
419impl From<Timestamp> for String {
420 fn from(value: Timestamp) -> Self {
421 value.0
422 }
423}
424
425impl From<&Timestamp> for String {
426 fn from(value: &Timestamp) -> Self {
427 value.as_str().to_string()
428 }
429}
430
431impl PartialEq<&str> for Timestamp {
432 fn eq(&self, other: &&str) -> bool {
433 self.as_str() == *other
434 }
435}
436
437impl PartialEq<str> for Timestamp {
438 fn eq(&self, other: &str) -> bool {
439 self.as_str() == other
440 }
441}
442
443impl PartialEq<Timestamp> for &str {
444 fn eq(&self, other: &Timestamp) -> bool {
445 *self == other.as_str()
446 }
447}
448
449impl PartialEq<Timestamp> for str {
450 fn eq(&self, other: &Timestamp) -> bool {
451 self == other.as_str()
452 }
453}
454
455impl PartialEq<String> for Timestamp {
456 fn eq(&self, other: &String) -> bool {
457 self.as_str() == other.as_str()
458 }
459}
460
461impl PartialEq<Timestamp> for String {
462 fn eq(&self, other: &Timestamp) -> bool {
463 self.as_str() == other.as_str()
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn string_newtypes_compare_like_strings_without_erasing_type_identity() {
473 let fact_id = FactId::new("fact-1");
474 let proposal_id = ProposalId::new("fact-1");
475
476 assert_eq!(fact_id, "fact-1");
477 assert_eq!("fact-1", fact_id);
478 assert_ne!(fact_id.to_string(), "");
479 assert_ne!(fact_id.as_str(), "");
480 assert_eq!(proposal_id.as_str(), "fact-1");
481 }
482
483 #[test]
484 fn content_hash_hex_roundtrip() {
485 let hash = ContentHash::from_hex(
486 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
487 );
488 assert_eq!(
489 hash.to_hex(),
490 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
491 );
492 }
493
494 #[test]
495 fn timestamp_is_transparent() {
496 let timestamp = Timestamp::epoch();
497 let json = serde_json::to_string(×tamp).expect("timestamp should serialize");
498 assert_eq!(json, r#""1970-01-01T00:00:00Z""#);
499 }
500}