Skip to main content

ironflow_runtime/
error.rs

1//! Typed error for the ironflow runtime.
2
3use thiserror::Error;
4use tokio_cron_scheduler::JobSchedulerError;
5
6/// Error returned by [`Runtime::serve`](crate::runtime::Runtime::serve) and
7/// [`Runtime::run_crons`](crate::runtime::Runtime::run_crons).
8#[derive(Debug, Error)]
9pub enum RuntimeError {
10    /// Failed to bind the TCP listener to the requested address.
11    #[error("failed to bind address: {0}")]
12    Bind(std::io::Error),
13    /// A cron expression is invalid or the scheduler failed to start.
14    #[error("cron scheduler error: {0}")]
15    Cron(#[from] JobSchedulerError),
16    /// The Axum server encountered a fatal I/O error.
17    #[error("http server error: {0}")]
18    Serve(std::io::Error),
19    /// The cron scheduler failed to shut down cleanly.
20    #[error("scheduler shutdown error: {0}")]
21    Shutdown(JobSchedulerError),
22}
23
24#[cfg(test)]
25mod tests {
26    use super::*;
27
28    #[test]
29    fn bind_error_display() {
30        let err = RuntimeError::Bind(std::io::Error::new(
31            std::io::ErrorKind::AddrInUse,
32            "port taken",
33        ));
34        assert_eq!(err.to_string(), "failed to bind address: port taken");
35    }
36
37    #[test]
38    fn serve_error_display() {
39        let err = RuntimeError::Serve(std::io::Error::other("fatal"));
40        assert_eq!(err.to_string(), "http server error: fatal");
41    }
42
43    #[test]
44    fn runtime_error_implements_std_error() {
45        let err = RuntimeError::Bind(std::io::Error::other("x"));
46        let _: &dyn std::error::Error = &err;
47    }
48
49    #[test]
50    fn cron_error_display() {
51        // Create a cron error through the From implementation
52        // by providing a JobSchedulerError (ParseSchedule variant for cron parse failures)
53        let err = RuntimeError::Cron(JobSchedulerError::ParseSchedule);
54        let msg = err.to_string();
55        assert!(msg.contains("cron scheduler error"));
56    }
57
58    #[test]
59    fn cron_error_from_impl() {
60        // Test From implementation: JobSchedulerError -> RuntimeError::Cron
61        let cron_err = JobSchedulerError::ParseSchedule;
62        let err: RuntimeError = cron_err.into();
63        let msg = err.to_string();
64        assert!(msg.contains("cron scheduler error"));
65    }
66
67    #[test]
68    fn shutdown_error_display() {
69        // Test Shutdown variant with a JobSchedulerError
70        let err = RuntimeError::Shutdown(JobSchedulerError::ParseSchedule);
71        let msg = err.to_string();
72        assert!(msg.contains("scheduler shutdown error"));
73    }
74
75    #[test]
76    fn cron_error_debug() {
77        let err = RuntimeError::Cron(JobSchedulerError::ParseSchedule);
78        let debug_str = format!("{:?}", err);
79        assert!(debug_str.contains("Cron"));
80    }
81
82    #[test]
83    fn shutdown_error_debug() {
84        let err = RuntimeError::Shutdown(JobSchedulerError::ParseSchedule);
85        let debug_str = format!("{:?}", err);
86        assert!(debug_str.contains("Shutdown"));
87    }
88
89    #[test]
90    fn bind_error_debug() {
91        let err = RuntimeError::Bind(std::io::Error::other("test"));
92        let debug_str = format!("{:?}", err);
93        assert!(debug_str.contains("Bind"));
94    }
95
96    #[test]
97    fn serve_error_debug() {
98        let err = RuntimeError::Serve(std::io::Error::other("test"));
99        let debug_str = format!("{:?}", err);
100        assert!(debug_str.contains("Serve"));
101    }
102
103    #[test]
104    fn error_variants_are_error() {
105        use std::error::Error;
106
107        let err_bind: Box<dyn Error> = Box::new(RuntimeError::Bind(std::io::Error::other("x")));
108        let err_serve: Box<dyn Error> = Box::new(RuntimeError::Serve(std::io::Error::other("x")));
109
110        assert!(!err_bind.to_string().is_empty());
111        assert!(!err_serve.to_string().is_empty());
112    }
113}