Skip to main content

wp_connector_api/errors/
sink.rs

1use derive_more::From;
2use orion_error::conversion::ToStructError;
3use orion_error::{OrionError, StructError, UvsReason};
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(UvsReason),
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
35impl From<anyhow::Error> for SinkReason {
36    fn from(e: anyhow::Error) -> Self {
37        SinkReason::Sink(format!("{}", e))
38    }
39}
40
41pub type SinkResult<T> = Result<T, SinkError>;
42
43impl SinkReason {
44    pub fn sink<S: Into<String>>(msg: S) -> Self {
45        SinkReason::Sink(msg.into())
46    }
47
48    pub fn err(self) -> SinkError {
49        self.to_err()
50    }
51
52    pub fn err_detail<S: Into<String>>(self, detail: S) -> SinkError {
53        self.to_err().with_detail(detail.into())
54    }
55
56    pub fn err_source<E>(self, source: E) -> SinkError
57    where
58        E: StdError + Send + Sync + 'static,
59    {
60        self.to_err().with_source(source)
61    }
62}
63
64pub trait SinkErrorOwe<T> {
65    fn owe_sink<S: Into<String>>(self, msg: S) -> Result<T, StructError<SinkReason>>;
66}
67
68impl<T, E> SinkErrorOwe<T> for Result<T, E>
69where
70    E: std::fmt::Display,
71{
72    fn owe_sink<S: Into<String>>(self, msg: S) -> Result<T, StructError<SinkReason>> {
73        match self {
74            Ok(v) => Ok(v),
75            Err(e) => {
76                Err(StructError::from(SinkReason::Sink(msg.into())).with_detail(e.to_string()))
77            }
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use std::sync::mpsc;
86
87    #[derive(Clone)]
88    struct Summary(&'static str);
89
90    impl ReasonSummary for Summary {
91        fn summary(&self) -> String {
92            self.0.into()
93        }
94    }
95
96    #[test]
97    fn sink_reason_from_send_error_uses_inner_summary() {
98        let (tx, rx) = mpsc::channel();
99        drop(rx);
100        let err = tx.send(Summary("queue overflow")).unwrap_err();
101        let reason = SinkReason::from(err);
102        match reason {
103            SinkReason::Sink(msg) => assert!(msg.contains("queue overflow")),
104            other => panic!("unexpected reason: {other:?}"),
105        }
106    }
107
108    #[test]
109    fn sink_error_owe_wraps_displayable_error() {
110        let failing: Result<(), &str> = Err("io timeout");
111        let err = failing.owe_sink("flush failed").unwrap_err();
112        match err.reason() {
113            SinkReason::Sink(msg) => assert_eq!(msg, "flush failed"),
114            other => panic!("unexpected reason: {other:?}"),
115        }
116        let detail = err.detail();
117        assert_eq!(detail.as_ref().map(|s| s.as_str()), Some("io timeout"));
118    }
119
120    #[test]
121    fn sink_reason_err_detail_sets_detail() {
122        let err = SinkReason::sink("flush failed").err_detail("io timeout");
123        assert_eq!(err.detail().as_deref(), Some("io timeout"));
124    }
125
126    #[test]
127    fn sink_reason_err_source_preserves_source_message() {
128        let err = SinkReason::sink("udp send failed").err_source(std::io::Error::other("no route"));
129        assert!(err.to_string().contains("no route"));
130    }
131}