1use std::path::PathBuf;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum BuildError {
12 #[error("Dockerfile parse error at line {line}: {message}")]
14 DockerfileParse {
15 message: String,
17 line: usize,
19 },
20
21 #[error("Failed to read build context at '{path}': {source}")]
23 ContextRead {
24 path: PathBuf,
26 source: std::io::Error,
28 },
29
30 #[error("Path escape attempt: '{path}' escapes build context")]
32 PathEscape {
33 path: PathBuf,
35 },
36
37 #[error("File '{path}' is ignored by .dockerignore")]
39 FileIgnored {
40 path: PathBuf,
42 },
43
44 #[error("Stage '{name}' not found in Dockerfile")]
46 StageNotFound {
47 name: String,
49 },
50
51 #[error("RUN command failed with exit code {exit_code}: {command}")]
53 RunFailed {
54 command: String,
56 exit_code: i32,
58 },
59
60 #[error("Failed to create layer: {message}")]
62 LayerCreate {
63 message: String,
65 },
66
67 #[error("Cache error: {message}")]
69 CacheError {
70 message: String,
72 },
73
74 #[error("Registry error: {message}")]
76 RegistryError {
77 message: String,
79 },
80
81 #[error("IO error: {0}")]
83 IoError(#[from] std::io::Error),
84
85 #[error("Variable expansion failed: {0}")]
87 VariableExpansion(String),
88
89 #[error("Invalid instruction '{instruction}': {reason}")]
91 InvalidInstruction {
92 instruction: String,
94 reason: String,
96 },
97
98 #[error("Buildah execution failed: {command} (exit code {exit_code}): {stderr}")]
100 BuildahExecution {
101 command: String,
103 exit_code: i32,
105 stderr: String,
107 },
108
109 #[error("Build context too large: {size} bytes (max: {max} bytes)")]
111 ContextTooLarge {
112 size: u64,
114 max: u64,
116 },
117
118 #[error("Base image not found: {image}")]
120 BaseImageNotFound {
121 image: String,
123 },
124
125 #[error("Circular dependency detected in multi-stage build: {stages:?}")]
127 CircularDependency {
128 stages: Vec<String>,
130 },
131
132 #[error("Buildah not found: {message}")]
134 BuildahNotFound {
135 message: String,
137 },
138
139 #[error("ZImagefile parse error: {message}")]
141 ZImagefileParse {
142 message: String,
144 },
145
146 #[error("ZImagefile validation error: {message}")]
148 ZImagefileValidation {
149 message: String,
151 },
152
153 #[error("Pipeline error: {message}")]
155 PipelineError {
156 message: String,
158 },
159
160 #[error("WASM build error: {0}")]
162 WasmBuild(#[from] crate::wasm_builder::WasmBuildError),
163
164 #[error("Operation '{operation}' is not supported by this backend")]
166 NotSupported {
167 operation: String,
169 },
170}
171
172impl BuildError {
173 pub fn parse_error(msg: impl Into<String>, line: usize) -> Self {
175 Self::DockerfileParse {
176 message: msg.into(),
177 line,
178 }
179 }
180
181 pub fn context_read(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
183 Self::ContextRead {
184 path: path.into(),
185 source,
186 }
187 }
188
189 pub fn path_escape(path: impl Into<PathBuf>) -> Self {
191 Self::PathEscape { path: path.into() }
192 }
193
194 pub fn stage_not_found(name: impl Into<String>) -> Self {
196 Self::StageNotFound { name: name.into() }
197 }
198
199 pub fn run_failed(command: impl Into<String>, exit_code: i32) -> Self {
201 Self::RunFailed {
202 command: command.into(),
203 exit_code,
204 }
205 }
206
207 pub fn layer_create(msg: impl Into<String>) -> Self {
209 Self::LayerCreate {
210 message: msg.into(),
211 }
212 }
213
214 pub fn cache_error(msg: impl Into<String>) -> Self {
216 Self::CacheError {
217 message: msg.into(),
218 }
219 }
220
221 pub fn registry_error(msg: impl Into<String>) -> Self {
223 Self::RegistryError {
224 message: msg.into(),
225 }
226 }
227
228 pub fn invalid_instruction(instruction: impl Into<String>, reason: impl Into<String>) -> Self {
230 Self::InvalidInstruction {
231 instruction: instruction.into(),
232 reason: reason.into(),
233 }
234 }
235
236 pub fn buildah_execution(
238 command: impl Into<String>,
239 exit_code: i32,
240 stderr: impl Into<String>,
241 ) -> Self {
242 Self::BuildahExecution {
243 command: command.into(),
244 exit_code,
245 stderr: stderr.into(),
246 }
247 }
248
249 pub fn buildah_not_found(message: impl Into<String>) -> Self {
251 Self::BuildahNotFound {
252 message: message.into(),
253 }
254 }
255
256 pub fn zimagefile_parse(message: impl Into<String>) -> Self {
258 Self::ZImagefileParse {
259 message: message.into(),
260 }
261 }
262
263 pub fn zimagefile_validation(message: impl Into<String>) -> Self {
265 Self::ZImagefileValidation {
266 message: message.into(),
267 }
268 }
269
270 pub fn pipeline_error(message: impl Into<String>) -> Self {
272 Self::PipelineError {
273 message: message.into(),
274 }
275 }
276}
277
278pub type Result<T, E = BuildError> = std::result::Result<T, E>;
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_error_display() {
287 let err = BuildError::parse_error("unexpected token", 42);
288 assert!(err.to_string().contains("line 42"));
289 assert!(err.to_string().contains("unexpected token"));
290 }
291
292 #[test]
293 fn test_path_escape_error() {
294 let err = BuildError::path_escape("/etc/passwd");
295 assert!(err.to_string().contains("/etc/passwd"));
296 assert!(err.to_string().contains("escape"));
297 }
298
299 #[test]
300 fn test_run_failed_error() {
301 let err = BuildError::run_failed("apt-get install foo", 127);
302 assert!(err.to_string().contains("exit code 127"));
303 assert!(err.to_string().contains("apt-get install foo"));
304 }
305}