sitemap_gen/
error.rs

1//! Error types for the sitemap library.
2//!
3//! This module defines various error types that can occur during sitemap operations,
4//! including XML parsing, date handling, URL parsing, and I/O operations.
5//!
6//! The main error type is `SitemapError`, which encapsulates all possible errors
7//! that can occur within the library. This allows for consistent error handling
8//! and propagation throughout the codebase.
9
10use dtt::error::DateTimeError;
11use std::string::FromUtf8Error;
12use thiserror::Error;
13
14/// Errors that can occur when working with sitemaps.
15///
16/// This enum represents all possible errors that can occur within the sitemap library.
17/// It uses the `thiserror` crate for deriving the `Error` trait, which simplifies
18/// error handling and provides good interoperability with the standard library.
19///
20/// The `non_exhaustive` attribute allows for future expansion of the error types
21/// without breaking backwards compatibility.
22#[derive(Error, Debug)]
23#[non_exhaustive]
24pub enum SitemapError {
25    /// Error occurred during XML writing.
26    #[error("XML writing error: {0}")]
27    XmlWriteError(#[from] xml::writer::Error),
28
29    /// Error occurred during XML parsing.
30    #[error("XML parsing error: {0}")]
31    XmlParseError(#[from] xml::reader::Error),
32
33    /// Error occurred during date parsing or formatting.
34    #[error("Date error: {0}")]
35    DateError(#[from] DateTimeError),
36
37    /// Error occurred during URL parsing.
38    #[error("URL error: {0}")]
39    UrlError(#[from] url::ParseError),
40
41    /// Error occurred during I/O operations.
42    #[error("I/O error: {0}")]
43    IoError(#[from] std::io::Error),
44
45    /// Error occurred during string encoding.
46    #[error("Encoding error: {0}")]
47    EncodingError(#[from] FromUtf8Error),
48
49    /// Invalid change frequency provided.
50    #[error("Invalid change frequency: {0}")]
51    InvalidChangeFreq(String),
52
53    /// Custom error for unforeseen scenarios.
54    #[error("Custom error: {0}")]
55    CustomError(String),
56
57    /// Error occurred when a sitemap exceeds the maximum allowed size.
58    #[error("Sitemap size exceeds the maximum allowed (10MB)")]
59    SitemapTooLarge,
60
61    /// Error occurred when the number of URLs in a sitemap exceeds the maximum allowed.
62    #[error("Number of URLs ({0}) exceeds the maximum allowed limit (50,000)")]
63    MaxUrlLimitExceeded(usize),
64}
65
66impl SitemapError {
67    /// Provides additional context for the error.
68    ///
69    /// This method returns a static string that gives more information about
70    /// the context in which the error occurred. This can be useful for logging
71    /// or providing more detailed error messages to users.
72    ///
73    /// # Returns
74    /// A string slice describing the context of the error.
75    pub fn context(&self) -> &'static str {
76        match self {
77            SitemapError::XmlWriteError(_) => "Error occurred while writing XML data",
78            SitemapError::XmlParseError(_) => "Error occurred while parsing XML data",
79            SitemapError::DateError(_) => "Error occurred while parsing or formatting dates",
80            SitemapError::UrlError(_) => "Error occurred while parsing URLs",
81            SitemapError::IoError(_) => "Error occurred during file or network operations",
82            SitemapError::EncodingError(_) => "Error occurred during UTF-8 string encoding or decoding",
83            SitemapError::InvalidChangeFreq(_) => "An invalid change frequency value was provided",
84            SitemapError::CustomError(_) => "An unexpected error occurred",
85            SitemapError::SitemapTooLarge => "The generated sitemap exceeds the maximum allowed size",
86            SitemapError::MaxUrlLimitExceeded(_) => "The number of URLs exceeds the maximum allowed limit",
87        }
88    }
89}
90
91/// Custom result type for sitemap operations.
92///
93/// This type alias simplifies the return types of functions that can produce
94/// a `SitemapError`. It's a convenient shorthand for `Result<T, SitemapError>`.
95pub type SitemapResult<T> = Result<T, SitemapError>;
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use std::io;
101    use xml::writer::{EventWriter, XmlEvent};
102
103    #[test]
104    fn test_error_creation_and_formatting() {
105        // Create a XML writing error
106        let mut writer = EventWriter::new(Vec::new());
107        let xml_write_result = writer.write(XmlEvent::end_element()); // This will cause an error because we're ending an element that wasn't started
108        let xml_write_error = xml_write_result.unwrap_err();
109        let sitemap_error =
110            SitemapError::XmlWriteError(xml_write_error);
111        assert!(sitemap_error
112            .to_string()
113            .contains("XML writing error"));
114        assert_eq!(
115            sitemap_error.context(),
116            "Error occurred while writing XML data"
117        );
118
119        let custom_error =
120            SitemapError::CustomError("Test error".to_string());
121        assert_eq!(
122            custom_error.to_string(),
123            "Custom error: Test error"
124        );
125    }
126
127    #[test]
128    fn test_error_context() {
129        let url_error =
130            SitemapError::UrlError(url::ParseError::EmptyHost);
131        assert_eq!(
132            url_error.context(),
133            "Error occurred while parsing URLs"
134        );
135
136        let io_error = SitemapError::IoError(io::Error::new(
137            io::ErrorKind::Other,
138            "I/O Error",
139        ));
140        assert_eq!(
141            io_error.context(),
142            "Error occurred during file or network operations"
143        );
144
145        let invalid_change_freq =
146            SitemapError::InvalidChangeFreq("invalid".to_string());
147        assert_eq!(
148            invalid_change_freq.context(),
149            "An invalid change frequency value was provided"
150        );
151    }
152
153    #[test]
154    fn test_error_display() {
155        let date_error =
156            SitemapError::DateError(DateTimeError::InvalidFormat);
157        assert_eq!(
158            date_error.to_string(),
159            "Date error: Invalid date format"
160        );
161
162        let url_error =
163            SitemapError::UrlError(url::ParseError::EmptyHost);
164        assert_eq!(url_error.to_string(), "URL error: empty host");
165
166        let io_error = SitemapError::IoError(io::Error::new(
167            io::ErrorKind::Other,
168            "I/O Error",
169        ));
170        assert_eq!(io_error.to_string(), "I/O error: I/O Error");
171
172        let custom_error = SitemapError::CustomError(
173            "Custom error message".to_string(),
174        );
175        assert_eq!(
176            custom_error.to_string(),
177            "Custom error: Custom error message"
178        );
179
180        let sitemap_too_large = SitemapError::SitemapTooLarge;
181        assert_eq!(
182            sitemap_too_large.to_string(),
183            "Sitemap size exceeds the maximum allowed (10MB)"
184        );
185
186        let max_url_limit_exceeded =
187            SitemapError::MaxUrlLimitExceeded(60000);
188        assert_eq!(
189            max_url_limit_exceeded.to_string(),
190            "Number of URLs (60000) exceeds the maximum allowed limit (50,000)"
191        );
192    }
193
194    #[test]
195    fn test_result_type_alias() {
196        fn demo_function() -> SitemapResult<()> {
197            Err(SitemapError::CustomError("Test error".to_string()))
198        }
199
200        let result = demo_function();
201        assert!(result.is_err());
202        assert!(matches!(
203            result.unwrap_err(),
204            SitemapError::CustomError(_)
205        ));
206    }
207
208    #[test]
209    fn test_xml_parse_error() {
210        let xml = "<invalid>";
211        let reader = xml::reader::EventReader::from_str(xml);
212        let parse_result: Result<
213            Vec<xml::reader::XmlEvent>,
214            xml::reader::Error,
215        > = reader.into_iter().collect();
216        let xml_parse_error = parse_result.unwrap_err();
217        let sitemap_error =
218            SitemapError::XmlParseError(xml_parse_error);
219        assert!(sitemap_error
220            .to_string()
221            .contains("XML parsing error"));
222        assert_eq!(
223            sitemap_error.context(),
224            "Error occurred while parsing XML data"
225        );
226    }
227
228    #[test]
229    fn test_date_error() {
230        let date_error =
231            SitemapError::DateError(DateTimeError::InvalidFormat);
232        assert_eq!(
233            date_error.context(),
234            "Error occurred while parsing or formatting dates"
235        );
236    }
237
238    #[test]
239    fn test_encoding_error() {
240        let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
241        let encoding_error =
242            String::from_utf8(invalid_utf8).unwrap_err();
243        let sitemap_error = SitemapError::EncodingError(encoding_error);
244        assert!(sitemap_error.to_string().contains("Encoding error"));
245        assert_eq!(
246            sitemap_error.context(),
247            "Error occurred during UTF-8 string encoding or decoding"
248        );
249    }
250
251    #[test]
252    fn test_sitemap_size_errors() {
253        let sitemap_too_large = SitemapError::SitemapTooLarge;
254        assert_eq!(
255            sitemap_too_large.to_string(),
256            "Sitemap size exceeds the maximum allowed (10MB)"
257        );
258        assert_eq!(
259            sitemap_too_large.context(),
260            "The generated sitemap exceeds the maximum allowed size"
261        );
262
263        let max_url_limit_exceeded =
264            SitemapError::MaxUrlLimitExceeded(60000);
265        assert_eq!(
266            max_url_limit_exceeded.to_string(),
267            "Number of URLs (60000) exceeds the maximum allowed limit (50,000)"
268        );
269        assert_eq!(
270            max_url_limit_exceeded.context(),
271            "The number of URLs exceeds the maximum allowed limit"
272        );
273    }
274
275    #[test]
276    fn test_error_propagation() {
277        fn parse_url() -> SitemapResult<()> {
278            Err(SitemapError::UrlError(url::ParseError::EmptyHost))
279        }
280
281        fn handle_url() -> SitemapResult<()> {
282            parse_url()?;
283            Ok(())
284        }
285
286        let result = handle_url();
287        assert!(result.is_err());
288        assert!(matches!(
289            result.unwrap_err(),
290            SitemapError::UrlError(_)
291        ));
292    }
293
294    #[test]
295    fn test_url_parsing_errors() {
296        let empty_host =
297            SitemapError::UrlError(url::ParseError::EmptyHost);
298        assert_eq!(empty_host.to_string(), "URL error: empty host");
299
300        let invalid_port =
301            SitemapError::UrlError(url::ParseError::InvalidPort);
302        assert_eq!(
303            invalid_port.to_string(),
304            "URL error: invalid port number"
305        ); // Adjusted expected message
306
307        let relative_url = SitemapError::UrlError(
308            url::ParseError::RelativeUrlWithoutBase,
309        );
310        assert_eq!(
311            relative_url.to_string(),
312            "URL error: relative URL without a base"
313        );
314    }
315
316    #[test]
317    fn test_invalid_change_freq_edge_cases() {
318        let empty_string =
319            SitemapError::InvalidChangeFreq("".to_string());
320        assert_eq!(
321            empty_string.to_string(),
322            "Invalid change frequency: "
323        );
324
325        let long_string =
326            SitemapError::InvalidChangeFreq("a".repeat(1000));
327        assert!(long_string
328            .to_string()
329            .contains("Invalid change frequency"));
330    }
331
332    #[test]
333    fn test_max_url_limit_exceeded_edge_cases() {
334        let just_under_limit = SitemapError::MaxUrlLimitExceeded(49999);
335        assert_eq!(just_under_limit.to_string(), "Number of URLs (49999) exceeds the maximum allowed limit (50,000)");
336
337        let at_limit = SitemapError::MaxUrlLimitExceeded(50000);
338        assert_eq!(at_limit.to_string(), "Number of URLs (50000) exceeds the maximum allowed limit (50,000)");
339
340        let over_limit = SitemapError::MaxUrlLimitExceeded(50001);
341        assert_eq!(over_limit.to_string(), "Number of URLs (50001) exceeds the maximum allowed limit (50,000)");
342    }
343}