1use koi_common::error::ErrorCode;
2use thiserror::Error;
3
4#[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 #[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}