Skip to main content

pjson_rs/
error.rs

1//! Error types for PJS operations
2
3/// Result type alias for PJS operations
4pub type Result<T> = std::result::Result<T, Error>;
5
6/// Main error type for PJS operations
7#[derive(Debug, Clone, thiserror::Error)]
8pub enum Error {
9    /// Invalid JSON syntax
10    #[error("Invalid JSON syntax at position {position}: {message}")]
11    InvalidJson {
12        /// Position in the input where error occurred
13        position: usize,
14        /// Error description
15        message: String,
16    },
17
18    /// Frame format error
19    #[error("Invalid frame format: {0}")]
20    InvalidFrame(String),
21
22    /// Schema validation error
23    #[error("Schema validation failed: {0}")]
24    SchemaValidation(String),
25
26    /// Semantic type mismatch
27    #[error("Semantic type mismatch: expected {expected}, got {actual}")]
28    SemanticTypeMismatch {
29        /// Expected semantic type
30        expected: String,
31        /// Actual semantic type
32        actual: String,
33    },
34
35    /// Buffer overflow or underflow
36    #[error("Buffer error: {0}")]
37    Buffer(String),
38
39    /// Memory allocation error
40    #[error("Memory allocation failed: {0}")]
41    Memory(String),
42
43    /// I/O error
44    #[error("I/O error: {0}")]
45    Io(String),
46
47    /// Connection failed error
48    #[error("Connection failed: {0}")]
49    ConnectionFailed(String),
50
51    /// Client error
52    #[error("Client error: {0}")]
53    ClientError(String),
54
55    /// Invalid session error
56    #[error("Invalid session: {0}")]
57    InvalidSession(String),
58
59    /// Invalid URL error
60    #[error("Invalid URL: {0}")]
61    InvalidUrl(String),
62
63    /// Serialization error
64    #[error("Serialization error: {0}")]
65    Serialization(String),
66
67    /// UTF-8 conversion error
68    #[error("UTF-8 conversion failed: {0}")]
69    Utf8(String),
70
71    /// Security violation error
72    #[error("Security error: {0}")]
73    SecurityError(String),
74
75    /// Compression or decompression codec failure
76    #[error("Compression error: {0}")]
77    CompressionError(String),
78
79    /// Generic error for other cases
80    #[error("{0}")]
81    Other(String),
82}
83
84impl Error {
85    /// Create an invalid JSON error
86    pub fn invalid_json(position: usize, message: impl Into<String>) -> Self {
87        Self::InvalidJson {
88            position,
89            message: message.into(),
90        }
91    }
92
93    /// Create an invalid frame error
94    pub fn invalid_frame(message: impl Into<String>) -> Self {
95        Self::InvalidFrame(message.into())
96    }
97
98    /// Create a schema validation error
99    pub fn schema_validation(message: impl Into<String>) -> Self {
100        Self::SchemaValidation(message.into())
101    }
102
103    /// Create a semantic type mismatch error
104    pub fn semantic_type_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
105        Self::SemanticTypeMismatch {
106            expected: expected.into(),
107            actual: actual.into(),
108        }
109    }
110
111    /// Create a buffer error
112    pub fn buffer(message: impl Into<String>) -> Self {
113        Self::Buffer(message.into())
114    }
115
116    /// Create a memory error
117    pub fn memory(message: impl Into<String>) -> Self {
118        Self::Memory(message.into())
119    }
120
121    /// Create a connection failed error
122    pub fn connection_failed(message: impl Into<String>) -> Self {
123        Self::ConnectionFailed(message.into())
124    }
125
126    /// Create a client error
127    pub fn client_error(message: impl Into<String>) -> Self {
128        Self::ClientError(message.into())
129    }
130
131    /// Create an invalid session error
132    pub fn invalid_session(message: impl Into<String>) -> Self {
133        Self::InvalidSession(message.into())
134    }
135
136    /// Create an invalid URL error
137    pub fn invalid_url(message: impl Into<String>) -> Self {
138        Self::InvalidUrl(message.into())
139    }
140
141    /// Create a serialization error
142    pub fn serialization(message: impl Into<String>) -> Self {
143        Self::Serialization(message.into())
144    }
145
146    /// Create a UTF-8 error
147    pub fn utf8(message: impl Into<String>) -> Self {
148        Self::Utf8(message.into())
149    }
150
151    /// Create a security error
152    pub fn security_error(message: impl Into<String>) -> Self {
153        Self::SecurityError(message.into())
154    }
155
156    /// Create a compression error
157    pub fn compression_error(message: impl Into<String>) -> Self {
158        Self::CompressionError(message.into())
159    }
160
161    /// Create a generic error
162    pub fn other(message: impl Into<String>) -> Self {
163        Self::Other(message.into())
164    }
165
166    /// Check if the error is related to JSON parsing
167    pub fn is_json_error(&self) -> bool {
168        matches!(self, Self::InvalidJson { .. } | Self::Serialization(_))
169    }
170
171    /// Check if the error is a network-related error
172    pub fn is_network_error(&self) -> bool {
173        matches!(self, Self::ConnectionFailed(_) | Self::Io(_))
174    }
175
176    /// Check if the error is a validation error
177    pub fn is_validation_error(&self) -> bool {
178        matches!(
179            self,
180            Self::SchemaValidation(_) | Self::SemanticTypeMismatch { .. } | Self::InvalidFrame(_)
181        )
182    }
183
184    /// Check if the error is a security-related error
185    pub fn is_security_error(&self) -> bool {
186        matches!(self, Self::SecurityError(_))
187    }
188
189    /// Get error category as string
190    pub fn category(&self) -> &'static str {
191        match self {
192            Self::InvalidJson { .. } | Self::Serialization(_) => "json",
193            Self::InvalidFrame(_)
194            | Self::SchemaValidation(_)
195            | Self::SemanticTypeMismatch { .. } => "validation",
196            Self::Buffer(_) | Self::Memory(_) => "memory",
197            Self::Io(_) | Self::ConnectionFailed(_) => "network",
198            Self::ClientError(_) | Self::InvalidSession(_) | Self::InvalidUrl(_) => "client",
199            Self::Utf8(_) => "encoding",
200            Self::SecurityError(_) => "security",
201            Self::CompressionError(_) => "compression",
202            Self::Other(_) => "other",
203        }
204    }
205}
206
207impl From<std::io::Error> for Error {
208    fn from(err: std::io::Error) -> Self {
209        Error::Io(err.to_string())
210    }
211}
212
213impl From<std::str::Utf8Error> for Error {
214    fn from(err: std::str::Utf8Error) -> Self {
215        Error::Utf8(err.to_string())
216    }
217}
218
219impl From<serde_json::Error> for Error {
220    fn from(err: serde_json::Error) -> Self {
221        Error::Serialization(err.to_string())
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_error_creation_constructors() {
231        let json_err = Error::invalid_json(42, "unexpected token");
232        if let Error::InvalidJson { position, message } = json_err {
233            assert_eq!(position, 42);
234            assert_eq!(message, "unexpected token");
235        } else {
236            panic!("Expected InvalidJson error");
237        }
238
239        let frame_err = Error::invalid_frame("malformed frame");
240        assert!(matches!(frame_err, Error::InvalidFrame(_)));
241
242        let schema_err = Error::schema_validation("required field missing");
243        assert!(matches!(schema_err, Error::SchemaValidation(_)));
244
245        let type_err = Error::semantic_type_mismatch("string", "number");
246        if let Error::SemanticTypeMismatch { expected, actual } = type_err {
247            assert_eq!(expected, "string");
248            assert_eq!(actual, "number");
249        } else {
250            panic!("Expected SemanticTypeMismatch error");
251        }
252    }
253
254    #[test]
255    fn test_all_error_constructors() {
256        assert!(matches!(Error::buffer("overflow"), Error::Buffer(_)));
257        assert!(matches!(
258            Error::memory("allocation failed"),
259            Error::Memory(_)
260        ));
261        assert!(matches!(
262            Error::connection_failed("timeout"),
263            Error::ConnectionFailed(_)
264        ));
265        assert!(matches!(
266            Error::client_error("bad request"),
267            Error::ClientError(_)
268        ));
269        assert!(matches!(
270            Error::invalid_session("expired"),
271            Error::InvalidSession(_)
272        ));
273        assert!(matches!(
274            Error::invalid_url("malformed"),
275            Error::InvalidUrl(_)
276        ));
277        assert!(matches!(
278            Error::serialization("json error"),
279            Error::Serialization(_)
280        ));
281        assert!(matches!(Error::utf8("invalid utf8"), Error::Utf8(_)));
282        assert!(matches!(Error::other("unknown"), Error::Other(_)));
283    }
284
285    #[test]
286    fn test_error_classification() {
287        let json_err = Error::invalid_json(0, "test");
288        assert!(json_err.is_json_error());
289        assert!(!json_err.is_network_error());
290        assert!(!json_err.is_validation_error());
291
292        let serialization_err = Error::serialization("test");
293        assert!(serialization_err.is_json_error());
294
295        let network_err = Error::connection_failed("test");
296        assert!(network_err.is_network_error());
297        assert!(!network_err.is_json_error());
298
299        let io_err = Error::Io("test".to_string());
300        assert!(io_err.is_network_error());
301
302        let validation_err = Error::schema_validation("test");
303        assert!(validation_err.is_validation_error());
304        assert!(!validation_err.is_json_error());
305
306        let frame_err = Error::invalid_frame("test");
307        assert!(frame_err.is_validation_error());
308
309        let type_err = Error::semantic_type_mismatch("a", "b");
310        assert!(type_err.is_validation_error());
311    }
312
313    #[test]
314    fn test_error_categories() {
315        assert_eq!(Error::invalid_json(0, "test").category(), "json");
316        assert_eq!(Error::serialization("test").category(), "json");
317        assert_eq!(Error::invalid_frame("test").category(), "validation");
318        assert_eq!(Error::schema_validation("test").category(), "validation");
319        assert_eq!(
320            Error::semantic_type_mismatch("a", "b").category(),
321            "validation"
322        );
323        assert_eq!(Error::buffer("test").category(), "memory");
324        assert_eq!(Error::memory("test").category(), "memory");
325        assert_eq!(Error::Io("test".to_string()).category(), "network");
326        assert_eq!(Error::connection_failed("test").category(), "network");
327        assert_eq!(Error::client_error("test").category(), "client");
328        assert_eq!(Error::invalid_session("test").category(), "client");
329        assert_eq!(Error::invalid_url("test").category(), "client");
330        assert_eq!(Error::utf8("test").category(), "encoding");
331        assert_eq!(Error::other("test").category(), "other");
332    }
333
334    #[test]
335    fn test_error_display() {
336        let json_err = Error::invalid_json(42, "unexpected token");
337        assert_eq!(
338            json_err.to_string(),
339            "Invalid JSON syntax at position 42: unexpected token"
340        );
341
342        let type_err = Error::semantic_type_mismatch("string", "number");
343        assert_eq!(
344            type_err.to_string(),
345            "Semantic type mismatch: expected string, got number"
346        );
347
348        let frame_err = Error::invalid_frame("malformed");
349        assert_eq!(frame_err.to_string(), "Invalid frame format: malformed");
350    }
351
352    #[test]
353    fn test_from_std_io_error() {
354        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
355        let pjs_err = Error::from(io_err);
356
357        assert!(matches!(pjs_err, Error::Io(_)));
358        assert!(pjs_err.is_network_error());
359        assert_eq!(pjs_err.category(), "network");
360    }
361
362    #[test]
363    fn test_from_utf8_error() {
364        // Create invalid UTF-8 dynamically to avoid compiler warning
365        let mut invalid_utf8 = vec![0xF0, 0x28, 0x8C, 0xBC]; // Invalid UTF-8 sequence
366        invalid_utf8[1] = 0x28; // Make it definitely invalid
367
368        let utf8_err = std::str::from_utf8(&invalid_utf8).unwrap_err();
369        let pjs_err = Error::from(utf8_err);
370
371        assert!(matches!(pjs_err, Error::Utf8(_)));
372        assert_eq!(pjs_err.category(), "encoding");
373    }
374
375    #[test]
376    fn test_from_serde_json_error() {
377        let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
378        let pjs_err = Error::from(json_err);
379
380        assert!(matches!(pjs_err, Error::Serialization(_)));
381        assert!(pjs_err.is_json_error());
382        assert_eq!(pjs_err.category(), "json");
383    }
384
385    #[test]
386    fn test_error_debug_format() {
387        let err = Error::invalid_json(10, "syntax error");
388        let debug_str = format!("{:?}", err);
389        assert!(debug_str.contains("InvalidJson"));
390        assert!(debug_str.contains("10"));
391        assert!(debug_str.contains("syntax error"));
392    }
393
394    #[test]
395    fn test_error_clone() {
396        let original = Error::semantic_type_mismatch("expected", "actual");
397        let cloned = original.clone();
398
399        assert_eq!(original.category(), cloned.category());
400        assert_eq!(original.to_string(), cloned.to_string());
401    }
402
403    #[test]
404    fn test_result_type_alias() {
405        fn returns_result() -> Result<i32> {
406            Ok(42)
407        }
408
409        fn returns_error() -> Result<i32> {
410            Err(Error::other("test error"))
411        }
412
413        assert_eq!(returns_result().unwrap(), 42);
414        assert!(returns_error().is_err());
415    }
416}