phytrace_sdk/
error.rs

1//! Error types for the PhyTrace SDK.
2
3use thiserror::Error;
4
5use crate::core::license::LicenseError;
6
7/// Result type alias using `PhyTraceError`.
8pub type Result<T> = std::result::Result<T, PhyTraceError>;
9
10/// Convenient type alias
11pub type PhyTraceResult<T> = Result<T>;
12
13/// Top-level error type for the PhyTrace SDK.
14#[derive(Debug, Error)]
15pub enum PhyTraceError {
16    /// Error during event building
17    #[error("Builder error: {0}")]
18    Builder(#[from] BuilderError),
19
20    /// Validation error
21    #[error("Validation error: {0}")]
22    Validation(String),
23
24    /// Transport error
25    #[error("Transport error: {0}")]
26    Transport(String),
27
28    /// Buffer error
29    #[error("Buffer error: {0}")]
30    Buffer(#[from] BufferError),
31
32    /// Configuration error
33    #[error("Configuration error: {0}")]
34    Config(#[from] ConfigError),
35
36    /// License validation error
37    #[error("License error: {0}")]
38    License(#[from] LicenseError),
39
40    /// Serialization error
41    #[error("Serialization error: {0}")]
42    Serialization(String),
43
44    /// YAML parsing error
45    #[error("YAML error: {0}")]
46    Yaml(#[from] serde_yaml::Error),
47
48    /// IO error
49    #[error("IO error: {0}")]
50    Io(#[from] std::io::Error),
51
52    /// Provenance/signing error
53    #[error("Provenance error: {0}")]
54    Provenance(String),
55
56    /// Agent lifecycle error
57    #[error("Agent error: {0}")]
58    Agent(String),
59
60    /// Timeout error
61    #[error("Timeout: {0}")]
62    Timeout(String),
63
64    /// Cryptographic error
65    #[error("Crypto error: {0}")]
66    Crypto(String),
67}
68
69/// Errors that can occur during event building.
70#[derive(Debug, Error)]
71pub enum BuilderError {
72    /// Missing required field
73    #[error("Missing required field: {0}")]
74    MissingField(String),
75
76    /// Invalid field value
77    #[error("Invalid value for field '{field}': {message}")]
78    InvalidValue {
79        /// Field name
80        field: String,
81        /// Error message
82        message: String,
83    },
84
85    /// Validation failed after build
86    #[error("Validation failed: {0}")]
87    ValidationFailed(String),
88}
89
90/// Errors related to the edge buffer.
91#[derive(Debug, Error)]
92pub enum BufferError {
93    /// Buffer is full
94    #[error("Buffer full: {0}")]
95    Full(String),
96
97    /// IO error
98    #[error("IO error: {0}")]
99    Io(String),
100
101    /// Serialization error
102    #[error("Serialization error: {0}")]
103    Serialization(String),
104
105    /// Buffer corrupted
106    #[error("Buffer corrupted: {0}")]
107    Corrupted(String),
108}
109
110/// Configuration-related errors.
111#[derive(Debug, Error)]
112pub enum ConfigError {
113    /// Config file read error
114    #[error("Failed to read config file: {0}")]
115    FileRead(String),
116
117    /// Config parse error
118    #[error("Failed to parse config: {0}")]
119    Parse(String),
120
121    /// Validation error
122    #[error("Config validation error: {0}")]
123    Validation(String),
124
125    /// Missing required configuration
126    #[error("Missing required config: {0}")]
127    MissingRequired(String),
128}
129
130/// Error codes as defined in the SDK specification.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum ErrorCode {
133    // Validation errors (E001-E009)
134    /// E001: Missing required field
135    MissingRequiredField,
136    /// E002: Invalid field type
137    InvalidFieldType,
138    /// E003: Value out of range
139    ValueOutOfRange,
140    /// E004: Invalid enum value
141    InvalidEnumValue,
142
143    // Transport errors (E010-E019)
144    /// E010: Network unreachable
145    NetworkUnreachable,
146    /// E011: Connection timeout
147    ConnectionTimeout,
148    /// E012: TLS handshake failed
149    TlsHandshakeFailed,
150    /// E013: Rate limited
151    RateLimited,
152
153    // Auth errors (E020-E029)
154    /// E020: Invalid API key
155    InvalidApiKey,
156    /// E021: Token expired
157    TokenExpired,
158    /// E022: Insufficient permissions
159    InsufficientPermissions,
160
161    // Buffer errors (E030-E039)
162    /// E030: Buffer full
163    BufferFull,
164    /// E031: Disk write failed
165    DiskWriteFailed,
166    /// E032: Encryption error
167    EncryptionError,
168
169    // Adapter errors (E040-E049)
170    /// E040: Platform connection failed
171    PlatformConnectionFailed,
172    /// E041: Topic not found
173    TopicNotFound,
174    /// E042: Message parse error
175    MessageParseError,
176}
177
178impl ErrorCode {
179    /// Returns the string code (e.g., "E001")
180    pub fn code(&self) -> &'static str {
181        match self {
182            Self::MissingRequiredField => "E001",
183            Self::InvalidFieldType => "E002",
184            Self::ValueOutOfRange => "E003",
185            Self::InvalidEnumValue => "E004",
186            Self::NetworkUnreachable => "E010",
187            Self::ConnectionTimeout => "E011",
188            Self::TlsHandshakeFailed => "E012",
189            Self::RateLimited => "E013",
190            Self::InvalidApiKey => "E020",
191            Self::TokenExpired => "E021",
192            Self::InsufficientPermissions => "E022",
193            Self::BufferFull => "E030",
194            Self::DiskWriteFailed => "E031",
195            Self::EncryptionError => "E032",
196            Self::PlatformConnectionFailed => "E040",
197            Self::TopicNotFound => "E041",
198            Self::MessageParseError => "E042",
199        }
200    }
201
202    /// Returns a human-readable description
203    pub fn description(&self) -> &'static str {
204        match self {
205            Self::MissingRequiredField => "Missing required field",
206            Self::InvalidFieldType => "Invalid field type",
207            Self::ValueOutOfRange => "Value out of range",
208            Self::InvalidEnumValue => "Invalid enum value",
209            Self::NetworkUnreachable => "Network unreachable",
210            Self::ConnectionTimeout => "Connection timeout",
211            Self::TlsHandshakeFailed => "TLS handshake failed",
212            Self::RateLimited => "Rate limited",
213            Self::InvalidApiKey => "Invalid API key",
214            Self::TokenExpired => "Token expired",
215            Self::InsufficientPermissions => "Insufficient permissions",
216            Self::BufferFull => "Buffer full",
217            Self::DiskWriteFailed => "Disk write failed",
218            Self::EncryptionError => "Encryption error",
219            Self::PlatformConnectionFailed => "Platform connection failed",
220            Self::TopicNotFound => "Topic not found",
221            Self::MessageParseError => "Message parse error",
222        }
223    }
224}
225
226impl std::fmt::Display for ErrorCode {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        write!(f, "{}: {}", self.code(), self.description())
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    // ==========================================================================
237    // ErrorCode Tests
238    // ==========================================================================
239
240    #[test]
241    fn test_error_code_codes() {
242        // Validation errors
243        assert_eq!(ErrorCode::MissingRequiredField.code(), "E001");
244        assert_eq!(ErrorCode::InvalidFieldType.code(), "E002");
245        assert_eq!(ErrorCode::ValueOutOfRange.code(), "E003");
246        assert_eq!(ErrorCode::InvalidEnumValue.code(), "E004");
247
248        // Transport errors
249        assert_eq!(ErrorCode::NetworkUnreachable.code(), "E010");
250        assert_eq!(ErrorCode::ConnectionTimeout.code(), "E011");
251        assert_eq!(ErrorCode::TlsHandshakeFailed.code(), "E012");
252        assert_eq!(ErrorCode::RateLimited.code(), "E013");
253
254        // Auth errors
255        assert_eq!(ErrorCode::InvalidApiKey.code(), "E020");
256        assert_eq!(ErrorCode::TokenExpired.code(), "E021");
257        assert_eq!(ErrorCode::InsufficientPermissions.code(), "E022");
258
259        // Buffer errors
260        assert_eq!(ErrorCode::BufferFull.code(), "E030");
261        assert_eq!(ErrorCode::DiskWriteFailed.code(), "E031");
262        assert_eq!(ErrorCode::EncryptionError.code(), "E032");
263
264        // Adapter errors
265        assert_eq!(ErrorCode::PlatformConnectionFailed.code(), "E040");
266        assert_eq!(ErrorCode::TopicNotFound.code(), "E041");
267        assert_eq!(ErrorCode::MessageParseError.code(), "E042");
268    }
269
270    #[test]
271    fn test_error_code_descriptions() {
272        // Validation errors
273        assert_eq!(
274            ErrorCode::MissingRequiredField.description(),
275            "Missing required field"
276        );
277        assert_eq!(
278            ErrorCode::InvalidFieldType.description(),
279            "Invalid field type"
280        );
281        assert_eq!(
282            ErrorCode::ValueOutOfRange.description(),
283            "Value out of range"
284        );
285        assert_eq!(
286            ErrorCode::InvalidEnumValue.description(),
287            "Invalid enum value"
288        );
289
290        // Transport errors
291        assert_eq!(
292            ErrorCode::NetworkUnreachable.description(),
293            "Network unreachable"
294        );
295        assert_eq!(
296            ErrorCode::ConnectionTimeout.description(),
297            "Connection timeout"
298        );
299        assert_eq!(
300            ErrorCode::TlsHandshakeFailed.description(),
301            "TLS handshake failed"
302        );
303        assert_eq!(ErrorCode::RateLimited.description(), "Rate limited");
304
305        // Auth errors
306        assert_eq!(ErrorCode::InvalidApiKey.description(), "Invalid API key");
307        assert_eq!(ErrorCode::TokenExpired.description(), "Token expired");
308        assert_eq!(
309            ErrorCode::InsufficientPermissions.description(),
310            "Insufficient permissions"
311        );
312
313        // Buffer errors
314        assert_eq!(ErrorCode::BufferFull.description(), "Buffer full");
315        assert_eq!(
316            ErrorCode::DiskWriteFailed.description(),
317            "Disk write failed"
318        );
319        assert_eq!(ErrorCode::EncryptionError.description(), "Encryption error");
320
321        // Adapter errors
322        assert_eq!(
323            ErrorCode::PlatformConnectionFailed.description(),
324            "Platform connection failed"
325        );
326        assert_eq!(ErrorCode::TopicNotFound.description(), "Topic not found");
327        assert_eq!(
328            ErrorCode::MessageParseError.description(),
329            "Message parse error"
330        );
331    }
332
333    #[test]
334    fn test_error_code_display() {
335        assert_eq!(
336            format!("{}", ErrorCode::MissingRequiredField),
337            "E001: Missing required field"
338        );
339        assert_eq!(
340            format!("{}", ErrorCode::NetworkUnreachable),
341            "E010: Network unreachable"
342        );
343        assert_eq!(
344            format!("{}", ErrorCode::InvalidApiKey),
345            "E020: Invalid API key"
346        );
347        assert_eq!(format!("{}", ErrorCode::BufferFull), "E030: Buffer full");
348        assert_eq!(
349            format!("{}", ErrorCode::PlatformConnectionFailed),
350            "E040: Platform connection failed"
351        );
352    }
353
354    #[test]
355    fn test_error_code_equality() {
356        assert_eq!(
357            ErrorCode::MissingRequiredField,
358            ErrorCode::MissingRequiredField
359        );
360        assert_ne!(ErrorCode::MissingRequiredField, ErrorCode::InvalidFieldType);
361    }
362
363    #[test]
364    fn test_error_code_clone() {
365        let code = ErrorCode::NetworkUnreachable;
366        let cloned = code;
367        assert_eq!(code, cloned);
368    }
369
370    #[test]
371    fn test_error_code_debug() {
372        let debug_str = format!("{:?}", ErrorCode::ConnectionTimeout);
373        assert!(debug_str.contains("ConnectionTimeout"));
374    }
375
376    // ==========================================================================
377    // BuilderError Tests
378    // ==========================================================================
379
380    #[test]
381    fn test_builder_error_missing_field() {
382        let err = BuilderError::MissingField("source_id".to_string());
383        assert!(format!("{}", err).contains("source_id"));
384        assert!(format!("{}", err).contains("Missing required field"));
385    }
386
387    #[test]
388    fn test_builder_error_invalid_value() {
389        let err = BuilderError::InvalidValue {
390            field: "latitude".to_string(),
391            message: "must be between -90 and 90".to_string(),
392        };
393        let display = format!("{}", err);
394        assert!(display.contains("latitude"));
395        assert!(display.contains("must be between -90 and 90"));
396    }
397
398    #[test]
399    fn test_builder_error_validation_failed() {
400        let err = BuilderError::ValidationFailed("Event has no domain data".to_string());
401        assert!(format!("{}", err).contains("Validation failed"));
402        assert!(format!("{}", err).contains("Event has no domain data"));
403    }
404
405    #[test]
406    fn test_builder_error_debug() {
407        let err = BuilderError::MissingField("test".to_string());
408        let debug_str = format!("{:?}", err);
409        assert!(debug_str.contains("MissingField"));
410    }
411
412    // ==========================================================================
413    // BufferError Tests
414    // ==========================================================================
415
416    #[test]
417    fn test_buffer_error_full() {
418        let err = BufferError::Full("Buffer capacity exceeded".to_string());
419        assert!(format!("{}", err).contains("Buffer full"));
420        assert!(format!("{}", err).contains("Buffer capacity exceeded"));
421    }
422
423    #[test]
424    fn test_buffer_error_io() {
425        let err = BufferError::Io("Failed to write to disk".to_string());
426        assert!(format!("{}", err).contains("IO error"));
427        assert!(format!("{}", err).contains("Failed to write to disk"));
428    }
429
430    #[test]
431    fn test_buffer_error_serialization() {
432        let err = BufferError::Serialization("Invalid JSON".to_string());
433        assert!(format!("{}", err).contains("Serialization error"));
434        assert!(format!("{}", err).contains("Invalid JSON"));
435    }
436
437    #[test]
438    fn test_buffer_error_corrupted() {
439        let err = BufferError::Corrupted("Checksum mismatch".to_string());
440        assert!(format!("{}", err).contains("Buffer corrupted"));
441        assert!(format!("{}", err).contains("Checksum mismatch"));
442    }
443
444    // ==========================================================================
445    // ConfigError Tests
446    // ==========================================================================
447
448    #[test]
449    fn test_config_error_file_read() {
450        let err = ConfigError::FileRead("/path/to/config.yaml".to_string());
451        assert!(format!("{}", err).contains("Failed to read config file"));
452    }
453
454    #[test]
455    fn test_config_error_parse() {
456        let err = ConfigError::Parse("Invalid YAML syntax".to_string());
457        assert!(format!("{}", err).contains("Failed to parse config"));
458    }
459
460    #[test]
461    fn test_config_error_validation() {
462        let err = ConfigError::Validation("endpoint is required".to_string());
463        assert!(format!("{}", err).contains("Config validation error"));
464    }
465
466    #[test]
467    fn test_config_error_missing_required() {
468        let err = ConfigError::MissingRequired("api_key".to_string());
469        assert!(format!("{}", err).contains("Missing required config"));
470    }
471
472    // ==========================================================================
473    // PhyTraceError Tests
474    // ==========================================================================
475
476    #[test]
477    fn test_phytrace_error_builder() {
478        let builder_err = BuilderError::MissingField("test".to_string());
479        let err: PhyTraceError = builder_err.into();
480        assert!(format!("{}", err).contains("Builder error"));
481    }
482
483    #[test]
484    fn test_phytrace_error_validation() {
485        let err = PhyTraceError::Validation("Invalid latitude".to_string());
486        assert!(format!("{}", err).contains("Validation error"));
487        assert!(format!("{}", err).contains("Invalid latitude"));
488    }
489
490    #[test]
491    fn test_phytrace_error_transport() {
492        let err = PhyTraceError::Transport("Connection refused".to_string());
493        assert!(format!("{}", err).contains("Transport error"));
494        assert!(format!("{}", err).contains("Connection refused"));
495    }
496
497    #[test]
498    fn test_phytrace_error_buffer() {
499        let buffer_err = BufferError::Full("capacity".to_string());
500        let err: PhyTraceError = buffer_err.into();
501        assert!(format!("{}", err).contains("Buffer error"));
502    }
503
504    #[test]
505    fn test_phytrace_error_config() {
506        let config_err = ConfigError::Validation("invalid".to_string());
507        let err: PhyTraceError = config_err.into();
508        assert!(format!("{}", err).contains("Configuration error"));
509    }
510
511    #[test]
512    fn test_phytrace_error_serialization() {
513        let err = PhyTraceError::Serialization("JSON error".to_string());
514        assert!(format!("{}", err).contains("Serialization error"));
515    }
516
517    #[test]
518    fn test_phytrace_error_io() {
519        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
520        let err: PhyTraceError = io_err.into();
521        assert!(format!("{}", err).contains("IO error"));
522    }
523
524    #[test]
525    fn test_phytrace_error_provenance() {
526        let err = PhyTraceError::Provenance("Invalid signature".to_string());
527        assert!(format!("{}", err).contains("Provenance error"));
528    }
529
530    #[test]
531    fn test_phytrace_error_agent() {
532        let err = PhyTraceError::Agent("Agent not started".to_string());
533        assert!(format!("{}", err).contains("Agent error"));
534    }
535
536    #[test]
537    fn test_phytrace_error_timeout() {
538        let err = PhyTraceError::Timeout("Request timed out after 30s".to_string());
539        assert!(format!("{}", err).contains("Timeout"));
540    }
541
542    #[test]
543    fn test_phytrace_error_crypto() {
544        let err = PhyTraceError::Crypto("Invalid key length".to_string());
545        assert!(format!("{}", err).contains("Crypto error"));
546    }
547
548    #[test]
549    fn test_phytrace_error_debug() {
550        let err = PhyTraceError::Transport("test".to_string());
551        let debug_str = format!("{:?}", err);
552        assert!(debug_str.contains("Transport"));
553    }
554
555    // ==========================================================================
556    // Result Type Alias Tests
557    // ==========================================================================
558
559    #[test]
560    fn test_result_type_alias() {
561        fn returns_result() -> Result<i32> {
562            Ok(42)
563        }
564        assert_eq!(returns_result().unwrap(), 42);
565    }
566
567    #[test]
568    fn test_phytrace_result_type_alias() {
569        fn returns_phytrace_result() -> PhyTraceResult<String> {
570            Ok("success".to_string())
571        }
572        assert_eq!(returns_phytrace_result().unwrap(), "success");
573    }
574
575    #[test]
576    fn test_result_error() {
577        fn returns_error() -> Result<()> {
578            Err(PhyTraceError::Validation("test".to_string()))
579        }
580        assert!(returns_error().is_err());
581    }
582}