Skip to main content

apimock_server/
error.rs

1//! Errors produced by server-level operations.
2//!
3//! See `apimock_routing::error` for the rationale on per-crate error
4//! types. `ServerError` wraps `ConfigError` via `#[from]` because
5//! server startup calls `Config::new` on the user's behalf.
6//!
7//! # `#[non_exhaustive]` and `kind()` (RFC 041)
8//!
9//! `ServerError` is `#[non_exhaustive]` and gains `kind()` /
10//! `ServerErrorKind`, one variant per `ServerError` variant โ€” no
11//! delegation into `ConfigErrorKind` for the wrapped `Config` variant,
12//! same reasoning as `WorkspaceError`'s in `apimock_config::error`. No
13//! variant here carries a `toml::de::Error`, so nothing in this enum
14//! needed boxing โ€” `apimock_config::error`'s module doc has the full
15//! reasoning for the two variants (elsewhere) that did.
16
17use std::{io, path::PathBuf};
18
19pub type ServerResult<T> = Result<T, ServerError>;
20
21#[derive(Debug, thiserror::Error)]
22#[non_exhaustive]
23pub enum ServerError {
24    /// TLS certificate or private key failed to load.
25    #[error("TLS material load failed ({kind}) at `{path}`: {reason}")]
26    TlsLoad {
27        kind: TlsKind,
28        path: PathBuf,
29        reason: String,
30    },
31
32    /// Listener address failed to resolve or bind.
33    #[error("invalid listener address `{addr}`: {reason}")]
34    ListenerAddress { addr: String, reason: String },
35
36    /// A middleware file listed in config was missing on disk.
37    #[error("middleware script not found: `{path}`")]
38    MiddlewareMissing { path: PathBuf },
39
40    /// A middleware file was found but failed to compile.
41    #[error("failed to compile middleware `{path}`: {reason}")]
42    MiddlewareCompile { path: PathBuf, reason: String },
43
44    /// Catch-all for plain I/O that doesn't have a more specific variant.
45    #[error("i/o error: {0}")]
46    Io(#[from] io::Error),
47
48    /// Forwarded from the config crate when server startup triggers
49    /// config loading.
50    #[error(transparent)]
51    Config(#[from] apimock_config::ConfigError),
52}
53
54/// `ServerError`'s failure class.
55#[non_exhaustive]
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum ServerErrorKind {
58    TlsLoad,
59    ListenerAddress,
60    MiddlewareMissing,
61    MiddlewareCompile,
62    Io,
63    Config,
64}
65
66impl ServerError {
67    pub fn kind(&self) -> ServerErrorKind {
68        match self {
69            ServerError::TlsLoad { .. } => ServerErrorKind::TlsLoad,
70            ServerError::ListenerAddress { .. } => ServerErrorKind::ListenerAddress,
71            ServerError::MiddlewareMissing { .. } => ServerErrorKind::MiddlewareMissing,
72            ServerError::MiddlewareCompile { .. } => ServerErrorKind::MiddlewareCompile,
73            ServerError::Io(_) => ServerErrorKind::Io,
74            ServerError::Config(_) => ServerErrorKind::Config,
75        }
76    }
77}
78
79#[derive(Debug, Clone, Copy)]
80#[non_exhaustive]
81pub enum TlsKind {
82    Certificate,
83    PrivateKey,
84}
85
86impl std::fmt::Display for TlsKind {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        match self {
89            TlsKind::Certificate => f.write_str("certificate"),
90            TlsKind::PrivateKey => f.write_str("private key"),
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    // RFC 041 ยง 6: kind() โ€” one assertion per variant.
100    #[test]
101    fn server_error_kind_matches_every_variant() {
102        assert_eq!(
103            ServerError::TlsLoad {
104                kind: TlsKind::Certificate,
105                path: PathBuf::from("x"),
106                reason: "x".to_owned(),
107            }
108            .kind(),
109            ServerErrorKind::TlsLoad
110        );
111        assert_eq!(
112            ServerError::ListenerAddress {
113                addr: "x".to_owned(),
114                reason: "x".to_owned(),
115            }
116            .kind(),
117            ServerErrorKind::ListenerAddress
118        );
119        assert_eq!(
120            ServerError::MiddlewareMissing {
121                path: PathBuf::from("x"),
122            }
123            .kind(),
124            ServerErrorKind::MiddlewareMissing
125        );
126        assert_eq!(
127            ServerError::MiddlewareCompile {
128                path: PathBuf::from("x"),
129                reason: "x".to_owned(),
130            }
131            .kind(),
132            ServerErrorKind::MiddlewareCompile
133        );
134        assert_eq!(
135            ServerError::Io(io::Error::other("x")).kind(),
136            ServerErrorKind::Io
137        );
138        assert_eq!(
139            ServerError::Config(apimock_config::ConfigError::Validation {
140                reason: "x".to_owned()
141            })
142            .kind(),
143            ServerErrorKind::Config
144        );
145    }
146}