Skip to main content

orcs_runtime/channel/
error.rs

1//! Channel layer errors.
2//!
3//! This module defines errors for the Channel layer.
4//! All errors implement [`ErrorCode`] for standardized handling.
5//!
6//! # Error Codes
7//!
8//! | Variant | Code | Recoverable |
9//! |---------|------|-------------|
10//! | [`ChannelError::NotFound`] | `CHANNEL_NOT_FOUND` | No |
11//! | [`ChannelError::InvalidState`] | `CHANNEL_INVALID_STATE` | No |
12//! | [`ChannelError::ParentNotFound`] | `CHANNEL_PARENT_NOT_FOUND` | No |
13//! | [`ChannelError::AlreadyExists`] | `CHANNEL_ALREADY_EXISTS` | No |
14//!
15//! # Example
16//!
17//! ```
18//! use orcs_runtime::ChannelError;
19//! use orcs_types::{ChannelId, ErrorCode};
20//!
21//! let err = ChannelError::NotFound(ChannelId::new());
22//!
23//! // Machine-readable code for programmatic handling
24//! assert_eq!(err.code(), "CHANNEL_NOT_FOUND");
25//!
26//! // Recoverability info for retry logic
27//! assert!(!err.is_recoverable());
28//! ```
29
30use orcs_types::{ChannelId, ErrorCode};
31use thiserror::Error;
32
33/// Channel layer error.
34///
35/// Represents failures within the Channel management layer.
36/// Implements [`ErrorCode`] for standardized error handling.
37///
38/// # Example
39///
40/// ```
41/// use orcs_runtime::ChannelError;
42/// use orcs_types::{ChannelId, ErrorCode};
43///
44/// fn handle_error(err: ChannelError) {
45///     match err.code() {
46///         "CHANNEL_NOT_FOUND" => eprintln!("Channel missing"),
47///         "CHANNEL_INVALID_STATE" => eprintln!("State error"),
48///         _ => eprintln!("Other error: {err}"),
49///     }
50/// }
51/// ```
52#[derive(Debug, Clone, Error)]
53pub enum ChannelError {
54    /// The specified channel does not exist.
55    ///
56    /// This typically occurs when:
57    /// - Querying a channel that was never created
58    /// - Accessing a channel that was killed
59    #[error("channel not found: {0}")]
60    NotFound(ChannelId),
61
62    /// Attempted an invalid state transition.
63    ///
64    /// Channel state transitions are strict:
65    /// - Only `Running` → `Completed` is allowed via `complete()`
66    /// - Only `Running` → `Aborted` is allowed via `abort()`
67    /// - Terminal states cannot transition further
68    #[error("invalid state transition: {from} -> {to}")]
69    InvalidState {
70        /// The current state name.
71        from: String,
72        /// The attempted target state name.
73        to: String,
74    },
75
76    /// The parent channel for a spawn operation does not exist.
77    ///
78    /// When spawning a child channel, the specified parent must exist.
79    #[error("parent channel not found: {0}")]
80    ParentNotFound(ChannelId),
81
82    /// A channel with this ID already exists.
83    ///
84    /// Channel IDs are UUIDs and should be globally unique.
85    /// This error indicates a potential bug or UUID collision.
86    #[error("channel already exists: {0}")]
87    AlreadyExists(ChannelId),
88}
89
90impl ErrorCode for ChannelError {
91    fn code(&self) -> &'static str {
92        match self {
93            Self::NotFound(_) => "CHANNEL_NOT_FOUND",
94            Self::InvalidState { .. } => "CHANNEL_INVALID_STATE",
95            Self::ParentNotFound(_) => "CHANNEL_PARENT_NOT_FOUND",
96            Self::AlreadyExists(_) => "CHANNEL_ALREADY_EXISTS",
97        }
98    }
99
100    fn is_recoverable(&self) -> bool {
101        // All channel errors are non-recoverable
102        false
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use orcs_types::assert_error_codes;
110
111    fn all_variants() -> Vec<ChannelError> {
112        vec![
113            ChannelError::NotFound(ChannelId::new()),
114            ChannelError::InvalidState {
115                from: "x".into(),
116                to: "y".into(),
117            },
118            ChannelError::ParentNotFound(ChannelId::new()),
119            ChannelError::AlreadyExists(ChannelId::new()),
120        ]
121    }
122
123    #[test]
124    fn all_error_codes_valid() {
125        assert_error_codes(&all_variants(), "CHANNEL_");
126    }
127
128    #[test]
129    fn not_found_error() {
130        let id = ChannelId::new();
131        let err = ChannelError::NotFound(id);
132        assert_eq!(err.code(), "CHANNEL_NOT_FOUND");
133        assert!(!err.is_recoverable());
134    }
135
136    #[test]
137    fn invalid_state_error() {
138        let err = ChannelError::InvalidState {
139            from: "Running".into(),
140            to: "Running".into(),
141        };
142        assert_eq!(err.code(), "CHANNEL_INVALID_STATE");
143        assert!(!err.is_recoverable());
144    }
145}