Skip to main content

zlayer_builder/
error.rs

1//! Builder error types
2//!
3//! This module defines all error types for the Dockerfile builder subsystem,
4//! covering parsing, context handling, build execution, and caching operations.
5
6use std::path::PathBuf;
7use thiserror::Error;
8
9/// Build-specific errors
10#[derive(Debug, Error)]
11pub enum BuildError {
12    /// Dockerfile parsing failed
13    #[error("Dockerfile parse error at line {line}: {message}")]
14    DockerfileParse {
15        /// The underlying parsing error message
16        message: String,
17        /// Line number where the error occurred (1-indexed)
18        line: usize,
19    },
20
21    /// Failed to read build context
22    #[error("Failed to read build context at '{path}': {source}")]
23    ContextRead {
24        /// Path that could not be read
25        path: PathBuf,
26        /// Underlying IO error
27        source: std::io::Error,
28    },
29
30    /// Path escape attempt detected (security violation)
31    #[error("Path escape attempt: '{path}' escapes build context")]
32    PathEscape {
33        /// The offending path
34        path: PathBuf,
35    },
36
37    /// File was ignored by .dockerignore
38    #[error("File '{path}' is ignored by .dockerignore")]
39    FileIgnored {
40        /// The ignored file path
41        path: PathBuf,
42    },
43
44    /// Referenced stage not found
45    #[error("Stage '{name}' not found in Dockerfile")]
46    StageNotFound {
47        /// The stage name or index that was referenced
48        name: String,
49    },
50
51    /// RUN instruction failed
52    #[error("RUN command failed with exit code {exit_code}: {command}")]
53    RunFailed {
54        /// The command that failed
55        command: String,
56        /// Exit code returned by the command
57        exit_code: i32,
58    },
59
60    /// Failed to create layer
61    #[error("Failed to create layer: {message}")]
62    LayerCreate {
63        /// Underlying error description
64        message: String,
65    },
66
67    /// Cache operation failed
68    #[error("Cache error: {message}")]
69    CacheError {
70        /// Underlying cache error
71        message: String,
72    },
73
74    /// Registry operation failed
75    #[error("Registry error: {message}")]
76    RegistryError {
77        /// Underlying registry error
78        message: String,
79    },
80
81    /// IO error
82    #[error("IO error: {0}")]
83    IoError(#[from] std::io::Error),
84
85    /// Variable expansion failed
86    #[error("Variable expansion failed: {0}")]
87    VariableExpansion(String),
88
89    /// Invalid instruction
90    #[error("Invalid instruction '{instruction}': {reason}")]
91    InvalidInstruction {
92        /// The instruction that was invalid
93        instruction: String,
94        /// Reason why it was invalid
95        reason: String,
96    },
97
98    /// Buildah command execution failed
99    #[error("Buildah execution failed: {command} (exit code {exit_code}): {stderr}")]
100    BuildahExecution {
101        /// The buildah command that failed
102        command: String,
103        /// Exit code from buildah
104        exit_code: i32,
105        /// Standard error output
106        stderr: String,
107    },
108
109    /// Build context too large
110    #[error("Build context too large: {size} bytes (max: {max} bytes)")]
111    ContextTooLarge {
112        /// Actual size in bytes
113        size: u64,
114        /// Maximum allowed size
115        max: u64,
116    },
117
118    /// Base image not found
119    #[error("Base image not found: {image}")]
120    BaseImageNotFound {
121        /// The image reference that was not found
122        image: String,
123    },
124
125    /// Circular dependency in multi-stage build
126    #[error("Circular dependency detected in multi-stage build: {stages:?}")]
127    CircularDependency {
128        /// The stages involved in the cycle
129        stages: Vec<String>,
130    },
131
132    /// Buildah binary not found or installation failed
133    #[error("Buildah not found: {message}")]
134    BuildahNotFound {
135        /// Details about the failure
136        message: String,
137    },
138
139    /// `ZImagefile` YAML deserialization failed
140    #[error("ZImagefile parse error: {message}")]
141    ZImagefileParse {
142        /// The underlying YAML parse error message
143        message: String,
144    },
145
146    /// `ZImagefile` semantic validation failed
147    #[error("ZImagefile validation error: {message}")]
148    ZImagefileValidation {
149        /// Description of what validation rule was violated
150        message: String,
151    },
152
153    /// Pipeline validation or execution error
154    #[error("Pipeline error: {message}")]
155    PipelineError {
156        /// Description of the pipeline error
157        message: String,
158    },
159
160    /// WASM build failed
161    #[error("WASM build error: {0}")]
162    WasmBuild(#[from] crate::wasm_builder::WasmBuildError),
163
164    /// Operation not supported by this backend
165    #[error("Operation '{operation}' is not supported by this backend")]
166    NotSupported {
167        /// The operation that was attempted
168        operation: String,
169    },
170}
171
172impl BuildError {
173    /// Create a `DockerfileParse` error from a message and line number
174    pub fn parse_error(msg: impl Into<String>, line: usize) -> Self {
175        Self::DockerfileParse {
176            message: msg.into(),
177            line,
178        }
179    }
180
181    /// Create a `ContextRead` error from a path and IO error
182    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    /// Create a `PathEscape` error
190    pub fn path_escape(path: impl Into<PathBuf>) -> Self {
191        Self::PathEscape { path: path.into() }
192    }
193
194    /// Create a `StageNotFound` error
195    pub fn stage_not_found(name: impl Into<String>) -> Self {
196        Self::StageNotFound { name: name.into() }
197    }
198
199    /// Create a `RunFailed` error
200    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    /// Create a `LayerCreate` error
208    pub fn layer_create(msg: impl Into<String>) -> Self {
209        Self::LayerCreate {
210            message: msg.into(),
211        }
212    }
213
214    /// Create a `CacheError`
215    pub fn cache_error(msg: impl Into<String>) -> Self {
216        Self::CacheError {
217            message: msg.into(),
218        }
219    }
220
221    /// Create a `RegistryError`
222    pub fn registry_error(msg: impl Into<String>) -> Self {
223        Self::RegistryError {
224            message: msg.into(),
225        }
226    }
227
228    /// Create an `InvalidInstruction` error
229    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    /// Create a `BuildahExecution` error
237    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    /// Create a `BuildahNotFound` error
250    pub fn buildah_not_found(message: impl Into<String>) -> Self {
251        Self::BuildahNotFound {
252            message: message.into(),
253        }
254    }
255
256    /// Create a `ZImagefileParse` error
257    pub fn zimagefile_parse(message: impl Into<String>) -> Self {
258        Self::ZImagefileParse {
259            message: message.into(),
260        }
261    }
262
263    /// Create a `ZImagefileValidation` error
264    pub fn zimagefile_validation(message: impl Into<String>) -> Self {
265        Self::ZImagefileValidation {
266            message: message.into(),
267        }
268    }
269
270    /// Create a `PipelineError`
271    pub fn pipeline_error(message: impl Into<String>) -> Self {
272        Self::PipelineError {
273            message: message.into(),
274        }
275    }
276}
277
278/// Result type alias for build operations
279pub 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}