wp_connector_api/errors/
sink.rs1use 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}