1use crate::actions::ArtifactValue;
5use crate::step_info::StepInfo;
6use rite_model::{
7 ActionType, ArtifactId, Material, MaterialKind, MaterialSource, Sha256Digest, Step, StepId,
8};
9use rite_sdk::BackendError;
10use std::fs;
11use std::io;
12use std::io::Write;
13use std::path::{Path, PathBuf};
14use thiserror::Error;
15use zeroize::Zeroizing;
16
17use crate::output_config::OutputConfig;
18
19#[derive(Debug, Error)]
21pub enum ExecutionError {
22 #[error("Validation failed: {0}")]
24 ValidationFailed(String),
25
26 #[error("Ceremony aborted by the operator")]
30 Aborted,
31
32 #[error("IO error: {0}")]
34 Io(#[from] io::Error),
35
36 #[error("Step '{step}' failed: {reason}")]
38 StepFailed {
39 step: StepId,
41 reason: String,
43 },
44
45 #[error("Unknown action: '{0}'")]
47 UnknownAction(ActionType),
48
49 #[error("Invalid params: {0}")]
51 InvalidParams(String),
52
53 #[error("Failed to gather machine entropy: {0}")]
55 EntropyError(String),
56
57 #[error("Failed to load material '{name}': {reason}")]
59 MaterialLoadFailed {
60 name: String,
62 reason: String,
64 },
65
66 #[error("Failed to write output '{name}': {reason}")]
68 OutputWriteFailed {
69 name: String,
71 reason: String,
73 },
74
75 #[error("Transcript error: {0}")]
77 TranscriptError(String),
78
79 #[error("Backend error: {0}")]
81 BackendError(#[from] BackendError),
82}
83
84pub(crate) fn step_info_from(step: &Step) -> StepInfo {
86 StepInfo::new(
87 step.id.clone(),
88 step.role.clone(),
89 step.backend.clone(),
90 step.creates.clone(),
91 step.reads_resolved.clone(),
92 )
93}
94
95pub(crate) fn load_material_artifact(
97 name: &str,
98 material: &Material,
99) -> Result<ArtifactValue, ExecutionError> {
100 match &material.kind {
101 MaterialKind::Physical { identifier, .. } => {
102 let text = identifier
103 .as_deref()
104 .or(material.title.as_deref())
105 .unwrap_or(name)
106 .to_string();
107 Ok(ArtifactValue::Text(text))
108 }
109 MaterialKind::Digital { source } => {
110 let source = source
111 .as_ref()
112 .ok_or_else(|| ExecutionError::MaterialLoadFailed {
113 name: name.to_string(),
114 reason: "no source provided for digital material".to_string(),
115 })?;
116 match source {
117 MaterialSource::File { file } => {
118 let bytes = fs::read(file).map_err(|e| ExecutionError::MaterialLoadFailed {
119 name: name.to_string(),
120 reason: e.to_string(),
121 })?;
122 Ok(ArtifactValue::Bytes(bytes))
123 }
124 MaterialSource::Identifier { identifier } => {
125 Ok(ArtifactValue::Text(identifier.clone()))
127 }
128 }
129 }
130 }
131}
132
133pub fn write_new_file(path: &Path, bytes: &[u8]) -> io::Result<()> {
144 let mut options = fs::OpenOptions::new();
145 options.write(true).create_new(true);
146 #[cfg(unix)]
147 {
148 use std::os::unix::fs::OpenOptionsExt;
149 options.mode(0o600);
150 }
151 let mut file = options.open(path)?;
152 file.write_all(bytes)?;
153 file.sync_all()
154}
155
156pub(crate) fn write_artifact_to_disk(
158 artifact_id: &ArtifactId,
159 artifact_value: &ArtifactValue,
160 output_config: &OutputConfig,
161) -> Result<(PathBuf, Sha256Digest, u64, Option<String>), ExecutionError> {
162 let serialized =
163 artifact_value
164 .serialize(None)
165 .map_err(|e| ExecutionError::OutputWriteFailed {
166 name: artifact_id.as_str().to_string(),
167 reason: e,
168 })?;
169 let bytes = Zeroizing::new(serialized.bytes);
173
174 let path = output_config
175 .artifact_path(artifact_id.as_str(), serialized.extension)
176 .map_err(|e| ExecutionError::OutputWriteFailed {
177 name: artifact_id.as_str().to_string(),
178 reason: e.to_string(),
179 })?;
180
181 write_new_file(&path, &bytes).map_err(|e| ExecutionError::OutputWriteFailed {
182 name: artifact_id.as_str().to_string(),
183 reason: e.to_string(),
184 })?;
185
186 let hash = Sha256Digest::of(&bytes);
189
190 let size = fs::metadata(&path)
191 .map_err(|e| ExecutionError::OutputWriteFailed {
192 name: artifact_id.as_str().to_string(),
193 reason: format!("metadata read failed: {e}"),
194 })?
195 .len();
196
197 Ok((path, hash, size, serialized.mime_type))
198}