1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Error, Debug)]
10pub enum Error {
11 #[error("Mock server is already running on port {0}. Call stop() before starting again.")]
13 ServerAlreadyStarted(u16),
14
15 #[error("Mock server has not been started yet. Call start() first.")]
17 ServerNotStarted,
18
19 #[error("Port {0} is already in use. Try using a different port or enable auto_port().")]
21 PortInUse(u16),
22
23 #[error("Port discovery failed: {0}\nTip: Try expanding the port range using port_range(start, end).")]
25 PortDiscoveryFailed(String),
26
27 #[error("Invalid configuration: {0}\nCheck your configuration file or builder settings.")]
29 InvalidConfig(String),
30
31 #[error("Invalid stub: {0}\nEnsure method, path, and response body are properly set.")]
33 InvalidStub(String),
34
35 #[error("Stub not found for {method} {path}. Available stubs: {available}")]
37 StubNotFound {
38 method: String,
40 path: String,
42 available: String,
44 },
45
46 #[error("HTTP error: {0}\nThis may indicate a network or protocol issue.")]
48 Http(#[from] axum::http::Error),
49
50 #[error("IO error: {0}\nCheck file permissions and network connectivity.")]
52 Io(#[from] std::io::Error),
53
54 #[error("JSON serialization error: {0}\nEnsure your request/response body is valid JSON.")]
56 Json(#[from] serde_json::Error),
57
58 #[error("MockForge core error: {0}")]
60 Core(#[from] mockforge_core::Error),
61
62 #[error("Server failed to start within {timeout_secs} seconds.\nCheck logs for details or increase timeout.")]
64 StartupTimeout {
65 timeout_secs: u64,
67 },
68
69 #[error("Server failed to stop within {timeout_secs} seconds.\nSome connections may still be active.")]
71 ShutdownTimeout {
72 timeout_secs: u64,
74 },
75
76 #[error("Admin API error ({operation}): {message}\nEndpoint: {endpoint}")]
78 AdminApiError {
79 operation: String,
81 message: String,
83 endpoint: String,
85 },
86
87 #[error("{0}")]
89 General(String),
90}
91
92impl Error {
93 pub fn admin_api_error(
107 operation: impl Into<String>,
108 message: impl Into<String>,
109 endpoint: impl Into<String>,
110 ) -> Self {
111 Self::AdminApiError {
112 operation: operation.into(),
113 message: message.into(),
114 endpoint: endpoint.into(),
115 }
116 }
117
118 pub fn stub_not_found(
132 method: impl Into<String>,
133 path: impl Into<String>,
134 available: Vec<String>,
135 ) -> Self {
136 Self::StubNotFound {
137 method: method.into(),
138 path: path.into(),
139 available: if available.is_empty() {
140 "none".to_string()
141 } else {
142 available.join(", ")
143 },
144 }
145 }
146
147 #[must_use]
161 pub fn to_log_string(&self) -> String {
162 format!("{self}").replace('\n', " | ")
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_server_already_started_error() {
172 let err = Error::ServerAlreadyStarted(3000);
173 let msg = format!("{err}");
174 assert!(msg.contains("3000"));
175 assert!(msg.contains("already running"));
176 assert!(msg.contains("stop()"));
177 }
178
179 #[test]
180 fn test_server_not_started_error() {
181 let err = Error::ServerNotStarted;
182 let msg = format!("{err}");
183 assert!(msg.contains("not been started"));
184 assert!(msg.contains("start()"));
185 }
186
187 #[test]
188 fn test_port_in_use_error() {
189 let err = Error::PortInUse(8080);
190 let msg = format!("{err}");
191 assert!(msg.contains("8080"));
192 assert!(msg.contains("already in use"));
193 assert!(msg.contains("auto_port()"));
194 }
195
196 #[test]
197 fn test_port_discovery_failed_error() {
198 let err = Error::PortDiscoveryFailed("No ports available in range".to_string());
199 let msg = format!("{err}");
200 assert!(msg.contains("Port discovery failed"));
201 assert!(msg.contains("No ports available"));
202 assert!(msg.contains("port_range"));
203 }
204
205 #[test]
206 fn test_invalid_config_error() {
207 let err = Error::InvalidConfig("Invalid host address".to_string());
208 let msg = format!("{err}");
209 assert!(msg.contains("Invalid configuration"));
210 assert!(msg.contains("Invalid host address"));
211 assert!(msg.contains("configuration file"));
212 }
213
214 #[test]
215 fn test_invalid_stub_error() {
216 let err = Error::InvalidStub("Missing response body".to_string());
217 let msg = format!("{err}");
218 assert!(msg.contains("Invalid stub"));
219 assert!(msg.contains("Missing response body"));
220 assert!(msg.contains("properly set"));
221 }
222
223 #[test]
224 fn test_stub_not_found_error_with_available() {
225 let err = Error::stub_not_found(
226 "GET",
227 "/api/missing",
228 vec!["GET /api/users".to_string(), "POST /api/orders".to_string()],
229 );
230 let msg = format!("{err}");
231 assert!(msg.contains("GET"));
232 assert!(msg.contains("/api/missing"));
233 assert!(msg.contains("GET /api/users"));
234 assert!(msg.contains("POST /api/orders"));
235 }
236
237 #[test]
238 fn test_stub_not_found_error_no_available() {
239 let err = Error::stub_not_found("DELETE", "/api/users/1", vec![]);
240 let msg = format!("{err}");
241 assert!(msg.contains("DELETE"));
242 assert!(msg.contains("/api/users/1"));
243 assert!(msg.contains("none"));
244 }
245
246 #[test]
247 fn test_startup_timeout_error() {
248 let err = Error::StartupTimeout { timeout_secs: 30 };
249 let msg = format!("{err}");
250 assert!(msg.contains("30 seconds"));
251 assert!(msg.contains("failed to start"));
252 }
253
254 #[test]
255 fn test_shutdown_timeout_error() {
256 let err = Error::ShutdownTimeout { timeout_secs: 10 };
257 let msg = format!("{err}");
258 assert!(msg.contains("10 seconds"));
259 assert!(msg.contains("failed to stop"));
260 assert!(msg.contains("connections"));
261 }
262
263 #[test]
264 fn test_admin_api_error() {
265 let err = Error::admin_api_error("create_mock", "Invalid JSON payload", "/api/mocks");
266 let msg = format!("{err}");
267 assert!(msg.contains("create_mock"));
268 assert!(msg.contains("Invalid JSON payload"));
269 assert!(msg.contains("/api/mocks"));
270 }
271
272 #[test]
273 fn test_general_error() {
274 let err = Error::General("Something went wrong".to_string());
275 let msg = format!("{err}");
276 assert_eq!(msg, "Something went wrong");
277 }
278
279 #[test]
280 fn test_to_log_string_single_line() {
281 let err = Error::General("Simple error".to_string());
282 let log_str = err.to_log_string();
283 assert_eq!(log_str, "Simple error");
284 assert!(!log_str.contains('\n'));
285 }
286
287 #[test]
288 fn test_to_log_string_multiline() {
289 let err = Error::InvalidConfig("Line 1\nLine 2\nLine 3".to_string());
290 let log_str = err.to_log_string();
291 assert!(!log_str.contains('\n'));
292 assert!(log_str.contains(" | "));
293 assert!(log_str.contains("Line 1"));
294 assert!(log_str.contains("Line 2"));
295 assert!(log_str.contains("Line 3"));
296 }
297
298 #[test]
299 fn test_http_error_conversion() {
300 use axum::http::header::InvalidHeaderValue;
302 let http_err: axum::http::Error =
303 axum::http::header::HeaderValue::from_bytes(&[0x80]).unwrap_err().into();
304 let err = Error::from(http_err);
305 let msg = format!("{err}");
306 assert!(msg.contains("HTTP error"));
307 }
308
309 #[test]
310 fn test_io_error_conversion() {
311 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
312 let err = Error::from(io_err);
313 let msg = format!("{err}");
314 assert!(msg.contains("IO error"));
315 assert!(msg.contains("file not found"));
316 }
317
318 #[test]
319 fn test_json_error_conversion() {
320 let json_str = "{invalid json";
321 let json_err = serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
322 let err = Error::from(json_err);
323 let msg = format!("{err}");
324 assert!(msg.contains("JSON serialization error"));
325 }
326
327 #[test]
328 fn test_error_debug_format() {
329 let err = Error::ServerNotStarted;
330 let debug_str = format!("{err:?}");
331 assert!(debug_str.contains("ServerNotStarted"));
332 }
333
334 #[test]
335 fn test_stub_not_found_with_single_available() {
336 let err = Error::stub_not_found("POST", "/api/create", vec!["GET /api/list".to_string()]);
337 let msg = format!("{err}");
338 assert!(msg.contains("GET /api/list"));
339 assert!(!msg.contains(", ")); }
341
342 #[test]
343 fn test_result_type_ok() {
344 let result: Result<i32> = Ok(42);
345 assert!(result.is_ok());
346 assert_eq!(result.unwrap(), 42);
347 }
348
349 #[test]
350 fn test_result_type_err() {
351 let result: Result<i32> = Err(Error::ServerNotStarted);
352 assert!(result.is_err());
353 }
354}