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