wp_connector_api/errors/
sink.rs1use 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 SinkReason::Sink(_) => 500, SinkReason::Mock => 599, SinkReason::StgCtrl => 510, 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 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}