rite_model/ir/ids.rs
1//! Type-safe identifier newtypes for the ceremony IR.
2//!
3//! These wrapper types prevent accidentally mixing up different kinds of IDs
4//! (e.g., passing a `RoleId` where a `StepId` is expected).
5//!
6//! Each ID wraps a `String` for easy debugging and display.
7
8use std::fmt;
9use std::hash::Hash;
10
11macro_rules! define_id {
12 ($(#[$meta:meta])* $name:ident) => {
13 $(#[$meta])*
14 #[derive(Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)]
15 #[serde(transparent)]
16 pub struct $name(String);
17
18 impl $name {
19 /// Create a new ID from any string-like value.
20 pub fn new(s: impl Into<String>) -> Self {
21 Self(s.into())
22 }
23
24 /// Get the underlying string.
25 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28
29 /// Consume and return the underlying string.
30 pub fn into_string(self) -> String {
31 self.0
32 }
33 }
34
35 impl fmt::Display for $name {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 write!(f, "{}", self.0)
38 }
39 }
40
41 impl From<String> for $name {
42 fn from(s: String) -> Self {
43 Self(s)
44 }
45 }
46
47 impl From<&str> for $name {
48 fn from(s: &str) -> Self {
49 Self(s.to_string())
50 }
51 }
52
53 impl AsRef<str> for $name {
54 fn as_ref(&self) -> &str {
55 &self.0
56 }
57 }
58 };
59}
60
61define_id!(
62 /// Unique identifier for a role in the ceremony.
63 RoleId
64);
65
66define_id!(
67 /// Unique identifier for a step in the ceremony.
68 StepId
69);
70
71define_id!(
72 /// Unique identifier for a section in the ceremony.
73 SectionId
74);
75
76define_id!(
77 /// Unique identifier for an act in the ceremony.
78 ActId
79);
80
81define_id!(
82 /// Unique identifier for a parameter in the ceremony.
83 ParamId
84);
85
86define_id!(
87 /// Unique identifier for a material in the ceremony.
88 MaterialId
89);
90
91define_id!(
92 /// Unique identifier for an artifact produced during the ceremony.
93 ///
94 /// Artifacts are runtime values produced by step actions and stored in execution state.
95 /// They can be referenced by subsequent steps (e.g., `${artifact.keypair.private}`)
96 /// or declared as outputs for file/stdout destinations.
97 ///
98 /// Not all artifacts are outputs; intermediate artifacts may be produced and consumed
99 /// without ever being written to disk.
100 ///
101 /// See also: [`OutputId`] for output declarations.
102 ArtifactId
103);
104
105define_id!(
106 /// Unique identifier for an output declaration in the ceremony.
107 ///
108 /// Outputs are ceremony declarations that specify the type and format of artifacts
109 /// produced during the ceremony. They are resolved at ceremony load time.
110 ///
111 /// # Relationship to `ArtifactId`
112 ///
113 /// `OutputId` and `ArtifactId` are **separate types** that **match by name**:
114 ///
115 /// ```yaml
116 /// # Ceremony declares outputs (OutputId)
117 /// output:
118 /// public_key: # OutputId("public_key")
119 /// type: public_key
120 ///
121 /// # Steps produce artifacts (ArtifactId)
122 /// steps:
123 /// - id: export
124 /// produces: ${artifact.public_key} # ArtifactId("public_key")
125 /// ```
126 ///
127 /// At runtime, the executor looks up: "for this `ArtifactId`, is there a declared `OutputId`?"
128 ///
129 /// They remain separate types because:
130 /// - **Different lifespans**: `OutputId` exists from resolution, `ArtifactId` from execution
131 /// - **Different scopes**: Not all artifacts are outputs (intermediate values)
132 /// - **Type safety**: Prevents mixing declarations vs runtime values
133 ///
134 /// See also: [`ArtifactId`] for runtime artifacts.
135 OutputId
136);
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use std::collections::HashMap;
142
143 #[test]
144 fn ids_are_type_safe() {
145 let role_id = RoleId::new("witness");
146 let step_id = StepId::new("witness"); // Same string, different type.
147
148 // These are different types, can't be compared directly.
149 assert_eq!(role_id.as_str(), step_id.as_str());
150 }
151
152 #[test]
153 fn ids_work_as_hash_keys() {
154 let mut map: HashMap<RoleId, String> = HashMap::new();
155 map.insert(RoleId::new("admin"), "Alice".to_string());
156 map.insert(RoleId::new("witness"), "Bob".to_string());
157
158 assert_eq!(map.get(&RoleId::new("admin")), Some(&"Alice".to_string()));
159 }
160
161 #[test]
162 fn ids_display_nicely() {
163 let id = RoleId::new("ceremony_admin");
164 assert_eq!(format!("{id}"), "ceremony_admin");
165 assert_eq!(format!("{id:?}"), r#"RoleId("ceremony_admin")"#);
166 }
167}