Skip to main content

wp_connector_api/errors/
sink.rs

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