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