1use std::fmt;
12
13#[derive(Debug)]
16pub struct TerminalReplayRefusal(pub(crate) detcore_model::schedule::boundary::Error);
17
18impl fmt::Display for TerminalReplayRefusal {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 write!(f, "terminal replay refusal: {}", self.0)
21 }
22}
23
24impl std::error::Error for TerminalReplayRefusal {
25 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
26 Some(&self.0)
27 }
28}
29
30#[derive(Debug)]
35pub struct PriorityHistoryRefusal(pub(crate) String);
36
37impl fmt::Display for PriorityHistoryRefusal {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "terminal replay refusal: {}", self.0)
40 }
41}
42
43impl std::error::Error for PriorityHistoryRefusal {}
44
45#[derive(Clone, Debug, Eq, PartialEq)]
49pub struct ReplayDepartureError {
50 pub(crate) summary: Option<detcore_model::replay_departure::Summary>,
51 pub(crate) publication_refusal: Option<(
52 crate::types::DetTid,
53 crate::tool_global::replay_departure::Refusal,
54 )>,
55}
56
57impl ReplayDepartureError {
58 pub fn summary(&self) -> Option<&detcore_model::replay_departure::Summary> {
60 self.summary.as_ref()
61 }
62
63 pub fn in_error(error: &anyhow::Error) -> Option<&Self> {
67 if let Some(pair) = error.downcast_ref::<FinalizationFailure>() {
68 return Self::in_error(pair.primary());
69 }
70 if let Some(departure) = error.downcast_ref::<Self>() {
71 return Some(departure);
72 }
73 error
74 .chain()
75 .find_map(|cause| match cause.downcast_ref::<reverie::Error>() {
76 Some(reverie::Error::Tool(inner)) => Self::in_error(inner),
77 _ => None,
78 })
79 }
80}
81
82impl fmt::Display for ReplayDepartureError {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 f.write_str("HERMIT_REPLAY_DEPARTURE: ")?;
85 if let Some(summary) = &self.summary {
86 let first = &summary.first;
87 write!(
88 f,
89 "{} nonadvancing change-point detection(s); first {:?} in thread {} at {}, preceding {}; ",
90 summary.count, first.source, first.thread, first.observed, first.previous
91 )?;
92 }
93 if let Some((thread, reason)) = &self.publication_refusal {
94 write!(
95 f,
96 "finding publication from thread {thread} refused ({reason:?}); "
97 )?;
98 }
99 f.write_str("refusing faithful replay completion")
100 }
101}
102
103impl std::error::Error for ReplayDepartureError {}
104
105#[derive(Debug)]
109pub struct FinalizationFailure {
110 primary: anyhow::Error,
111 secondary: anyhow::Error,
112}
113
114impl FinalizationFailure {
115 pub fn primary(&self) -> &anyhow::Error {
117 &self.primary
118 }
119
120 pub fn secondary(&self) -> &anyhow::Error {
122 &self.secondary
123 }
124}
125
126impl fmt::Display for FinalizationFailure {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 write!(
129 f,
130 "{}; additionally, finalization failed: {:#}",
131 self.primary, self.secondary
132 )
133 }
134}
135
136impl std::error::Error for FinalizationFailure {
137 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
138 Some(self.primary.as_ref())
139 }
140}
141
142pub fn combine_finalization<T>(
145 primary: anyhow::Result<T>,
146 finalization: anyhow::Result<()>,
147) -> anyhow::Result<T> {
148 match (primary, finalization) {
149 (Ok(value), Ok(())) => Ok(value),
150 (Err(error), Ok(())) | (Ok(_), Err(error)) => Err(error),
151 (Err(primary), Err(secondary)) => Err(FinalizationFailure { primary, secondary }.into()),
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[derive(Debug)]
160 struct Primary;
161 impl fmt::Display for Primary {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.write_str("primary identity")
164 }
165 }
166 impl std::error::Error for Primary {}
167
168 #[derive(Debug)]
169 struct Secondary;
170 impl fmt::Display for Secondary {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.write_str("secondary identity")
173 }
174 }
175 impl std::error::Error for Secondary {}
176
177 #[test]
178 fn both_failures_remain_owned_but_only_primary_classifies() {
179 let error =
180 combine_finalization::<()>(Err(Primary.into()), Err(Secondary.into())).unwrap_err();
181 let pair = error.downcast_ref::<FinalizationFailure>().unwrap();
182 assert!(pair.primary().is::<Primary>());
183 assert!(pair.secondary().is::<Secondary>());
184 assert!(error.chain().any(|cause| cause.is::<Primary>()));
185 assert!(!error.chain().any(|cause| cause.is::<Secondary>()));
186 assert!(format!("{error:#}").contains("secondary identity"));
187 }
188
189 #[test]
190 fn either_failure_refuses_and_success_keeps_value() {
191 assert_eq!(combine_finalization(Ok(7), Ok(())).unwrap(), 7);
192 assert!(
193 combine_finalization::<()>(Err(Primary.into()), Ok(()))
194 .unwrap_err()
195 .is::<Primary>()
196 );
197 assert!(
198 combine_finalization(Ok(7), Err(Secondary.into()))
199 .unwrap_err()
200 .is::<Secondary>()
201 );
202 }
203}