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