Skip to main content

eggserve_core/server/
errors.rs

1//! Public runtime error types.
2//!
3//! Errors are classified into four categories:
4//!
5//! - **Startup errors** ([`ServerError::Bind`], [`ServerError::Config`],
6//!   [`ServerError::TlsSetup`], [`ServerError::Startup`]) — returned to the
7//!   caller before the listener is ready.
8//! - **Lifecycle errors** ([`ServerError::AlreadyStarted`],
9//!   [`ServerError::NotStarted`]) — indicate misuse of the server handle or
10//!   lifecycle state violations.
11//! - **Runtime errors** ([`ServerError::Accept`], [`ServerError::ShutdownTimeout`])
12//!   — occur during serving and are logged, not returned to callers.
13//! - **Transport errors** ([`ServerError::Transport`]) — failures in response
14//!   normalization or body conversion.
15
16use std::fmt;
17
18/// Errors from server startup and lifecycle operations.
19#[derive(Debug)]
20pub enum ServerError {
21    /// Failed to bind the TCP listener.
22    Bind(std::io::Error),
23    /// Invalid or inconsistent configuration.
24    Config(String),
25    /// The server was already started.
26    AlreadyStarted,
27    /// The server has not been started.
28    NotStarted,
29    /// An error occurred during connection acceptance.
30    Accept(std::io::Error),
31    /// TLS certificate or configuration error.
32    TlsSetup(String),
33    /// Transport conversion failure (e.g., body conversion, response normalization).
34    Transport(String),
35    /// The graceful shutdown timed out.
36    ShutdownTimeout,
37    /// A fatal startup error occurred (bind failure, TLS error, etc.).
38    Startup(String),
39    /// The server encountered a terminal runtime error.
40    Terminal(String),
41}
42
43impl fmt::Display for ServerError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::Bind(e) => write!(f, "failed to bind: {}", e),
47            Self::Config(msg) => write!(f, "configuration error: {}", msg),
48            Self::AlreadyStarted => write!(f, "server already started"),
49            Self::NotStarted => write!(f, "server not started"),
50            Self::Accept(e) => write!(f, "accept error: {}", e),
51            Self::TlsSetup(msg) => write!(f, "TLS setup error: {}", msg),
52            Self::Transport(msg) => write!(f, "transport error: {}", msg),
53            Self::ShutdownTimeout => write!(f, "graceful shutdown timed out"),
54            Self::Startup(msg) => write!(f, "startup error: {}", msg),
55            Self::Terminal(msg) => write!(f, "terminal runtime error: {}", msg),
56        }
57    }
58}
59
60impl std::error::Error for ServerError {
61    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62        match self {
63            Self::Bind(e) => Some(e),
64            Self::Accept(e) => Some(e),
65            _ => None,
66        }
67    }
68}
69
70impl From<std::io::Error> for ServerError {
71    fn from(e: std::io::Error) -> Self {
72        ServerError::Bind(e)
73    }
74}
75
76/// Outcome of a server shutdown.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum ShutdownResult {
79    /// All in-flight connections completed within the grace period.
80    Clean,
81    /// The grace period expired; some connections were forcibly cancelled.
82    Timeout,
83    /// The server was forcefully terminated.
84    Forced,
85}
86
87impl fmt::Display for ShutdownResult {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        match self {
90            Self::Clean => write!(f, "clean shutdown"),
91            Self::Timeout => write!(f, "shutdown timed out"),
92            Self::Forced => write!(f, "forced shutdown"),
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn server_error_display() {
103        let err = ServerError::Config("bad value".into());
104        assert!(err.to_string().contains("bad value"));
105
106        let err = ServerError::AlreadyStarted;
107        assert!(err.to_string().contains("already started"));
108
109        let err = ServerError::NotStarted;
110        assert!(err.to_string().contains("not started"));
111
112        let err = ServerError::TlsSetup("invalid cert".into());
113        assert!(err.to_string().contains("TLS setup error"));
114        assert!(err.to_string().contains("invalid cert"));
115
116        let err = ServerError::Transport("body conversion failed".into());
117        assert!(err.to_string().contains("transport error"));
118        assert!(err.to_string().contains("body conversion failed"));
119
120        let err = ServerError::ShutdownTimeout;
121        assert!(err.to_string().contains("timed out"));
122
123        let err = ServerError::Startup("bind failed".into());
124        assert!(err.to_string().contains("startup error"));
125        assert!(err.to_string().contains("bind failed"));
126
127        let err = ServerError::Terminal("runtime crashed".into());
128        assert!(err.to_string().contains("terminal runtime error"));
129        assert!(err.to_string().contains("runtime crashed"));
130    }
131
132    #[test]
133    fn server_error_is_error() {
134        let err: Box<dyn std::error::Error> = Box::new(ServerError::Config("test".into()));
135        assert!(!err.to_string().is_empty());
136    }
137
138    #[test]
139    fn shutdown_result_display() {
140        assert_eq!(ShutdownResult::Clean.to_string(), "clean shutdown");
141        assert_eq!(ShutdownResult::Timeout.to_string(), "shutdown timed out");
142        assert_eq!(ShutdownResult::Forced.to_string(), "forced shutdown");
143    }
144
145    #[test]
146    fn shutdown_result_equality() {
147        assert_eq!(ShutdownResult::Clean, ShutdownResult::Clean);
148        assert_ne!(ShutdownResult::Clean, ShutdownResult::Timeout);
149        assert_ne!(ShutdownResult::Forced, ShutdownResult::Clean);
150    }
151}