1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright (c) 2026 Hamze Ghalebi. All rights reserved.
// Licensed under the Rustlift Non-Commercial Licence v1.0.
//! Deployment error taxonomy for the Rustlift pipeline.
//!
//! This module defines a single, exhaustive error type — [`DeployError`] —
//! that every fallible function in the crate returns. Each variant maps to
//! a distinct failure domain (network, config, build, etc.), and the
//! [`crate::resilience::reliable_op`] wrapper uses this taxonomy to decide
//! whether to **retry** or **abort immediately**.
//!
//! # Learning: Why a Single Error Enum?
//!
//! In Rust, error handling follows a principle: **errors are values**, not
//! exceptions. By collecting every failure mode into one enum, callers can
//! pattern-match exhaustively. The compiler guarantees that you have
//! handled every case — no uncaught exceptions, no surprise panics.
//!
//! The `#[derive(Error)]` macro from the [`thiserror`] crate generates
//! the boilerplate implementations of [`std::fmt::Display`] and
//! [`std::error::Error`] automatically, keeping this file concise.
//!
//! # Fatal vs. Transient
//!
//! The retry layer in [`crate::resilience`] uses a simple rule:
//!
//! | Category | Variants | Behaviour |
//! |---------------|------------------------------------------------|-------------------|
//! | **Fatal** | `Config`, `Dependency`, `Build`, `PathEncoding` | Abort immediately |
//! | **Transient** | Everything else | Retry with backoff|
//!
//! Fatal errors stem from **user mistakes** (wrong config, missing tools)
//! that no amount of retrying will fix. Transient errors stem from
//! **external systems** (network blips, Azure throttling) that often
//! resolve on their own.
use Error;
/// Exhaustive error type for every failure mode in the deployment pipeline.
///
/// # Design: The `thiserror` Derive Macro
///
/// The `#[derive(Error)]` attribute auto-generates:
/// - `impl Display` using the `#[error("...")]` format strings.
/// - `impl Error` with proper `source()` chaining via `#[from]` and `#[source]`.
///
/// This eliminates hundreds of lines of boilerplate while keeping errors
/// strongly typed and pattern-matchable.
///
/// # Examples
///
/// Creating and displaying an error:
///
/// ```
/// use rustlift::errors::DeployError;
///
/// let missing_env = DeployError::Config("Missing AZURE_SUBSCRIPTION_ID".into());
/// assert_eq!(
/// missing_env.to_string().contains("Configuration Error"),
/// true
/// );
/// ```
///
/// Pattern-matching to provide recovery hints:
///
/// ```
/// use rustlift::errors::DeployError;
///
/// let error = DeployError::Dependency("Azure CLI ('az') not found".into());
///
/// let hint = match &error {
/// DeployError::Dependency(msg) => msg.as_str(),
/// _ => "unreachable",
/// };
///
/// assert_eq!(hint, "Azure CLI ('az') not found");
/// ```
///
/// # Panics
///
/// Constructing and matching on [`DeployError`] does not panic.
///
/// # Safety
///
/// This enum is safe to construct and pattern-match. It does not expose
/// unsafe memory or lifetime requirements.
/// Convenience alias used throughout the pipeline.
///
/// Every fallible function in this crate returns `Result<T>` instead of
/// the verbose `std::result::Result<T, DeployError>`. This is a common
/// Rust idiom — crates define their own `Result` type to reduce visual
/// noise.
///
/// # Learning: Type Aliases
///
/// A type alias does not create a new type; it is purely syntactic sugar.
/// `Result<String>` and `std::result::Result<String, DeployError>` are
/// identical to the compiler. The advantage is readability.
///
/// # Examples
///
/// ```
/// use rustlift::errors::{DeployError, Result};
///
/// fn checked(input: i32) -> Result<i32> {
/// if input >= 0 {
/// Ok(input)
/// } else {
/// Err(DeployError::Config("input must be non-negative".into()))
/// }
/// }
///
/// assert_eq!(checked(7).unwrap(), 7);
/// assert_eq!(checked(-1).is_err(), true);
/// ```
///
/// # Panics
///
/// The alias itself cannot panic.
///
/// # Safety
///
/// This type alias has no unsafe preconditions.
pub type Result<T> = Result;