panopticon_core/error.rs
1/// Errors produced while building or compiling a draft pipeline.
2///
3/// `DraftError` is returned by construction methods on `Pipeline<Draft>`
4/// and by `Pipeline::compile`. Callers should match on the variants
5/// when they want to surface specific failures (duplicate names,
6/// unresolved references) with custom diagnostics — for example when a
7/// loader is translating external configuration into a pipeline.
8#[derive(Debug, thiserror::Error, PartialEq)]
9pub enum DraftError {
10 /// A [`Pipeline::var`](crate::prelude::Pipeline#method.var) call used
11 /// a name already bound to another variable.
12 #[error("A variable named '{name}' already exists in the pipeline")]
13 DuplicateVariable {
14 /// The conflicting variable name.
15 name: String,
16 },
17
18 /// A [`Pipeline::step`](crate::prelude::Pipeline#method.step) call
19 /// used a name already bound to another step.
20 #[error("A step named '{name}' already exists in the pipeline")]
21 DuplicateStep {
22 /// The conflicting step name.
23 name: String,
24 },
25
26 /// A `Pipeline::returns` call used a name already bound to another
27 /// return block.
28 #[error("A return named '{name}' already exists in the pipeline")]
29 DuplicateReturn {
30 /// The conflicting return-block name.
31 name: String,
32 },
33
34 /// Compile-time simulation found a step parameter referencing a
35 /// store entry that is not produced by any preceding step.
36 #[error(
37 "Step '{step}' references '{reference}' which is not available at that point in the pipeline"
38 )]
39 UnresolvedReference {
40 /// The step whose parameters contained the bad reference.
41 step: String,
42 /// The dotted store path that failed to resolve.
43 reference: String,
44 },
45
46 /// Compile-time simulation found a return-block parameter
47 /// referencing a store entry that is not available at the end of
48 /// the pipeline.
49 #[error(
50 "Return '{returns}' references '{reference}' which is not available at the end of the pipeline"
51 )]
52 UnresolvedReturn {
53 /// The return block whose parameters contained the bad
54 /// reference.
55 returns: String,
56 /// The dotted store path that failed to resolve.
57 reference: String,
58 },
59
60 /// Operation metadata failed validation when the operation was
61 /// registered — typically a [`NameSpec::DerivedFrom`](crate::extend::NameSpec)
62 /// referencing a missing input or an input of the wrong type.
63 #[error("Invalid operation metadata for '{operation}': {reason}")]
64 InvalidMetadata {
65 /// The operation whose metadata failed validation.
66 operation: &'static str,
67 /// Human-readable explanation of the failure.
68 reason: String,
69 },
70
71 /// A step declared an extension requirement that is not registered
72 /// on the pipeline.
73 #[error(
74 "Step '{step}' (operation '{operation}') requires extension '{extension}' which is not registered on the pipeline"
75 )]
76 MissingExtension {
77 /// The step whose operation requires the extension.
78 step: String,
79 /// The operation name from its metadata.
80 operation: &'static str,
81 /// The extension name that should be registered via
82 /// [`Pipeline::extension`](crate::prelude::Pipeline#method.extension).
83 extension: String,
84 },
85}
86
87/// Errors produced while executing a running pipeline.
88///
89/// Returned by `Pipeline::wait` when the worker thread reports failure,
90/// and surfaced through `OperationError` variants in custom operations.
91/// Iteration and guard failures wrap the underlying cause in the
92/// [`Iteration`](Self::Iteration) and [`Guard`](Self::Guard) variants so
93/// the trace preserves the scope in which the failure occurred.
94#[derive(Debug, thiserror::Error, PartialEq)]
95pub enum OperationError {
96 /// Parameter resolution tried to look up a dotted store path that
97 /// did not exist at runtime.
98 #[error("Reference '{reference}' not found in store")]
99 ReferenceNotFound {
100 /// The dotted store path that failed to resolve.
101 reference: String,
102 },
103
104 /// A [`Param::Template`](crate::prelude::Param::Template) contained
105 /// a part that resolved to an array or map instead of a scalar.
106 #[error("Template parts must resolve to scalar values")]
107 InvalidTemplatePart,
108
109 /// Resolving a parameter into the runtime store failed — typically
110 /// because the target key was already occupied.
111 #[error("Failed to resolve parameter '{parameter}': {reason}")]
112 ParameterResolutionFailed {
113 /// The parameter name being resolved.
114 parameter: String,
115 /// Human-readable explanation from the underlying store error.
116 reason: String,
117 },
118
119 /// A custom operation called `Context::input` with a name that was
120 /// not declared in its [`OperationMetadata`](crate::extend::OperationMetadata).
121 #[error("Operation '{operation}' has no declared input '{input}'")]
122 UndeclaredInput {
123 /// The operation name from its metadata.
124 operation: &'static str,
125 /// The undeclared input name that was requested.
126 input: String,
127 },
128
129 /// A custom operation called `Context::set_static_output` with a
130 /// name not present as a [`NameSpec::Static`](crate::extend::NameSpec)
131 /// output in its metadata.
132 #[error("Operation '{operation}' has no declared static output '{output}'")]
133 UndeclaredOutput {
134 /// The operation name from its metadata.
135 operation: &'static str,
136 /// The undeclared static output name.
137 output: String,
138 },
139
140 /// A custom operation called `Context::set_derived_output` with an
141 /// input name not referenced by any derived output in its metadata.
142 #[error("Operation '{operation}' has no declared derived output from input '{input}'")]
143 UndeclaredDerivedOutput {
144 /// The operation name from its metadata.
145 operation: &'static str,
146 /// The input name the derived output should have come from.
147 input: String,
148 },
149
150 /// An iteration node resolved its [`IterSource`](crate::prelude::IterSource)
151 /// reference to nothing at runtime.
152 #[error("Iteration '{iter_name}': source '{source_ref}' not found")]
153 IterSourceNotFound {
154 /// The iteration node's name.
155 iter_name: String,
156 /// The store path that failed to resolve.
157 source_ref: String,
158 },
159
160 /// An iteration source resolved to an entry of the wrong shape —
161 /// an array was expected but a map (or scalar) was found, or vice
162 /// versa.
163 #[error("Iteration '{iter_name}': source '{source_ref}' is not {expected}")]
164 IterSourceTypeMismatch {
165 /// The iteration node's name.
166 iter_name: String,
167 /// The store path whose entry had the wrong shape.
168 source_ref: String,
169 /// The shape the iteration expected (`"array"` or `"map"`).
170 expected: &'static str,
171 },
172
173 /// A step inside an iteration body failed. Wraps the underlying
174 /// error so the outer cause includes the iteration name, current
175 /// index/key, and nesting depth.
176 #[error("In iteration '{iter_name}' at index {index}: {source}")]
177 Iteration {
178 /// The enclosing iteration's name.
179 iter_name: String,
180 /// The current index or key as rendered by
181 /// [`IterIndex::fmt`](crate::extend::IterIndex).
182 index: String,
183 /// Nesting depth of the iteration (outermost is `0`).
184 depth: usize,
185 /// The underlying error from the inner step.
186 #[source]
187 source: Box<OperationError>,
188 },
189
190 /// A traversal of a [`StoreEntry`](crate::prelude::StoreEntry) failed.
191 #[error("Access error: {0}")]
192 AccessError(#[from] AccessError),
193
194 /// A [`Store`](crate::prelude::Store) operation failed at runtime.
195 #[error("Store error: {0}")]
196 StoreError(#[from] StoreError),
197
198 /// The runner tried to look up the parameters for a step or return
199 /// block that was never recorded at draft time — this indicates a
200 /// bug in the caller and is effectively unreachable through normal
201 /// construction APIs.
202 #[error("No parameters found for '{name}'")]
203 MissingParameters {
204 /// The missing step or return-block name.
205 name: String,
206 },
207
208 /// A hook interceptor returned [`HookAction::Abort`](crate::extend::HookAction::Abort).
209 #[error("Hook '{hook}' aborted execution: {reason}")]
210 HookAbort {
211 /// The hook that aborted the pipeline.
212 hook: String,
213 /// The reason returned by the interceptor.
214 reason: String,
215 },
216
217 /// `Pipeline::cancel` was observed between steps and the worker
218 /// exited early.
219 #[error("Operation cancelled")]
220 Cancelled,
221
222 /// The worker thread panicked. `Pipeline::wait` turns the panic
223 /// into this variant rather than unwinding into the caller.
224 #[error("Execution thread panicked")]
225 ThreadPanic,
226
227 /// `Context::extension` could not find the requested extension —
228 /// either the operation did not declare it, or no extension was
229 /// registered under that name and type.
230 #[error("Extension '{extension}' not found for operation '{operation}'")]
231 ExtensionNotFound {
232 /// The operation requesting the extension.
233 operation: String,
234 /// The extension name that could not be resolved.
235 extension: String,
236 },
237
238 /// A [`GuardSource`](crate::prelude::GuardSource) resolved to an
239 /// entry that was not a [`Value::Boolean`](crate::prelude::Value::Boolean).
240 #[error("Guard '{guard_name}': reference '{reference}' is not a boolean")]
241 GuardTypeMismatch {
242 /// The guard node's name.
243 guard_name: String,
244 /// The store path whose entry was not a boolean.
245 reference: String,
246 },
247
248 /// A step inside a guard body failed. Wraps the underlying error so
249 /// the outer cause includes the guard name.
250 #[error("In guard '{guard_name}': {source}")]
251 Guard {
252 /// The enclosing guard's name.
253 guard_name: String,
254 /// The underlying error from the inner step.
255 #[source]
256 source: Box<OperationError>,
257 },
258
259 /// A custom operation emitted an ad-hoc error via
260 /// [`Context::error`](crate::extend::Context) or the
261 /// [`op_error!`](crate::op_error) macro.
262 #[error("[{operation}] {message}")]
263 Custom {
264 /// The operation name (from metadata) that emitted the error.
265 operation: String,
266 /// The operation-supplied message.
267 message: String,
268 },
269}
270
271/// Errors produced by [`Store`](crate::prelude::Store) key management.
272#[derive(Debug, thiserror::Error, PartialEq)]
273pub enum StoreError {
274 /// A call to [`Store::insert`](crate::prelude::Store::insert) or a
275 /// `merge` collided with an existing entry under the same name.
276 #[error("An entry with the name '{0}' already exists in the store.")]
277 EntryAlreadyExists(String),
278 /// A call to [`Store::get`](crate::prelude::Store::get) did not find
279 /// the requested name.
280 #[error("No entry found in the store with the name '{0}'.")]
281 EntryNotFound(String),
282}
283
284/// Errors produced while narrowing or traversing a
285/// [`StoreEntry`](crate::prelude::StoreEntry) or
286/// [`Value`](crate::prelude::Value).
287#[derive(Debug, thiserror::Error, PartialEq)]
288pub enum AccessError {
289 /// A nested lookup could not find the given key.
290 #[error("Entry '{0}' not found")]
291 NotFound(String),
292
293 /// A map lookup failed — reserved for contexts where a bare key is
294 /// the most useful piece of information.
295 #[error("Key '{0}' not found")]
296 KeyNotFound(String),
297
298 /// An array index was out of range.
299 #[error("Index {0} out of bounds")]
300 IndexOutOfBounds(usize),
301
302 /// A `var` accessor was called on an array or map entry. The inner
303 /// `&'static str` is the name of the actual variant for diagnostics.
304 #[error("Expected var, found {0}")]
305 NotAVar(&'static str),
306
307 /// An `array` accessor was called on a var or map entry.
308 #[error("Expected array, found {0}")]
309 NotAnArray(&'static str),
310
311 /// A `map` accessor was called on a var or array entry.
312 #[error("Expected map, found {0}")]
313 NotAMap(&'static str),
314
315 /// A typed [`Value`](crate::prelude::Value) accessor (`as_text`,
316 /// `as_integer`, etc.) was called on the wrong variant.
317 #[error("Type mismatch: expected {expected}, found {found}")]
318 TypeMismatch {
319 /// The type name the caller asked for.
320 expected: &'static str,
321 /// The type name the value actually had.
322 found: &'static str,
323 },
324}
325
326/// Builds an [`OperationError::Custom`] from format arguments, tagged
327/// with the operation name taken from a [`Context`](crate::extend::Context).
328///
329/// Thin wrapper over `Context::error` that lets custom operations use
330/// `format!`-style syntax without the `format!` call. The first
331/// argument is the context (`$ctx`); the rest are passed verbatim to
332/// `format!`.
333///
334/// ```no_run
335/// use panopticon_core::extend::*;
336///
337/// fn execute(ctx: &mut Context) -> Result<(), OperationError> {
338/// let key: i64 = 42;
339/// Err(op_error!(ctx, "key {} out of range", key))
340/// }
341/// ```
342#[macro_export]
343macro_rules! op_error {
344 ($ctx:expr, $($arg:tt)*) => {
345 $ctx.error(format!($($arg)*))
346 };
347}