orion_error/traits/
into_as.rs1use std::{error::Error as StdError, fmt};
2
3use crate::{DomainReason, StructError};
4
5mod private {
6 pub trait Sealed {}
7}
8
9pub trait RawStdError: StdError + Send + Sync + 'static {}
18
19#[doc(hidden)]
20pub trait UnstructuredSource: private::Sealed {
21 fn into_struct_error<R>(self, reason: R, detail: String) -> StructError<R>
22 where
23 R: DomainReason;
24}
25
26pub trait IntoAs<T, R: DomainReason>: Sized {
27 fn into_as(self, reason: R, detail: impl Into<String>) -> Result<T, StructError<R>>;
28}
29
30#[derive(Debug)]
31pub struct RawSource<E>(E);
32
33pub fn raw_source<E>(err: E) -> RawSource<E>
74where
75 E: RawStdError,
76{
77 RawSource(err)
78}
79
80impl<E> RawSource<E> {
81 pub fn into_inner(self) -> E {
82 self.0
83 }
84
85 pub fn inner(&self) -> &E {
86 &self.0
87 }
88}
89
90impl<E: fmt::Display> fmt::Display for RawSource<E> {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 self.0.fmt(f)
93 }
94}
95
96impl<E> StdError for RawSource<E>
97where
98 E: RawStdError,
99{
100 fn source(&self) -> Option<&(dyn StdError + 'static)> {
101 Some(&self.0)
102 }
103}
104
105impl<T, E, R> IntoAs<T, R> for Result<T, E>
106where
107 E: UnstructuredSource,
108 R: DomainReason,
109{
110 fn into_as(self, reason: R, detail: impl Into<String>) -> Result<T, StructError<R>> {
111 let detail = detail.into();
112 self.map_err(|err| err.into_struct_error(reason, detail))
113 }
114}
115
116fn attach_std_source<E, R>(err: E, reason: R, detail: String) -> StructError<R>
117where
118 E: StdError + Send + Sync + 'static,
119 R: DomainReason,
120{
121 StructError::from(reason)
122 .with_detail(detail)
123 .with_std_source(err)
124}
125
126impl RawStdError for std::io::Error {}
127
128impl private::Sealed for std::io::Error {}
129
130impl UnstructuredSource for std::io::Error {
131 fn into_struct_error<R>(self, reason: R, detail: String) -> StructError<R>
132 where
133 R: DomainReason,
134 {
135 attach_std_source(self, reason, detail)
136 }
137}
138
139impl<E> private::Sealed for RawSource<E> where E: RawStdError {}
140
141impl<E> UnstructuredSource for RawSource<E>
142where
143 E: RawStdError,
144{
145 fn into_struct_error<R>(self, reason: R, detail: String) -> StructError<R>
146 where
147 R: DomainReason,
148 {
149 attach_std_source(self.0, reason, detail)
150 }
151}
152
153#[cfg(feature = "anyhow")]
154#[derive(Debug)]
155struct AnyhowStdSource(anyhow::Error);
156
157#[cfg(feature = "anyhow")]
158impl fmt::Display for AnyhowStdSource {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 self.0.fmt(f)
161 }
162}
163
164#[cfg(feature = "anyhow")]
165impl StdError for AnyhowStdSource {
166 fn source(&self) -> Option<&(dyn StdError + 'static)> {
167 self.0.source()
168 }
169}
170
171#[cfg(feature = "anyhow")]
172impl private::Sealed for anyhow::Error {}
173
174#[cfg(feature = "anyhow")]
175impl UnstructuredSource for anyhow::Error {
176 fn into_struct_error<R>(self, reason: R, detail: String) -> StructError<R>
177 where
178 R: DomainReason,
179 {
180 attach_std_source(AnyhowStdSource(self), reason, detail)
181 }
182}
183
184#[cfg(feature = "serde_json")]
185impl RawStdError for serde_json::Error {}
186
187#[cfg(feature = "serde_json")]
188impl private::Sealed for serde_json::Error {}
189
190#[cfg(feature = "serde_json")]
191impl UnstructuredSource for serde_json::Error {
192 fn into_struct_error<R>(self, reason: R, detail: String) -> StructError<R>
193 where
194 R: DomainReason,
195 {
196 attach_std_source(self, reason, detail)
197 }
198}
199
200#[cfg(feature = "toml")]
201impl RawStdError for toml::de::Error {}
202
203#[cfg(feature = "toml")]
204impl private::Sealed for toml::de::Error {}
205
206#[cfg(feature = "toml")]
207impl UnstructuredSource for toml::de::Error {
208 fn into_struct_error<R>(self, reason: R, detail: String) -> StructError<R>
209 where
210 R: DomainReason,
211 {
212 attach_std_source(self, reason, detail)
213 }
214}
215
216#[cfg(feature = "toml")]
217impl RawStdError for toml::ser::Error {}
218
219#[cfg(feature = "toml")]
220impl private::Sealed for toml::ser::Error {}
221
222#[cfg(feature = "toml")]
223impl UnstructuredSource for toml::ser::Error {
224 fn into_struct_error<R>(self, reason: R, detail: String) -> StructError<R>
225 where
226 R: DomainReason,
227 {
228 attach_std_source(self, reason, detail)
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use std::{fmt, io};
235
236 use super::{raw_source, IntoAs, RawStdError};
237 use crate::UvsReason;
238
239 #[derive(Debug)]
240 struct ThirdPartyError(&'static str);
241
242 impl fmt::Display for ThirdPartyError {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 write!(f, "{}", self.0)
245 }
246 }
247
248 impl std::error::Error for ThirdPartyError {}
249
250 impl RawStdError for ThirdPartyError {}
251
252 #[test]
253 fn test_into_as_for_io_error() {
254 let result: Result<(), io::Error> = Err(io::Error::other("disk offline"));
255
256 let err = result
257 .into_as(UvsReason::system_error(), "load config failed")
258 .expect_err("expected structured error");
259
260 assert_eq!(err.detail().as_deref(), Some("load config failed"));
261 assert_eq!(err.source_ref().unwrap().to_string(), "disk offline");
262 }
263
264 #[test]
265 fn test_into_as_for_raw_source_wrapper() {
266 let result: Result<(), ThirdPartyError> = Err(ThirdPartyError("parser aborted"));
267
268 let err = result
269 .map_err(raw_source)
270 .into_as(UvsReason::validation_error(), "parse config failed")
271 .expect_err("expected structured error");
272
273 assert_eq!(err.detail().as_deref(), Some("parse config failed"));
274 assert_eq!(err.source_ref().unwrap().to_string(), "parser aborted");
275 }
276}