Skip to main content

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}