1use std::fmt;
2
3use crate::{
4 Diagnostic, DiagnosticInfo, DiagnosticSeverity, ErrorCategory, Source, render_diagnostic,
5};
6
7type BoxedCause = Box<dyn std::error::Error + Send + Sync + 'static>;
8
9pub struct Error {
14 diagnostics: Vec<Diagnostic>,
15 cause: Option<BoxedCause>,
16 retained_source: Option<Source>,
17}
18
19impl Error {
20 #[must_use]
29 pub fn new(info: &'static DiagnosticInfo, message: impl Into<String>) -> Self {
30 debug_assert!(
31 info.category.is_some(),
32 "{} ends an operation but declares no error category",
33 info.code
34 );
35 let mut diagnostics =
36 vec![Diagnostic::of(info, message).with_severity(DiagnosticSeverity::Error)];
37 if info.category.is_none() {
38 diagnostics.push(
39 Diagnostic::of(
40 &crate::codes::REQUEST_DIAGNOSTIC_MISSING_CATEGORY,
41 format!("{} declares no error category", info.code),
42 )
43 .with_severity(DiagnosticSeverity::Note),
44 );
45 }
46 Self {
47 diagnostics,
48 cause: None,
49 retained_source: None,
50 }
51 }
52
53 #[must_use]
55 pub fn with_diagnostic(mut self, diagnostic: Diagnostic) -> Self {
56 self.diagnostics.push(diagnostic);
57 self
58 }
59
60 #[must_use]
62 pub fn with_diagnostics(mut self, diagnostics: impl IntoIterator<Item = Diagnostic>) -> Self {
63 self.diagnostics.extend(diagnostics);
64 self
65 }
66
67 #[must_use]
69 pub fn with_cause(mut self, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
70 self.cause = Some(Box::new(cause));
71 self
72 }
73
74 #[must_use]
76 pub fn with_source(mut self, source: Source) -> Self {
77 self.retained_source = Some(source);
78 self
79 }
80
81 #[must_use]
82 pub fn diagnostics(&self) -> &[Diagnostic] {
83 &self.diagnostics
84 }
85
86 #[must_use]
89 pub fn info(&self) -> Option<&'static crate::DiagnosticInfo> {
90 self.diagnostics
91 .first()
92 .and_then(Diagnostic::registered_info)
93 }
94
95 #[must_use]
97 pub fn category(&self) -> ErrorCategory {
98 self.diagnostics
99 .iter()
100 .find(|diagnostic| diagnostic.severity() == DiagnosticSeverity::Error)
101 .and_then(Diagnostic::registered_info)
102 .and_then(|info| info.category)
103 .unwrap_or(ErrorCategory::Data)
104 }
105
106 #[must_use]
107 pub const fn retained_source(&self) -> Option<&Source> {
108 self.retained_source.as_ref()
109 }
110
111 #[must_use]
112 pub fn into_diagnostics(self) -> Vec<Diagnostic> {
113 self.diagnostics
114 }
115}
116
117impl fmt::Display for Error {
118 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119 let diagnostic = self
120 .diagnostics
121 .iter()
122 .find(|diagnostic| diagnostic.severity() == DiagnosticSeverity::Error);
123 match diagnostic {
124 Some(diagnostic) => formatter.write_str(&render_diagnostic(diagnostic)),
125 None => formatter.write_str("PowerIO operation failed without a diagnostic"),
126 }
127 }
128}
129
130impl fmt::Debug for Error {
131 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132 formatter
133 .debug_struct("Error")
134 .field("category", &self.category())
135 .field("diagnostics", &self.diagnostics)
136 .field("cause", &self.cause.as_ref().map(ToString::to_string))
137 .field("retained_source", &self.retained_source)
138 .finish()
139 }
140}
141
142impl std::error::Error for Error {
143 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
144 self.cause
145 .as_deref()
146 .map(|cause| cause as &(dyn std::error::Error + 'static))
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use std::error::Error as _;
153
154 use super::*;
155
156 #[test]
157 fn an_error_has_one_error_diagnostic_and_a_registered_category() {
158 let error = Error::new(
159 &crate::codes::VALIDATE_TIME_SERIES_SHAPE,
160 "two values for one point",
161 );
162 assert_eq!(error.category(), ErrorCategory::Data);
163 assert_eq!(error.diagnostics().len(), 1);
164 assert_eq!(error.diagnostics()[0].severity(), DiagnosticSeverity::Error);
165 assert!(
166 error
167 .to_string()
168 .starts_with("VALIDATE.TIME_SERIES.SHAPE: ")
169 );
170 }
171
172 #[test]
173 fn cause_and_shared_source_are_retained() {
174 let source = Source::from_bytes("input.bin", vec![0, 255]).unwrap();
175 let byte_pointer = source.primary_buffer().unwrap().bytes().as_ptr();
176 let error = Error::new(&crate::codes::READ_IO_READ, "read failed")
177 .with_cause(std::io::Error::other("cause"))
178 .with_source(source);
179 assert_eq!(error.source().unwrap().to_string(), "cause");
180 assert_eq!(
181 error
182 .retained_source()
183 .unwrap()
184 .primary_buffer()
185 .unwrap()
186 .bytes()
187 .as_ptr(),
188 byte_pointer
189 );
190 }
191}