Skip to main content

dtt/
error.rs

1// error.rs
2//
3// Copyright © 2025 DateTime (DTT) library. All rights reserved.
4// SPDX-License-Identifier: Apache-2.0 OR MIT
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::{
9    env,
10    hash::{Hash, Hasher},
11};
12use thiserror::Error;
13use time::error::{ComponentRange, Parse};
14
15/// Custom error type for the application.
16///
17/// This error type encapsulates all possible errors that might occur in the application,
18/// including simulated errors for testing and environment variable retrieval errors.
19#[derive(Error, Debug)]
20pub enum AppError {
21    /// Error that occurs during datetime operations.
22    #[error("DateTime operation error: {0}")]
23    DateTimeError(#[from] DateTimeError),
24
25    /// Error that occurs during serialization.
26    #[cfg(feature = "serde")]
27    #[error("Serialization error: {0}")]
28    SerializationError(#[from] serde_json::Error),
29
30    /// General I/O or parsing error.
31    #[error("General I/O or parsing error: {0}")]
32    GeneralError(#[from] std::io::Error),
33
34    /// Error that occurs during other operations.
35    #[error("Other error: {0}")]
36    Other(String),
37
38    /// Error for simulating a failure in test mode.
39    #[error("Simulated error")]
40    SimulatedError,
41
42    /// Error that occurs when retrieving environment variables.
43    #[error("Environment variable error: {0}")]
44    EnvVarError(#[from] env::VarError),
45}
46
47/// Custom error type for the `DateTime` library.
48///
49/// This enum represents various errors that can occur when working with
50/// `DateTime` objects, such as invalid formats, timezones, and component ranges.
51#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
52pub enum DateTimeError {
53    /// The provided date format is invalid.
54    #[error("Invalid date format")]
55    InvalidFormat,
56
57    /// The provided timezone is invalid or not supported. DST is not supported.
58    #[error("Invalid or unsupported timezone; DST not supported")]
59    InvalidTimezone,
60
61    /// The date is invalid (e.g., February 30).
62    #[error("Invalid date")]
63    InvalidDate,
64
65    /// The time is invalid (e.g., 25:00).
66    #[error("Invalid time")]
67    InvalidTime,
68
69    /// An error occurred while parsing the date/time string.
70    #[error("Parsing error")]
71    ParseError(#[from] Parse),
72
73    /// A component (year, month, day, etc.) is out of the valid range.
74    #[error("Component range error")]
75    ComponentRange(#[from] ComponentRange),
76}
77
78impl Hash for DateTimeError {
79    /// Custom implementation of the `Hash` trait for `DateTimeError`.
80    ///
81    /// This allows `DateTimeError` to be used in hashed collections like `HashSet` and `HashMap`.
82    fn hash<H: Hasher>(&self, state: &mut H) {
83        // Use the discriminant of the enum as a simple hash value
84        std::mem::discriminant(self).hash(state);
85    }
86}
87
88#[cfg(feature = "serde")]
89impl Serialize for DateTimeError {
90    /// Serializes the `DateTimeError` into a string representation.
91    ///
92    /// This is a custom implementation to handle serialization for variants
93    /// that contain types (`Parse` and `ComponentRange`) which do not implement
94    /// `Serialize`.
95    ///
96    /// # Errors
97    ///
98    /// This function will return a serialization error if the process fails.
99    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
100    where
101        S: Serializer,
102    {
103        match self {
104            Self::InvalidFormat => {
105                serializer.serialize_str("InvalidFormat")
106            }
107            Self::InvalidTimezone => {
108                serializer.serialize_str("InvalidTimezone")
109            }
110            Self::InvalidDate => {
111                serializer.serialize_str("InvalidDate")
112            }
113            Self::InvalidTime => {
114                serializer.serialize_str("InvalidTime")
115            }
116            Self::ParseError(_) => {
117                serializer.serialize_str("ParseError")
118            }
119            Self::ComponentRange(_) => {
120                serializer.serialize_str("ComponentRange")
121            }
122        }
123    }
124}
125
126#[cfg(feature = "serde")]
127impl<'de> Deserialize<'de> for DateTimeError {
128    /// Deserializes a string into a `DateTimeError`.
129    ///
130    /// This is a custom implementation to handle deserialization for variants
131    /// that contain types (`Parse` and `ComponentRange`) which do not implement
132    /// `Deserialize`.
133    ///
134    /// # Errors
135    ///
136    /// This function will return a deserialization error if the input string
137    /// does not match any of the known variants.
138    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139    where
140        D: Deserializer<'de>,
141    {
142        let s: &str = Deserialize::deserialize(deserializer)?;
143        match s {
144            "InvalidFormat" => Ok(Self::InvalidFormat),
145            "InvalidTimezone" => Ok(Self::InvalidTimezone),
146            "InvalidDate" => Ok(Self::InvalidDate),
147            "InvalidTime" => Ok(Self::InvalidTime),
148            "ParseError" => Err(serde::de::Error::custom(
149                "Cannot deserialize ParseError directly",
150            )),
151            "ComponentRange" => Err(serde::de::Error::custom(
152                "Cannot deserialize ComponentRange directly",
153            )),
154            _ => Err(serde::de::Error::unknown_variant(
155                s,
156                &[
157                    "InvalidFormat",
158                    "InvalidTimezone",
159                    "InvalidDate",
160                    "InvalidTime",
161                    "ParseError",
162                    "ComponentRange",
163                ],
164            )),
165        }
166    }
167}
168
169impl Default for DateTimeError {
170    /// Provides a default value for `DateTimeError`.
171    ///
172    /// By default, the error is set to `InvalidFormat`.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// use dtt::error::DateTimeError;
178    ///
179    /// let error = DateTimeError::default();
180    /// assert_eq!(error, DateTimeError::InvalidFormat);
181    /// ```
182    fn default() -> Self {
183        Self::InvalidFormat
184    }
185}