Skip to main content

wp_connector_api/errors/
source.rs

1use derive_more::From;
2use orion_error::{ErrorCode, StructError, ToStructError, UvsReason};
3use serde::Serialize;
4use std::error::Error as StdError;
5use thiserror::Error;
6
7#[derive(Error, Debug, Clone, PartialEq, Serialize, From)]
8pub enum SourceReason {
9    #[error("not data")]
10    NotData,
11    #[error("eof")]
12    EOF,
13    #[error("supplier error : {0}")]
14    SupplierError(String),
15    #[from(skip)]
16    #[error("disconnected: {0}")]
17    Disconnect(String),
18    #[from(skip)]
19    #[error("{0}")]
20    Other(String),
21    #[error("{0}")]
22    Uvs(UvsReason),
23}
24
25impl ErrorCode for SourceReason {
26    fn error_code(&self) -> i32 {
27        match self {
28            // Informational: normal conditions
29            SourceReason::NotData => 100, // Temporary no data available
30            SourceReason::EOF => 101,     // End of data stream
31
32            // Retryable errors
33            SourceReason::Disconnect(_) => 503, // Connection lost, can retry
34
35            // Internal/supplier errors
36            SourceReason::SupplierError(_) => 500, // Upstream supplier error
37            SourceReason::Other(_) => 520,         // Unclassified error
38
39            // Delegate to wrapped reason
40            SourceReason::Uvs(r) => r.error_code(),
41        }
42    }
43}
44
45pub type SourceError = StructError<SourceReason>;
46pub type SourceResult<T> = Result<T, StructError<SourceReason>>;
47
48impl SourceReason {
49    pub fn err(self) -> SourceError {
50        self.to_err()
51    }
52
53    pub fn err_detail<S: Into<String>>(self, detail: S) -> SourceError {
54        self.to_err().with_detail(detail.into())
55    }
56
57    pub fn err_source<E>(self, source: E) -> SourceError
58    where
59        E: StdError + Send + Sync + 'static,
60    {
61        self.to_err().with_source(source)
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn source_reason_error_codes() {
71        // Informational codes (1xx)
72        assert_eq!(SourceReason::NotData.error_code(), 100);
73        assert_eq!(SourceReason::EOF.error_code(), 101);
74
75        // Retryable codes (5xx with specific meaning)
76        assert_eq!(
77            SourceReason::Disconnect("conn lost".into()).error_code(),
78            503
79        );
80
81        // Internal errors (5xx)
82        assert_eq!(
83            SourceReason::SupplierError("upstream".into()).error_code(),
84            500
85        );
86        assert_eq!(SourceReason::Other("misc".into()).error_code(), 520);
87    }
88
89    #[test]
90    fn source_reason_error_codes_are_distinct() {
91        let codes = vec![
92            SourceReason::NotData.error_code(),
93            SourceReason::EOF.error_code(),
94            SourceReason::Disconnect("x".into()).error_code(),
95            SourceReason::SupplierError("x".into()).error_code(),
96            SourceReason::Other("x".into()).error_code(),
97        ];
98        // Verify all codes are different
99        let mut unique = codes.clone();
100        unique.sort();
101        unique.dedup();
102        assert_eq!(codes.len(), unique.len(), "error codes should be distinct");
103    }
104
105    #[test]
106    fn source_reason_informational_codes_are_below_200() {
107        assert!(SourceReason::NotData.error_code() < 200);
108        assert!(SourceReason::EOF.error_code() < 200);
109    }
110
111    #[test]
112    fn source_reason_retryable_codes_are_5xx() {
113        let code = SourceReason::Disconnect("x".into()).error_code();
114        assert!((500..600).contains(&code));
115    }
116
117    #[test]
118    fn source_reason_err_detail_sets_detail() {
119        let err = SourceReason::Other("boom".into()).err_detail("ctx");
120        assert_eq!(err.detail().as_deref(), Some("ctx"));
121    }
122
123    #[test]
124    fn source_reason_err_source_preserves_source_message() {
125        let err = SourceReason::Disconnect("read failed".into())
126            .err_source(std::io::Error::other("disk gone"));
127        assert!(err.to_string().contains("disk gone"));
128    }
129}