http_handle/
error.rs

1// src/error.rs
2
3//! Error types for the Http Handle.
4//!
5//! This module defines the various error types that can occur during the operation
6//! of the Http Handle. It provides a centralized place for error handling and
7//! propagation throughout the application.
8//!
9//! The main type exposed by this module is the `ServerError` enum, which
10//! encompasses all possible error conditions the server might encounter.
11
12use std::io;
13use thiserror::Error;
14
15/// Represents the different types of errors that can occur in the server.
16///
17/// This enum defines various errors that can be encountered during the server's operation,
18/// such as I/O errors, invalid requests, file not found, and forbidden access.
19///
20/// # Examples
21///
22/// Creating an I/O error:
23///
24/// ```
25/// use std::io::{Error, ErrorKind};
26/// use http_handle::ServerError;
27///
28/// let io_error = Error::new(ErrorKind::NotFound, "file not found");
29/// let server_error = ServerError::from(io_error);
30/// assert!(matches!(server_error, ServerError::Io(_)));
31/// ```
32///
33/// Creating an invalid request error:
34///
35/// ```
36/// use http_handle::ServerError;
37///
38/// let invalid_request = ServerError::InvalidRequest("Missing HTTP method".to_string());
39/// assert!(matches!(invalid_request, ServerError::InvalidRequest(_)));
40/// ```
41#[derive(Error, Debug)]
42pub enum ServerError {
43    /// An I/O error occurred.
44    #[error("I/O error: {0}")]
45    Io(#[from] io::Error),
46
47    /// The request received by the server was invalid or malformed.
48    #[error("Invalid request: {0}")]
49    InvalidRequest(String),
50
51    /// The requested file was not found on the server.
52    #[error("File not found: {0}")]
53    NotFound(String),
54
55    /// Access to the requested resource is forbidden.
56    #[error("Forbidden: {0}")]
57    Forbidden(String),
58
59    /// A custom error type for unexpected scenarios.
60    #[error("Custom error: {0}")]
61    Custom(String),
62}
63
64impl ServerError {
65    /// Creates a new `InvalidRequest` error with the given message.
66    ///
67    /// # Arguments
68    ///
69    /// * `message` - A string slice that holds the error message.
70    ///
71    /// # Returns
72    ///
73    /// A `ServerError::InvalidRequest` variant.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use http_handle::ServerError;
79    ///
80    /// let error = ServerError::invalid_request("Missing HTTP version");
81    /// assert!(matches!(error, ServerError::InvalidRequest(_)));
82    /// ```
83    pub fn invalid_request<T: Into<String>>(message: T) -> Self {
84        ServerError::InvalidRequest(message.into())
85    }
86
87    /// Creates a new `NotFound` error with the given path.
88    ///
89    /// # Arguments
90    ///
91    /// * `path` - A string slice that holds the path of the not found resource.
92    ///
93    /// # Returns
94    ///
95    /// A `ServerError::NotFound` variant.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use http_handle::ServerError;
101    ///
102    /// let error = ServerError::not_found("/nonexistent.html");
103    /// assert!(matches!(error, ServerError::NotFound(_)));
104    /// ```
105    pub fn not_found<T: Into<String>>(path: T) -> Self {
106        ServerError::NotFound(path.into())
107    }
108
109    /// Creates a new `Forbidden` error with the given message.
110    ///
111    /// # Arguments
112    ///
113    /// * `message` - A string slice that holds the error message.
114    ///
115    /// # Returns
116    ///
117    /// A `ServerError::Forbidden` variant.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use http_handle::ServerError;
123    ///
124    /// let error = ServerError::forbidden("Access denied to sensitive file");
125    /// assert!(matches!(error, ServerError::Forbidden(_)));
126    /// ```
127    pub fn forbidden<T: Into<String>>(message: T) -> Self {
128        ServerError::Forbidden(message.into())
129    }
130}
131
132impl From<&str> for ServerError {
133    /// Converts a string slice into a `ServerError::Custom` variant.
134    ///
135    /// This implementation allows for easy creation of custom errors from string literals.
136    ///
137    /// # Arguments
138    ///
139    /// * `error` - A string slice that holds the error message.
140    ///
141    /// # Returns
142    ///
143    /// A `ServerError::Custom` variant.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use http_handle::ServerError;
149    ///
150    /// let error: ServerError = "Unexpected error".into();
151    /// assert!(matches!(error, ServerError::Custom(_)));
152    /// ```
153    fn from(error: &str) -> Self {
154        ServerError::Custom(error.to_string())
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use std::io;
162
163    /// Test case for converting an `io::Error` into `ServerError::Io`.
164    #[test]
165    fn test_io_error_conversion() {
166        let io_error =
167            io::Error::new(io::ErrorKind::NotFound, "file not found");
168        let server_error = ServerError::from(io_error);
169        assert!(matches!(server_error, ServerError::Io(_)));
170    }
171
172    /// Test case for creating a `ServerError::Custom` from a string slice.
173    #[test]
174    fn test_custom_error_creation() {
175        let custom_error: ServerError = "Unexpected error".into();
176        assert!(matches!(custom_error, ServerError::Custom(_)));
177    }
178
179    /// Test case for verifying the error messages of different `ServerError` variants.
180    #[test]
181    fn test_error_messages() {
182        let not_found = ServerError::not_found("index.html");
183        assert_eq!(not_found.to_string(), "File not found: index.html");
184
185        let forbidden = ServerError::forbidden("Access denied");
186        assert_eq!(forbidden.to_string(), "Forbidden: Access denied");
187
188        let invalid_request =
189            ServerError::invalid_request("Missing HTTP method");
190        assert_eq!(
191            invalid_request.to_string(),
192            "Invalid request: Missing HTTP method"
193        );
194    }
195
196    /// Test case for creating a `ServerError::InvalidRequest` using the `invalid_request` method.
197    #[test]
198    fn test_invalid_request_creation() {
199        let invalid_request =
200            ServerError::invalid_request("Bad request");
201        assert!(matches!(
202            invalid_request,
203            ServerError::InvalidRequest(_)
204        ));
205        assert_eq!(
206            invalid_request.to_string(),
207            "Invalid request: Bad request"
208        );
209    }
210
211    /// Test case for creating a `ServerError::NotFound` using the `not_found` method.
212    #[test]
213    fn test_not_found_creation() {
214        let not_found = ServerError::not_found("/nonexistent.html");
215        assert!(matches!(not_found, ServerError::NotFound(_)));
216        assert_eq!(
217            not_found.to_string(),
218            "File not found: /nonexistent.html"
219        );
220    }
221
222    /// Test case for creating a `ServerError::Forbidden` using the `forbidden` method.
223    #[test]
224    fn test_forbidden_creation() {
225        let forbidden = ServerError::forbidden("Access denied");
226        assert!(matches!(forbidden, ServerError::Forbidden(_)));
227        assert_eq!(forbidden.to_string(), "Forbidden: Access denied");
228    }
229
230    /// Test case for verifying the `ServerError::Custom` variant and its error message.
231    #[test]
232    fn test_custom_error_message() {
233        let custom_error =
234            ServerError::Custom("Custom error occurred".to_string());
235        assert_eq!(
236            custom_error.to_string(),
237            "Custom error: Custom error occurred"
238        );
239    }
240
241    /// Test case for checking `ServerError::from` for string conversion.
242    #[test]
243    fn test_custom_error_from_str() {
244        let custom_error: ServerError = "Some custom error".into();
245        assert!(matches!(custom_error, ServerError::Custom(_)));
246        assert_eq!(
247            custom_error.to_string(),
248            "Custom error: Some custom error"
249        );
250    }
251
252    /// Test case for converting `io::Error` using a different error kind to `ServerError::Io`.
253    #[test]
254    fn test_io_error_conversion_other_kind() {
255        let io_error = io::Error::new(
256            io::ErrorKind::PermissionDenied,
257            "permission denied",
258        );
259        let server_error = ServerError::from(io_error);
260        assert!(matches!(server_error, ServerError::Io(_)));
261        assert_eq!(
262            server_error.to_string(),
263            "I/O error: permission denied"
264        );
265    }
266
267    /// Test case for verifying if `ServerError::InvalidRequest` carries the correct error message.
268    #[test]
269    fn test_invalid_request_message() {
270        let error_message = "Invalid HTTP version";
271        let invalid_request =
272            ServerError::InvalidRequest(error_message.to_string());
273        assert_eq!(
274            invalid_request.to_string(),
275            format!("Invalid request: {}", error_message)
276        );
277    }
278
279    /// Test case for verifying if `ServerError::NotFound` carries the correct file path.
280    #[test]
281    fn test_not_found_message() {
282        let file_path = "missing.html";
283        let not_found = ServerError::NotFound(file_path.to_string());
284        assert_eq!(
285            not_found.to_string(),
286            format!("File not found: {}", file_path)
287        );
288    }
289
290    /// Test case for verifying if `ServerError::Forbidden` carries the correct message.
291    #[test]
292    fn test_forbidden_message() {
293        let forbidden_message = "Access denied to private resource";
294        let forbidden =
295            ServerError::Forbidden(forbidden_message.to_string());
296        assert_eq!(
297            forbidden.to_string(),
298            format!("Forbidden: {}", forbidden_message)
299        );
300    }
301
302    /// Test case for `ServerError::Io` with a generic IO error to ensure correct propagation.
303    #[test]
304    fn test_io_error_generic() {
305        let io_error =
306            io::Error::new(io::ErrorKind::Other, "generic I/O error");
307        let server_error = ServerError::from(io_error);
308        assert!(matches!(server_error, ServerError::Io(_)));
309        assert_eq!(
310            server_error.to_string(),
311            "I/O error: generic I/O error"
312        );
313    }
314}