Skip to main content

koi_mdns/
error.rs

1use koi_common::error::ErrorCode;
2use thiserror::Error;
3
4/// Domain-specific errors for the mDNS capability.
5#[derive(Debug, Error)]
6pub enum MdnsError {
7    #[error("Invalid service type: {0}")]
8    InvalidServiceType(String),
9
10    #[error("Registration not found: {0}")]
11    RegistrationNotFound(String),
12
13    #[error("Resolve timeout: {0}")]
14    ResolveTimeout(String),
15
16    #[error("mDNS daemon error: {0}")]
17    Daemon(String),
18
19    #[error(transparent)]
20    Io(#[from] std::io::Error),
21
22    #[error("Already draining: {0}")]
23    AlreadyDraining(String),
24
25    #[error("Not draining: {0}")]
26    NotDraining(String),
27
28    #[error("Ambiguous ID prefix: {0}")]
29    AmbiguousId(String),
30
31    #[error("Invalid payload: {0}")]
32    InvalidPayload(String),
33}
34
35pub type Result<T> = std::result::Result<T, MdnsError>;
36
37impl From<koi_common::types::ServiceTypeError> for MdnsError {
38    fn from(e: koi_common::types::ServiceTypeError) -> Self {
39        MdnsError::InvalidServiceType(e.to_string())
40    }
41}
42
43impl From<&MdnsError> for ErrorCode {
44    fn from(e: &MdnsError) -> Self {
45        match e {
46            MdnsError::InvalidServiceType(_) => Self::InvalidType,
47            MdnsError::RegistrationNotFound(_) => Self::NotFound,
48            MdnsError::ResolveTimeout(_) => Self::ResolveTimeout,
49            MdnsError::Daemon(_) => Self::DaemonError,
50            MdnsError::Io(_) => Self::IoError,
51            MdnsError::AlreadyDraining(_) => Self::AlreadyDraining,
52            MdnsError::NotDraining(_) => Self::NotDraining,
53            MdnsError::AmbiguousId(_) => Self::AmbiguousId,
54            MdnsError::InvalidPayload(_) => Self::InvalidPayload,
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    /// Exhaustive test: every MdnsError variant maps to the expected ErrorCode
64    /// and HTTP status. Adding a new variant forces a compile error until
65    /// explicitly mapped.
66    #[test]
67    fn all_mdns_error_variants_map_to_expected_error_code_and_http_status() {
68        let cases: Vec<(MdnsError, ErrorCode, u16)> = vec![
69            (
70                MdnsError::InvalidServiceType("bad".into()),
71                ErrorCode::InvalidType,
72                400,
73            ),
74            (
75                MdnsError::RegistrationNotFound("abc".into()),
76                ErrorCode::NotFound,
77                404,
78            ),
79            (
80                MdnsError::ResolveTimeout("srv".into()),
81                ErrorCode::ResolveTimeout,
82                504,
83            ),
84            (
85                MdnsError::Daemon("engine crash".into()),
86                ErrorCode::DaemonError,
87                500,
88            ),
89            (
90                MdnsError::Io(std::io::Error::other("test")),
91                ErrorCode::IoError,
92                500,
93            ),
94            (
95                MdnsError::AlreadyDraining("abc".into()),
96                ErrorCode::AlreadyDraining,
97                409,
98            ),
99            (
100                MdnsError::NotDraining("abc".into()),
101                ErrorCode::NotDraining,
102                409,
103            ),
104            (
105                MdnsError::AmbiguousId("a1".into()),
106                ErrorCode::AmbiguousId,
107                400,
108            ),
109            (
110                MdnsError::InvalidPayload("bad value".into()),
111                ErrorCode::InvalidPayload,
112                400,
113            ),
114        ];
115        for (error, expected_code, expected_status) in &cases {
116            let code = ErrorCode::from(error);
117            assert_eq!(
118                &code, expected_code,
119                "{error:?} should map to {expected_code:?}"
120            );
121            assert_eq!(
122                code.http_status(),
123                *expected_status,
124                "{error:?} → {expected_code:?} should have HTTP {expected_status}"
125            );
126        }
127    }
128
129    #[test]
130    fn service_type_error_converts_to_mdns_error() {
131        let st_err = koi_common::types::ServiceTypeError::Invalid("bad_proto".into());
132        let mdns_err: MdnsError = st_err.into();
133        assert!(matches!(mdns_err, MdnsError::InvalidServiceType(_)));
134        assert!(mdns_err.to_string().contains("bad_proto"));
135    }
136
137    #[test]
138    fn error_display_messages_contain_context() {
139        let e = MdnsError::InvalidServiceType("_bad._xyz".into());
140        assert!(e.to_string().contains("_bad._xyz"));
141
142        let e = MdnsError::RegistrationNotFound("deadbeef".into());
143        assert!(e.to_string().contains("deadbeef"));
144    }
145}