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