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
161impl BuildError {
162    /// Create a DockerfileParse error from a message and line number
163    pub fn parse_error(msg: impl Into<String>, line: usize) -> Self {
164        Self::DockerfileParse {
165            message: msg.into(),
166            line,
167        }
168    }
169
170    /// Create a ContextRead error from a path and IO error
171    pub fn context_read(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
172        Self::ContextRead {
173            path: path.into(),
174            source,
175        }
176    }
177
178    /// Create a PathEscape error
179    pub fn path_escape(path: impl Into<PathBuf>) -> Self {
180        Self::PathEscape { path: path.into() }
181    }
182
183    /// Create a StageNotFound error
184    pub fn stage_not_found(name: impl Into<String>) -> Self {
185        Self::StageNotFound { name: name.into() }
186    }
187
188    /// Create a RunFailed error
189    pub fn run_failed(command: impl Into<String>, exit_code: i32) -> Self {
190        Self::RunFailed {
191            command: command.into(),
192            exit_code,
193        }
194    }
195
196    /// Create a LayerCreate error
197    pub fn layer_create(msg: impl Into<String>) -> Self {
198        Self::LayerCreate {
199            message: msg.into(),
200        }
201    }
202
203    /// Create a CacheError
204    pub fn cache_error(msg: impl Into<String>) -> Self {
205        Self::CacheError {
206            message: msg.into(),
207        }
208    }
209
210    /// Create a RegistryError
211    pub fn registry_error(msg: impl Into<String>) -> Self {
212        Self::RegistryError {
213            message: msg.into(),
214        }
215    }
216
217    /// Create an InvalidInstruction error
218    pub fn invalid_instruction(instruction: impl Into<String>, reason: impl Into<String>) -> Self {
219        Self::InvalidInstruction {
220            instruction: instruction.into(),
221            reason: reason.into(),
222        }
223    }
224
225    /// Create a BuildahExecution error
226    pub fn buildah_execution(
227        command: impl Into<String>,
228        exit_code: i32,
229        stderr: impl Into<String>,
230    ) -> Self {
231        Self::BuildahExecution {
232            command: command.into(),
233            exit_code,
234            stderr: stderr.into(),
235        }
236    }
237
238    /// Create a BuildahNotFound error
239    pub fn buildah_not_found(message: impl Into<String>) -> Self {
240        Self::BuildahNotFound {
241            message: message.into(),
242        }
243    }
244
245    /// Create a ZImagefileParse error
246    pub fn zimagefile_parse(message: impl Into<String>) -> Self {
247        Self::ZImagefileParse {
248            message: message.into(),
249        }
250    }
251
252    /// Create a ZImagefileValidation error
253    pub fn zimagefile_validation(message: impl Into<String>) -> Self {
254        Self::ZImagefileValidation {
255            message: message.into(),
256        }
257    }
258
259    /// Create a PipelineError
260    pub fn pipeline_error(message: impl Into<String>) -> Self {
261        Self::PipelineError {
262            message: message.into(),
263        }
264    }
265}
266
267/// Result type alias for build operations
268pub type Result<T, E = BuildError> = std::result::Result<T, E>;
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_error_display() {
276        let err = BuildError::parse_error("unexpected token", 42);
277        assert!(err.to_string().contains("line 42"));
278        assert!(err.to_string().contains("unexpected token"));
279    }
280
281    #[test]
282    fn test_path_escape_error() {
283        let err = BuildError::path_escape("/etc/passwd");
284        assert!(err.to_string().contains("/etc/passwd"));
285        assert!(err.to_string().contains("escape"));
286    }
287
288    #[test]
289    fn test_run_failed_error() {
290        let err = BuildError::run_failed("apt-get install foo", 127);
291        assert!(err.to_string().contains("exit code 127"));
292        assert!(err.to_string().contains("apt-get install foo"));
293    }
294}