wp_connector_api/errors/
sink.rs

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