Skip to main content

taktora_executor/
error.rs

1//! Error types surfaced by the executor.
2
3use crate::task_id::TaskId;
4
5/// Type alias for user-supplied item errors. Boxed `dyn Error` so callers can
6/// plug in any error type without forcing this crate to know about it.
7pub type ItemError = Box<dyn std::error::Error + Send + Sync + 'static>;
8
9/// Top-level error type for the executor.
10#[derive(thiserror::Error, Debug)]
11#[non_exhaustive]
12pub enum ExecutorError {
13    /// An iceoryx2 operation failed. The original error is rendered with
14    /// `{}` because iceoryx2's error types do not collapse into a single
15    /// `From` source.
16    #[error("iceoryx2: {0}")]
17    Iceoryx2(String),
18
19    /// Graph validation failed at `build()` time.
20    #[error("invalid graph: {0}")]
21    InvalidGraph(String),
22
23    /// An item's `declare_triggers` call returned an error or the executor
24    /// rejected it (e.g. a duplicate subscriber attachment).
25    #[error("trigger declaration failed: {0}")]
26    DeclareTriggers(String),
27
28    /// An item returned `Err(...)` or panicked. The original error is wrapped.
29    #[error("item error in task {task_id}: {source}")]
30    Item {
31        /// The task that produced the error.
32        task_id: TaskId,
33        /// The underlying error from the item.
34        #[source]
35        source: ItemError,
36    },
37
38    /// `Executor::run` was called while the executor was already running.
39    #[error("executor already running")]
40    AlreadyRunning,
41
42    /// The runner thread panicked or could not be joined.
43    #[error("runner thread join failed")]
44    RunnerJoin,
45
46    /// Builder API used incorrectly (e.g. missing required field).
47    #[error("builder misuse: {0}")]
48    Builder(String),
49
50    /// The requested task id is not registered with this executor.
51    #[error("task `{0}` not found")]
52    TaskNotFound(TaskId),
53
54    /// A `clear_task_fault` call targeted a task that was already Running.
55    /// `REQ_0070`.
56    #[error("task `{0}` is not in fault state")]
57    TaskNotFaulted(TaskId),
58
59    /// A `clear_executor_fault` call was made while the executor was already
60    /// Running. `REQ_0071`.
61    #[error("executor is not in fault state")]
62    ExecutorNotFaulted,
63}
64
65impl ExecutorError {
66    /// Convenience constructor for wrapping arbitrary iceoryx2 error values.
67    #[must_use]
68    pub fn iceoryx2(err: impl core::fmt::Display) -> Self {
69        Self::Iceoryx2(err.to_string())
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn item_error_roundtrip() {
79        let source: ItemError = Box::new(std::io::Error::other("boom"));
80        let err = ExecutorError::Item {
81            task_id: "task-1".into(),
82            source,
83        };
84        let s = format!("{err}");
85        assert!(s.contains("task-1"));
86        assert!(s.contains("boom"));
87    }
88
89    #[test]
90    fn iceoryx2_helper_renders_display() {
91        #[derive(Debug, thiserror::Error)]
92        #[error("whatever happened")]
93        struct Whatever;
94        let e = ExecutorError::iceoryx2(Whatever);
95        assert!(matches!(e, ExecutorError::Iceoryx2(_)));
96        assert!(format!("{e}").contains("whatever happened"));
97    }
98}