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, 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}