Skip to main content

wp_connector_api/errors/
sink.rs

1use orion_error::conversion::ToStructError;
2use orion_error::{OrionError, StructError, UnifiedReason};
3use serde::Serialize;
4use std::error::Error as StdError;
5use std::sync::mpsc::SendError;
6
7#[derive(Debug, PartialEq, Serialize, OrionError)]
8pub enum SinkReason {
9    #[orion_error(identity = "biz.sink", message = "sink error")]
10    Sink,
11    #[orion_error(identity = "biz.sink_mock", message = "set mock error")]
12    Mock,
13    #[orion_error(identity = "biz.sink_stg_ctrl", message = "stg ctrl error")]
14    StgCtrl,
15    #[orion_error(transparent)]
16    Uvs(UnifiedReason),
17}
18
19pub type SinkError = StructError<SinkReason>;
20
21pub trait ReasonSummary {
22    fn summary(&self) -> String;
23}
24
25pub type SinkResult<T> = Result<T, SinkError>;
26
27impl SinkReason {
28    pub fn sink<S: Into<String>>(msg: S) -> SinkError {
29        SinkReason::Sink.err_detail(msg)
30    }
31
32    pub fn send_error<T>(err: SendError<T>) -> SinkError
33    where
34        T: ReasonSummary,
35    {
36        SinkReason::sink(format!("send error: {}", err.0.summary()))
37    }
38
39    pub fn err(self) -> SinkError {
40        self.to_err()
41    }
42
43    pub fn err_detail<S: Into<String>>(self, detail: S) -> SinkError {
44        self.to_err().with_detail(detail.into())
45    }
46
47    pub fn err_source<E>(self, source: E) -> SinkError
48    where
49        E: StdError + Send + Sync + 'static,
50    {
51        self.to_err().with_source(source)
52    }
53}
54
55pub trait SinkErrorOwe<T> {
56    fn owe_sink<S: Into<String>>(self, msg: S) -> Result<T, StructError<SinkReason>>;
57}
58
59impl<T, E> SinkErrorOwe<T> for Result<T, E>
60where
61    E: std::fmt::Display,
62{
63    fn owe_sink<S: Into<String>>(self, msg: S) -> Result<T, StructError<SinkReason>> {
64        match self {
65            Ok(v) => Ok(v),
66            Err(e) => Err(SinkReason::Sink
67                .to_err()
68                .with_detail(format!("{}: {}", msg.into(), e))),
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use std::sync::mpsc;
77
78    #[derive(Clone)]
79    struct Summary(&'static str);
80
81    impl ReasonSummary for Summary {
82        fn summary(&self) -> String {
83            self.0.into()
84        }
85    }
86
87    #[test]
88    fn sink_reason_from_send_error_uses_inner_summary() {
89        let (tx, rx) = mpsc::channel();
90        drop(rx);
91        let err = tx.send(Summary("queue overflow")).unwrap_err();
92        let err = SinkReason::send_error(err);
93        assert_eq!(err.reason(), &SinkReason::Sink);
94        assert_eq!(err.detail().as_deref(), Some("send error: queue overflow"));
95    }
96
97    #[test]
98    fn sink_error_owe_wraps_displayable_error() {
99        let failing: Result<(), &str> = Err("io timeout");
100        let err = failing.owe_sink("flush failed").unwrap_err();
101        match err.reason() {
102            SinkReason::Sink => {}
103            other => panic!("unexpected reason: {other:?}"),
104        }
105        let detail = err.detail();
106        assert_eq!(
107            detail.as_ref().map(|s| s.as_str()),
108            Some("flush failed: io timeout")
109        );
110    }
111
112    #[test]
113    fn sink_reason_err_detail_sets_detail() {
114        let err = SinkReason::sink("flush failed");
115        assert_eq!(err.detail().as_deref(), Some("flush failed"));
116    }
117
118    #[test]
119    fn sink_reason_err_source_preserves_source_message() {
120        let err = SinkReason::Sink.err_source(std::io::Error::other("no route"));
121        let as_std = err.as_std();
122        let src = as_std.source().expect("source should be present");
123        assert!(src.to_string().contains("no route"));
124    }
125}