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("Step '{0}' was aborted by user")]
26 StepAborted(StepId),
27
28 #[error("IO error: {0}")]
30 Io(#[from] io::Error),
31
32 #[error("Step '{step}' failed: {reason}")]
34 StepFailed {
35 step: StepId,
37 reason: String,
39 },
40
41 #[error("Unknown action: '{0}'")]
43 UnknownAction(ActionType),
44
45 #[error("Invalid params: {0}")]
47 InvalidParams(String),
48
49 #[error("Failed to gather machine entropy: {0}")]
51 EntropyError(String),
52
53 #[error("Failed to load material '{name}': {reason}")]
55 MaterialLoadFailed {
56 name: String,
58 reason: String,
60 },
61
62 #[error("Failed to write output '{name}': {reason}")]
64 OutputWriteFailed {
65 name: String,
67 reason: String,
69 },
70
71 #[error("Transcript error: {0}")]
73 TranscriptError(String),
74
75 #[error("Backend error: {0}")]
77 BackendError(#[from] BackendError),
78}
79
80pub(crate) fn step_info_from(step: &Step) -> StepInfo {
82 StepInfo::new(
83 step.id.clone(),
84 step.role.clone(),
85 step.backend.clone(),
86 step.creates.clone(),
87 step.reads_resolved.clone(),
88 )
89}
90
91pub(crate) fn load_material_artifact(
93 name: &str,
94 material: &Material,
95) -> Result<ArtifactValue, ExecutionError> {
96 match &material.kind {
97 MaterialKind::Physical { identifier, .. } => {
98 let text = identifier
99 .as_deref()
100 .or(material.title.as_deref())
101 .unwrap_or(name)
102 .to_string();
103 Ok(ArtifactValue::Text(text))
104 }
105 MaterialKind::Digital { source } => {
106 let source = source
107 .as_ref()
108 .ok_or_else(|| ExecutionError::MaterialLoadFailed {
109 name: name.to_string(),
110 reason: "no source provided for digital material".to_string(),
111 })?;
112 match source {
113 MaterialSource::File { file } => {
114 let bytes = fs::read(file).map_err(|e| ExecutionError::MaterialLoadFailed {
115 name: name.to_string(),
116 reason: e.to_string(),
117 })?;
118 Ok(ArtifactValue::Bytes(bytes))
119 }
120 MaterialSource::Identifier { identifier } => {
121 Ok(ArtifactValue::Text(identifier.clone()))
123 }
124 }
125 }
126 }
127}
128
129fn write_new_file(path: &Path, bytes: &[u8]) -> io::Result<()> {
136 let mut options = fs::OpenOptions::new();
137 options.write(true).create_new(true);
138 #[cfg(unix)]
139 {
140 use std::os::unix::fs::OpenOptionsExt;
141 options.mode(0o600);
142 }
143 let mut file = options.open(path)?;
144 file.write_all(bytes)?;
145 file.sync_all()
146}
147
148pub(crate) fn write_artifact_to_disk(
150 artifact_id: &ArtifactId,
151 artifact_value: &ArtifactValue,
152 output_config: &OutputConfig,
153) -> Result<(PathBuf, String, u64, Option<String>), ExecutionError> {
154 let serialized =
155 artifact_value
156 .serialize(None)
157 .map_err(|e| ExecutionError::OutputWriteFailed {
158 name: artifact_id.as_str().to_string(),
159 reason: e,
160 })?;
161
162 let path = output_config
163 .artifact_path(artifact_id.as_str(), serialized.extension)
164 .map_err(|e| ExecutionError::OutputWriteFailed {
165 name: artifact_id.as_str().to_string(),
166 reason: e.to_string(),
167 })?;
168
169 write_new_file(&path, &serialized.bytes).map_err(|e| ExecutionError::OutputWriteFailed {
170 name: artifact_id.as_str().to_string(),
171 reason: e.to_string(),
172 })?;
173
174 let hash = compute_file_fingerprint(&path).map_err(|e| ExecutionError::OutputWriteFailed {
175 name: artifact_id.as_str().to_string(),
176 reason: format!("hash computation failed: {e}"),
177 })?;
178
179 let size = fs::metadata(&path)
180 .map_err(|e| ExecutionError::OutputWriteFailed {
181 name: artifact_id.as_str().to_string(),
182 reason: format!("metadata read failed: {e}"),
183 })?
184 .len();
185
186 Ok((path, hash, size, serialized.mime_type))
187}