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
161impl BuildError {
162 pub fn parse_error(msg: impl Into<String>, line: usize) -> Self {
164 Self::DockerfileParse {
165 message: msg.into(),
166 line,
167 }
168 }
169
170 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 pub fn path_escape(path: impl Into<PathBuf>) -> Self {
180 Self::PathEscape { path: path.into() }
181 }
182
183 pub fn stage_not_found(name: impl Into<String>) -> Self {
185 Self::StageNotFound { name: name.into() }
186 }
187
188 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 pub fn layer_create(msg: impl Into<String>) -> Self {
198 Self::LayerCreate {
199 message: msg.into(),
200 }
201 }
202
203 pub fn cache_error(msg: impl Into<String>) -> Self {
205 Self::CacheError {
206 message: msg.into(),
207 }
208 }
209
210 pub fn registry_error(msg: impl Into<String>) -> Self {
212 Self::RegistryError {
213 message: msg.into(),
214 }
215 }
216
217 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 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 pub fn buildah_not_found(message: impl Into<String>) -> Self {
240 Self::BuildahNotFound {
241 message: message.into(),
242 }
243 }
244
245 pub fn zimagefile_parse(message: impl Into<String>) -> Self {
247 Self::ZImagefileParse {
248 message: message.into(),
249 }
250 }
251
252 pub fn zimagefile_validation(message: impl Into<String>) -> Self {
254 Self::ZImagefileValidation {
255 message: message.into(),
256 }
257 }
258
259 pub fn pipeline_error(message: impl Into<String>) -> Self {
261 Self::PipelineError {
262 message: message.into(),
263 }
264 }
265}
266
267pub 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}