1use std::fmt;
6use std::num::NonZeroU64;
7use std::str::FromStr;
8
9use serde::{Deserialize, Serialize};
10
11use crate::errors::{CoreError, CoreErrorCode};
12
13macro_rules! define_id {
14 ($name:ident, $prefix:literal) => {
15 #[doc = concat!("Strongly-typed ID for `", stringify!($prefix), "` resources.")]
16 #[doc = ""]
17 #[doc = "```"]
18 #[doc = concat!("use daedalus_core::ids::", stringify!($name), ";")]
19 #[doc = concat!("let id = ", stringify!($name), "::try_from(1).unwrap();")]
20 #[doc = concat!("assert_eq!(id.to_string(), \"", $prefix, ":1\");")]
21 #[doc = "```"]
22 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23 pub struct $name(NonZeroU64);
24
25 impl $name {
26 pub const PREFIX: &'static str = $prefix;
27
28 pub fn new(raw: NonZeroU64) -> Self {
29 Self(raw)
30 }
31
32 pub fn get(self) -> NonZeroU64 {
33 self.0
34 }
35
36 pub fn into_inner(self) -> NonZeroU64 {
37 self.0
38 }
39 }
40
41 impl fmt::Display for $name {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 write!(f, "{}:{}", Self::PREFIX, self.0)
44 }
45 }
46
47 impl From<NonZeroU64> for $name {
48 fn from(value: NonZeroU64) -> Self {
49 Self::new(value)
50 }
51 }
52
53 impl TryFrom<u64> for $name {
54 type Error = CoreError;
55
56 fn try_from(value: u64) -> Result<Self, Self::Error> {
57 let Some(nz) = NonZeroU64::new(value) else {
58 return Err(CoreError::new(
59 CoreErrorCode::InvalidId,
60 format!("{} must be non-zero", Self::PREFIX),
61 ));
62 };
63 Ok(Self::new(nz))
64 }
65 }
66
67 impl FromStr for $name {
68 type Err = CoreError;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 let (prefix, rest) = s.split_once(':').ok_or_else(|| {
72 CoreError::new(CoreErrorCode::InvalidId, format!("missing prefix in {}", s))
73 })?;
74 if prefix != Self::PREFIX {
75 return Err(CoreError::new(
76 CoreErrorCode::InvalidId,
77 format!("expected prefix {} but found {}", Self::PREFIX, prefix),
78 ));
79 }
80 let raw: u64 = rest.parse().map_err(|_| {
81 CoreError::new(
82 CoreErrorCode::InvalidId,
83 format!("invalid numeric id {}", s),
84 )
85 })?;
86 Self::try_from(raw)
87 }
88 }
89
90 impl Serialize for $name {
91 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
92 where
93 S: serde::Serializer,
94 {
95 serializer.serialize_str(&self.to_string())
96 }
97 }
98
99 impl<'de> Deserialize<'de> for $name {
100 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101 where
102 D: serde::Deserializer<'de>,
103 {
104 let s = String::deserialize(deserializer)?;
105 s.parse().map_err(serde::de::Error::custom)
106 }
107 }
108 };
109}
110
111define_id!(NodeId, "node");
112define_id!(PortId, "port");
113define_id!(EdgeId, "edge");
114define_id!(ChannelId, "chan");
115define_id!(RunId, "run");
116define_id!(TickId, "tick");
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use serde_json;
122
123 #[test]
124 fn display_and_parse_round_trip() {
125 let id = NodeId::try_from(42).expect("id");
126 let rendered = id.to_string();
127 assert_eq!(rendered, "node:42");
128 let parsed = rendered.parse::<NodeId>().expect("parse");
129 assert_eq!(parsed, id);
130 }
131
132 #[test]
133 fn rejects_zero() {
134 let err = NodeId::try_from(0).unwrap_err();
135 assert_eq!(err.code(), CoreErrorCode::InvalidId);
136 }
137
138 #[test]
139 fn serde_string_is_stable() {
140 let id = EdgeId::try_from(7).expect("id");
141 let json = serde_json::to_string(&id).expect("serialize");
142 assert_eq!(json, "\"edge:7\"");
143 let back: EdgeId = serde_json::from_str(&json).expect("deserialize");
144 assert_eq!(back, id);
145 }
146}