flow_gates/
error.rs

1//! Error types for gate operations.
2//!
3//! This module defines `GateError`, a comprehensive error type for all gate-related
4//! operations. It uses `thiserror` for convenient error construction and implements
5//! standard error traits for integration with error handling libraries.
6
7use std::error::Error as StdError;
8use thiserror::Error;
9
10/// Custom error type for gate operations.
11///
12/// All gate operations return `Result<T, GateError>`. The error type provides
13/// detailed context about what went wrong, making debugging easier.
14#[derive(Debug, Error)]
15pub enum GateError {
16    /// Geometry validation failures
17    #[error("Invalid geometry: {message}")]
18    InvalidGeometry { message: String },
19
20    /// Missing required parameter/channel
21    #[error("Missing parameter '{parameter}' in context: {context}")]
22    MissingParameter { parameter: String, context: String },
23
24    /// Invalid coordinate values
25    #[error("Invalid coordinate '{coordinate}': value {value} is not finite or out of range")]
26    InvalidCoordinate { coordinate: String, value: f32 },
27
28    /// Event filtering failures
29    #[error("Filtering error: {message}")]
30    FilteringError { message: String },
31
32    /// Hierarchy operation failures
33    #[error("Hierarchy error: {message}")]
34    HierarchyError { message: String },
35
36    /// Serialization/deserialization errors
37    #[error("Serialization error: {0}")]
38    SerializationError(#[from] serde_json::Error),
39
40    /// EventIndex build/query errors
41    #[error("Index error: {message}")]
42    IndexError { message: String },
43
44    /// Generic error with context (for wrapping other errors)
45    #[error("{message}")]
46    Other {
47        message: String,
48        #[source]
49        source: Option<Box<dyn StdError + Send + Sync>>,
50    },
51
52    /// Hierarchy cycle detection
53    #[error("Hierarchy cycle detected: adding '{gate_id}' as parent of '{would_create_cycle_to}' would create a cycle")]
54    HierarchyCycle {
55        gate_id: String,
56        would_create_cycle_to: String,
57    },
58
59    /// Invalid boolean operation configuration
60    #[error("Invalid boolean operation '{operation}': expected {expected_count} operand(s), got {operand_count}")]
61    InvalidBooleanOperation {
62        operation: String,
63        operand_count: usize,
64        expected_count: usize,
65    },
66
67    /// Referenced gate not found
68    #[error("Gate '{gate_id}' not found: {context}")]
69    GateNotFound {
70        gate_id: String,
71        context: String,
72    },
73
74    /// Invalid gate link operation
75    #[error("Invalid link from '{linking_gate_id}' to '{target_gate_id}': {reason}")]
76    InvalidLink {
77        target_gate_id: String,
78        linking_gate_id: String,
79        reason: String,
80    },
81
82    /// Cannot reparent gate
83    #[error("Cannot reparent gate '{gate_id}' to '{new_parent_id}': {reason}")]
84    CannotReparent {
85        gate_id: String,
86        new_parent_id: String,
87        reason: String,
88    },
89
90    /// Invalid subtree operation
91    #[error("Invalid subtree operation '{operation}' on gate '{gate_id}': {reason}")]
92    InvalidSubtreeOperation {
93        gate_id: String,
94        operation: String,
95        reason: String,
96    },
97
98    /// Boolean operation with no operands
99    #[error("Boolean operation '{operation}' requires at least one operand")]
100    EmptyOperands {
101        operation: String,
102    },
103
104    /// Builder in invalid state
105    #[error("Builder field '{field}' is invalid: {reason}")]
106    InvalidBuilderState {
107        field: String,
108        reason: String,
109    },
110
111    /// Duplicate gate ID
112    #[error("Gate ID '{gate_id}' already exists")]
113    DuplicateGateId {
114        gate_id: String,
115    },
116}
117
118impl GateError {
119    /// Create an InvalidGeometry error with a message
120    pub fn invalid_geometry(message: impl Into<String>) -> Self {
121        Self::InvalidGeometry {
122            message: message.into(),
123        }
124    }
125
126    /// Create a MissingParameter error
127    pub fn missing_parameter(parameter: impl Into<String>, context: impl Into<String>) -> Self {
128        Self::MissingParameter {
129            parameter: parameter.into(),
130            context: context.into(),
131        }
132    }
133
134    /// Create an InvalidCoordinate error
135    pub fn invalid_coordinate(coordinate: impl Into<String>, value: f32) -> Self {
136        Self::InvalidCoordinate {
137            coordinate: coordinate.into(),
138            value,
139        }
140    }
141
142    /// Create a FilteringError with a message
143    pub fn filtering_error(message: impl Into<String>) -> Self {
144        Self::FilteringError {
145            message: message.into(),
146        }
147    }
148
149    /// Create a HierarchyError with a message
150    pub fn hierarchy_error(message: impl Into<String>) -> Self {
151        Self::HierarchyError {
152            message: message.into(),
153        }
154    }
155
156    /// Create an IndexError with a message
157    pub fn index_error(message: impl Into<String>) -> Self {
158        Self::IndexError {
159            message: message.into(),
160        }
161    }
162
163    /// Create a HierarchyCycle error
164    pub fn hierarchy_cycle(gate_id: impl Into<String>, would_create_cycle_to: impl Into<String>) -> Self {
165        Self::HierarchyCycle {
166            gate_id: gate_id.into(),
167            would_create_cycle_to: would_create_cycle_to.into(),
168        }
169    }
170
171    /// Create an InvalidBooleanOperation error
172    pub fn invalid_boolean_operation(
173        operation: impl Into<String>,
174        operand_count: usize,
175        expected_count: usize,
176    ) -> Self {
177        Self::InvalidBooleanOperation {
178            operation: operation.into(),
179            operand_count,
180            expected_count,
181        }
182    }
183
184    /// Create a GateNotFound error
185    pub fn gate_not_found(gate_id: impl Into<String>, context: impl Into<String>) -> Self {
186        Self::GateNotFound {
187            gate_id: gate_id.into(),
188            context: context.into(),
189        }
190    }
191
192    /// Create an InvalidLink error
193    pub fn invalid_link(
194        target_gate_id: impl Into<String>,
195        linking_gate_id: impl Into<String>,
196        reason: impl Into<String>,
197    ) -> Self {
198        Self::InvalidLink {
199            target_gate_id: target_gate_id.into(),
200            linking_gate_id: linking_gate_id.into(),
201            reason: reason.into(),
202        }
203    }
204
205    /// Create a CannotReparent error
206    pub fn cannot_reparent(
207        gate_id: impl Into<String>,
208        new_parent_id: impl Into<String>,
209        reason: impl Into<String>,
210    ) -> Self {
211        Self::CannotReparent {
212            gate_id: gate_id.into(),
213            new_parent_id: new_parent_id.into(),
214            reason: reason.into(),
215        }
216    }
217
218    /// Create an InvalidSubtreeOperation error
219    pub fn invalid_subtree_operation(
220        gate_id: impl Into<String>,
221        operation: impl Into<String>,
222        reason: impl Into<String>,
223    ) -> Self {
224        Self::InvalidSubtreeOperation {
225            gate_id: gate_id.into(),
226            operation: operation.into(),
227            reason: reason.into(),
228        }
229    }
230
231    /// Create an EmptyOperands error
232    pub fn empty_operands(operation: impl Into<String>) -> Self {
233        Self::EmptyOperands {
234            operation: operation.into(),
235        }
236    }
237
238    /// Create an InvalidBuilderState error
239    pub fn invalid_builder_state(field: impl Into<String>, reason: impl Into<String>) -> Self {
240        Self::InvalidBuilderState {
241            field: field.into(),
242            reason: reason.into(),
243        }
244    }
245
246    /// Create a DuplicateGateId error
247    pub fn duplicate_gate_id(gate_id: impl Into<String>) -> Self {
248        Self::DuplicateGateId {
249            gate_id: gate_id.into(),
250        }
251    }
252
253    /// Add context to an error
254    pub fn with_context(self, context: impl Into<String>) -> Self {
255        match self {
256            Self::InvalidGeometry { message } => Self::InvalidGeometry {
257                message: format!("{}: {}", context.into(), message),
258            },
259            Self::MissingParameter {
260                parameter,
261                context: ctx,
262            } => Self::MissingParameter {
263                parameter,
264                context: format!("{}: {}", context.into(), ctx),
265            },
266            Self::InvalidCoordinate { coordinate, value } => {
267                Self::InvalidCoordinate { coordinate, value }
268            }
269            Self::FilteringError { message } => Self::FilteringError {
270                message: format!("{}: {}", context.into(), message),
271            },
272            Self::HierarchyError { message } => Self::HierarchyError {
273                message: format!("{}: {}", context.into(), message),
274            },
275            Self::SerializationError(e) => Self::Other {
276                message: format!("{}: {}", context.into(), e),
277                source: Some(Box::new(e)),
278            },
279            Self::IndexError { message } => Self::IndexError {
280                message: format!("{}: {}", context.into(), message),
281            },
282            Self::HierarchyCycle { gate_id, would_create_cycle_to } => Self::HierarchyCycle {
283                gate_id,
284                would_create_cycle_to,
285            },
286            Self::InvalidBooleanOperation { operation, operand_count, expected_count } => {
287                Self::InvalidBooleanOperation {
288                    operation,
289                    operand_count,
290                    expected_count,
291                }
292            }
293            Self::GateNotFound { gate_id, context: ctx } => Self::GateNotFound {
294                gate_id,
295                context: format!("{}: {}", context.into(), ctx),
296            },
297            Self::InvalidLink { target_gate_id, linking_gate_id, reason } => Self::InvalidLink {
298                target_gate_id,
299                linking_gate_id,
300                reason: format!("{}: {}", context.into(), reason),
301            },
302            Self::CannotReparent { gate_id, new_parent_id, reason } => Self::CannotReparent {
303                gate_id,
304                new_parent_id,
305                reason: format!("{}: {}", context.into(), reason),
306            },
307            Self::InvalidSubtreeOperation { gate_id, operation, reason } => {
308                Self::InvalidSubtreeOperation {
309                    gate_id,
310                    operation,
311                    reason: format!("{}: {}", context.into(), reason),
312                }
313            }
314            Self::EmptyOperands { operation } => Self::EmptyOperands { operation },
315            Self::InvalidBuilderState { field, reason } => Self::InvalidBuilderState {
316                field,
317                reason: format!("{}: {}", context.into(), reason),
318            },
319            Self::DuplicateGateId { gate_id } => Self::DuplicateGateId { gate_id },
320            Self::Other { message, source } => Self::Other {
321                message: format!("{}: {}", context.into(), message),
322                source,
323            },
324        }
325    }
326}
327
328// Conversion from anyhow::Error for convenience
329impl From<anyhow::Error> for GateError {
330    fn from(err: anyhow::Error) -> Self {
331        Self::Other {
332            message: err.to_string(),
333            source: None, // anyhow::Error already contains the full context
334        }
335    }
336}
337
338// Conversion from quick_xml errors for GatingML parsing
339impl From<quick_xml::Error> for GateError {
340    fn from(err: quick_xml::Error) -> Self {
341        Self::Other {
342            message: format!("XML parsing error: {}", err),
343            source: Some(Box::new(err)),
344        }
345    }
346}
347
348// Conversion from std::io::Error for GatingML writing
349impl From<std::io::Error> for GateError {
350    fn from(err: std::io::Error) -> Self {
351        Self::Other {
352            message: format!("IO error: {}", err),
353            source: Some(Box::new(err)),
354        }
355    }
356}
357
358// Type alias for Result using GateError
359pub type Result<T> = std::result::Result<T, GateError>;