Skip to main content

wp_connector_api/errors/
sink.rs

1use derive_more::From;
2use orion_error::StructError;
3use orion_error::{ErrorCode, ToStructError, UvsReason};
4use serde::Serialize;
5use std::error::Error as StdError;
6use std::sync::mpsc::SendError;
7use thiserror::Error;
8
9#[derive(Debug, Error, PartialEq, Serialize, From)]
10pub enum SinkReason {
11    #[error("sink unavailable {0}")]
12    Sink(String),
13    #[error("set mock error")]
14    Mock,
15    #[error("stg ctrl error")]
16    StgCtrl,
17    #[error("{0}")]
18    Uvs(UvsReason),
19}
20impl ErrorCode for SinkReason {
21    fn error_code(&self) -> i32 {
22        match self {
23            // General sink errors
24            SinkReason::Sink(_) => 500, // General sink unavailable
25
26            // Testing/mock errors
27            SinkReason::Mock => 599, // Mock/test error
28
29            // Storage control errors
30            SinkReason::StgCtrl => 510, // Storage control error
31
32            // Delegate to wrapped reason
33            SinkReason::Uvs(r) => r.error_code(),
34        }
35    }
36}
37
38pub type SinkError = StructError<SinkReason>;
39
40pub trait ReasonSummary {
41    fn summary(&self) -> String;
42}
43
44impl<T> From<SendError<T>> for SinkReason
45where
46    T: ReasonSummary,
47{
48    fn from(err: SendError<T>) -> Self {
49        SinkReason::Sink(format!("send error: {}", err.0.summary()))
50    }
51}
52
53impl From<anyhow::Error> for SinkReason {
54    fn from(e: anyhow::Error) -> Self {
55        SinkReason::Sink(format!("{}", e))
56    }
57}
58
59pub type SinkResult<T> = Result<T, SinkError>;
60
61impl SinkReason {
62    pub fn sink<S: Into<String>>(msg: S) -> Self {
63        SinkReason::Sink(msg.into())
64    }
65
66    pub fn err(self) -> SinkError {
67        self.to_err()
68    }
69
70    pub fn err_detail<S: Into<String>>(self, detail: S) -> SinkError {
71        self.to_err().with_detail(detail.into())
72    }
73
74    pub fn err_source<E>(self, source: E) -> SinkError
75    where
76        E: StdError + Send + Sync + 'static,
77    {
78        self.to_err().with_source(source)
79    }
80}
81
82pub trait SinkErrorOwe<T> {
83    fn owe_sink<S: Into<String>>(self, msg: S) -> Result<T, StructError<SinkReason>>;
84}
85
86impl<T, E> SinkErrorOwe<T> for Result<T, E>
87where
88    E: std::fmt::Display,
89{
90    fn owe_sink<S: Into<String>>(self, msg: S) -> Result<T, StructError<SinkReason>> {
91        match self {
92            Ok(v) => Ok(v),
93            Err(e) => {
94                Err(StructError::from(SinkReason::Sink(msg.into())).with_detail(e.to_string()))
95            }
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use std::sync::mpsc;
104
105    #[derive(Clone)]
106    struct Summary(&'static str);
107
108    impl ReasonSummary for Summary {
109        fn summary(&self) -> String {
110            self.0.into()
111        }
112    }
113
114    #[test]
115    fn sink_reason_from_send_error_uses_inner_summary() {
116        let (tx, rx) = mpsc::channel();
117        drop(rx);
118        let err = tx.send(Summary("queue overflow")).unwrap_err();
119        let reason = SinkReason::from(err);
120        match reason {
121            SinkReason::Sink(msg) => assert!(msg.contains("queue overflow")),
122            other => panic!("unexpected reason: {other:?}"),
123        }
124    }
125
126    #[test]
127    fn sink_error_owe_wraps_displayable_error() {
128        let failing: Result<(), &str> = Err("io timeout");
129        let err = failing.owe_sink("flush failed").unwrap_err();
130        match err.reason() {
131            SinkReason::Sink(msg) => assert_eq!(msg, "flush failed"),
132            other => panic!("unexpected reason: {other:?}"),
133        }
134        let detail = err.detail();
135        assert_eq!(detail.as_ref().map(|s| s.as_str()), Some("io timeout"));
136    }
137
138    #[test]
139    fn sink_reason_error_codes() {
140        assert_eq!(SinkReason::Sink("test".into()).error_code(), 500);
141        assert_eq!(SinkReason::Mock.error_code(), 599);
142        assert_eq!(SinkReason::StgCtrl.error_code(), 510);
143    }
144
145    #[test]
146    fn sink_reason_error_codes_are_distinct() {
147        let codes = vec![
148            SinkReason::Sink("x".into()).error_code(),
149            SinkReason::Mock.error_code(),
150            SinkReason::StgCtrl.error_code(),
151        ];
152        // Verify all codes are different
153        let mut unique = codes.clone();
154        unique.sort();
155        unique.dedup();
156        assert_eq!(codes.len(), unique.len(), "error codes should be distinct");
157    }
158
159    #[test]
160    fn sink_reason_err_detail_sets_detail() {
161        let err = SinkReason::sink("flush failed").err_detail("io timeout");
162        assert_eq!(err.detail().as_deref(), Some("io timeout"));
163    }
164
165    #[test]
166    fn sink_reason_err_source_preserves_source_message() {
167        let err = SinkReason::sink("udp send failed").err_source(std::io::Error::other("no route"));
168        assert!(err.to_string().contains("no route"));
169    }
170}