rite_runtime/
output_config.rs1use chrono::Utc;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
20pub struct OutputConfig {
21 base_dir: PathBuf,
23}
24
25impl OutputConfig {
26 #[must_use]
28 pub fn new(base_dir: PathBuf) -> Self {
29 Self { base_dir }
30 }
31
32 #[must_use]
41 pub fn for_ceremony(parent: Option<PathBuf>, ceremony_name: &str) -> Self {
42 let parent = parent.unwrap_or_else(|| PathBuf::from("."));
43 let timestamp = Utc::now().format("%Y%m%dT%H%M%S");
44 let slug = slugify(ceremony_name);
45 Self {
46 base_dir: parent.join(format!("{slug}-{timestamp}")),
47 }
48 }
49
50 pub fn base_dir(&self) -> &std::path::Path {
52 &self.base_dir
53 }
54
55 pub fn artifacts_dir(&self) -> PathBuf {
57 self.base_dir.join("artifacts")
58 }
59
60 pub fn transcript_path(&self) -> PathBuf {
64 self.base_dir.join("transcript.jsonl")
65 }
66
67 pub fn evidence_dir(&self) -> PathBuf {
69 self.base_dir.join("evidence")
70 }
71
72 pub fn artifact_path(&self, id: &str, extension: &str) -> PathBuf {
74 self.artifacts_dir().join(format!("{id}.{extension}"))
75 }
76}
77
78fn slugify(name: &str) -> String {
80 name.to_lowercase()
81 .chars()
82 .map(|c| if c.is_alphanumeric() { c } else { '-' })
83 .collect::<String>()
84 .split('-')
85 .filter(|s| !s.is_empty())
86 .collect::<Vec<_>>()
87 .join("-")
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn output_config_paths() {
96 let config = OutputConfig::new(PathBuf::from("/tmp/ceremony-20251229T143022"));
97
98 assert_eq!(
99 config.base_dir(),
100 std::path::Path::new("/tmp/ceremony-20251229T143022")
101 );
102 assert_eq!(
103 config.artifacts_dir(),
104 PathBuf::from("/tmp/ceremony-20251229T143022/artifacts")
105 );
106 assert_eq!(
107 config.transcript_path(),
108 PathBuf::from("/tmp/ceremony-20251229T143022/transcript.jsonl")
109 );
110 assert_eq!(
111 config.artifact_path("wrapped_key", "bin"),
112 PathBuf::from("/tmp/ceremony-20251229T143022/artifacts/wrapped_key.bin")
113 );
114 }
115
116 #[test]
117 fn test_slugify() {
118 assert_eq!(slugify("Simple Name"), "simple-name");
119 assert_eq!(slugify("HSM Bootstrap"), "hsm-bootstrap");
120 assert_eq!(slugify("Root CA Signing"), "root-ca-signing");
121 assert_eq!(slugify("Test_Ceremony-2024"), "test-ceremony-2024");
122 assert_eq!(slugify(" Leading/Trailing "), "leading-trailing");
123 }
124}