1use nexcore_id::NexId;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10macro_rules! define_id {
11 ($name:ident, $prefix:expr) => {
12 #[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13 #[serde(transparent)]
14 pub struct $name(NexId);
15
16 impl $name {
17 #[must_use]
18 pub fn new() -> Self {
19 Self(NexId::v4())
20 }
21
22 #[must_use]
23 pub fn from_nexid(id: NexId) -> Self {
24 Self(id)
25 }
26
27 #[must_use]
28 pub fn as_nexid(&self) -> &NexId {
29 &self.0
30 }
31
32 #[must_use]
33 pub fn into_nexid(self) -> NexId {
34 self.0
35 }
36
37 #[must_use]
39 pub fn parse(s: &str) -> Option<Self> {
40 s.parse::<NexId>().ok().map(Self)
41 }
42 }
43
44 impl Default for $name {
45 fn default() -> Self {
46 Self::new()
47 }
48 }
49
50 impl fmt::Display for $name {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(f, "{}_{}", $prefix, self.0)
53 }
54 }
55
56 impl fmt::Debug for $name {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{}({})", stringify!($name), self.0)
59 }
60 }
61
62 impl From<NexId> for $name {
63 fn from(id: NexId) -> Self {
64 Self(id)
65 }
66 }
67
68 impl From<$name> for NexId {
69 fn from(id: $name) -> Self {
70 id.0
71 }
72 }
73 };
74}
75
76define_id!(TenantId, "ten");
77define_id!(UserId, "usr");
78define_id!(ProgramId, "prg");
79define_id!(CompoundId, "cpd");
80define_id!(AssayId, "asy");
81define_id!(OrderId, "ord");
82define_id!(ProviderId, "prv");
83define_id!(ModelId, "mdl");
84define_id!(InvoiceId, "inv");
85define_id!(SignalId, "sig");
86define_id!(DealId, "deal");
87define_id!(AssetId, "ast");
88define_id!(TerminalSessionId, "tsn");
89
90#[cfg(test)]
91#[allow(clippy::unwrap_used)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn ids_are_not_interchangeable() {
97 let tenant = TenantId::new();
98 let user = UserId::new();
99 assert_ne!(tenant.as_nexid(), user.as_nexid());
100 }
101
102 #[test]
103 fn display_includes_prefix() {
104 let id = TenantId::from_nexid(NexId::NIL);
105 assert!(id.to_string().starts_with("ten_"));
106 }
107
108 #[test]
109 fn roundtrip_serde() {
110 let id = TenantId::new();
111 let json = serde_json::to_string(&id).unwrap();
112 let back: TenantId = serde_json::from_str(&json).unwrap();
113 assert_eq!(id, back);
114 }
115
116 #[test]
117 fn parse_valid_uuid() {
118 let id = TenantId::new();
119 let uuid_str = id.as_nexid().to_string();
120 let parsed = TenantId::parse(&uuid_str);
121 assert_eq!(parsed, Some(id));
122 }
123
124 #[test]
125 fn parse_invalid_returns_none() {
126 assert_eq!(TenantId::parse("not-a-uuid"), None);
127 }
128}