typespec/error/
mod.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4//! Interfaces for working with errors.
5
6#[cfg(feature = "http")]
7use crate::http::{RawResponse, StatusCode};
8use std::borrow::Cow;
9use std::fmt::{Debug, Display};
10
11/// A convenience alias for `Result` where the error type is hard coded to [`Error`].
12pub type Result<T> = std::result::Result<T, Error>;
13
14/// The kind of error.
15///
16/// The classification of error is intentionally fairly coarse.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub enum ErrorKind {
19    /// An error from the AMQP protocol.
20    #[cfg(feature = "amqp")]
21    Amqp,
22    /// An HTTP status code that was not expected.
23    #[cfg(feature = "http")]
24    HttpResponse {
25        /// An HTTP status code.
26        status: StatusCode,
27        /// An error code returned by the service, or a friendly description of the `status`.
28        error_code: Option<String>,
29        /// The raw response returned by the service.
30        raw_response: Option<Box<RawResponse>>,
31    },
32    /// An error performing IO.
33    Io,
34    /// An error converting data.
35    DataConversion,
36    /// An error getting an API credential token.
37    Credential,
38    /// An error having to do with the mock framework.
39    MockFramework,
40    /// A catch all for other kinds of errors.
41    Other,
42}
43
44impl ErrorKind {
45    /// Consumes the `ErrorKind` and converts to an [`Error`].
46    pub fn into_error(self) -> Error {
47        Error {
48            context: Repr::Simple(self),
49        }
50    }
51}
52
53impl Display for ErrorKind {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            #[cfg(feature = "http")]
57            ErrorKind::HttpResponse {
58                status, error_code, ..
59            } => f
60                .debug_tuple("HttpResponse")
61                .field(status)
62                .field(&error_code.as_deref().unwrap_or("(unknown error code)"))
63                .finish(),
64            ErrorKind::Io => f.write_str("Io"),
65            ErrorKind::DataConversion => f.write_str("DataConversion"),
66            ErrorKind::Credential => f.write_str("Credential"),
67            ErrorKind::MockFramework => f.write_str("MockFramework"),
68            ErrorKind::Other => f.write_str("Other"),
69            #[cfg(feature = "amqp")]
70            ErrorKind::Amqp => f.write_str("Amqp"),
71        }
72    }
73}
74
75/// An error encountered when communicating with the service.
76#[derive(Debug)]
77pub struct Error {
78    context: Repr,
79}
80
81impl Error {
82    /// Create a new `Error` based on an [`ErrorKind`] and an underlying error cause.
83    pub fn new<E>(kind: ErrorKind, error: E) -> Self
84    where
85        E: Into<Box<dyn std::error::Error + Send + Sync>>,
86    {
87        Self {
88            context: Repr::Custom(Custom {
89                kind,
90                error: error.into(),
91            }),
92        }
93    }
94
95    /// Create a new `Error` based on an [`ErrorKind`], an underlying error cause, and a message.
96    #[must_use]
97    pub fn with_error<E, C>(kind: ErrorKind, error: E, message: C) -> Self
98    where
99        E: Into<Box<dyn std::error::Error + Send + Sync>>,
100        C: Into<Cow<'static, str>>,
101    {
102        Self {
103            context: Repr::CustomMessage(
104                Custom {
105                    kind,
106                    error: error.into(),
107                },
108                message.into(),
109            ),
110        }
111    }
112
113    /// Create a new `Error` based on an [`ErrorKind`], an underlying error cause, and a function that returns a message.
114    #[must_use]
115    pub fn with_error_fn<E, F, C>(kind: ErrorKind, error: E, f: F) -> Self
116    where
117        E: Into<Box<dyn std::error::Error + Send + Sync>>,
118        F: FnOnce() -> C,
119        C: Into<Cow<'static, str>>,
120    {
121        Self::with_error(kind, error, f())
122    }
123
124    /// Create an `Error` based on an [`ErrorKind`] and a message.
125    #[must_use]
126    pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
127    where
128        C: Into<Cow<'static, str>>,
129    {
130        Self {
131            context: Repr::SimpleMessage(kind, message.into()),
132        }
133    }
134
135    /// Creates an `Error` based on an [`ErrorKind`] and a function that returns a message.
136    #[must_use]
137    pub fn with_message_fn<F, C>(kind: ErrorKind, f: F) -> Self
138    where
139        F: FnOnce() -> C,
140        C: Into<Cow<'static, str>>,
141    {
142        Self::with_message(kind, f())
143    }
144
145    /// Wrap this error with an additional message.
146    #[must_use]
147    pub fn with_context<C>(self, message: C) -> Self
148    where
149        C: Into<Cow<'static, str>>,
150    {
151        Self::with_error(self.kind().clone(), self, message)
152    }
153
154    /// Wrap this error with an additional message returned from a function.
155    #[must_use]
156    pub fn with_context_fn<F, C>(self, f: F) -> Self
157    where
158        F: FnOnce() -> C,
159        C: Into<Cow<'static, str>>,
160    {
161        self.with_context(f())
162    }
163
164    /// Get the [`ErrorKind`] of this `Error`.
165    pub fn kind(&self) -> &ErrorKind {
166        match &self.context {
167            Repr::Simple(kind)
168            | Repr::SimpleMessage(kind, ..)
169            | Repr::Custom(Custom { kind, .. })
170            | Repr::CustomMessage(Custom { kind, .. }, _) => kind,
171        }
172    }
173
174    /// If this error is an HTTP response error, return the associated status code.
175    #[cfg(feature = "http")]
176    pub fn http_status(&self) -> Option<StatusCode> {
177        match &self.kind() {
178            ErrorKind::HttpResponse { status, .. } => Some(*status),
179            _ => None,
180        }
181    }
182
183    /// Consumes the `Error`, returning its inner error, if any.
184    pub fn into_inner(self) -> std::result::Result<Box<dyn std::error::Error + Send + Sync>, Self> {
185        match self.context {
186            Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
187                Ok(error)
188            }
189            _ => Err(self),
190        }
191    }
192
193    /// Consumes the error, attempting to downcast the inner error as the type provided.
194    ///
195    /// Returns `Err(self)` if the downcast is not possible.
196    pub fn into_downcast<T: std::error::Error + 'static>(self) -> std::result::Result<T, Self> {
197        if self.downcast_ref::<T>().is_none() {
198            return Err(self);
199        }
200        // Unwrapping is ok here since we already check above that the downcast will work
201        Ok(*self.into_inner()?.downcast().expect("downcast is Some(T)"))
202    }
203
204    /// Returns a reference to the inner error wrapped by this error, if any.
205    pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
206        match &self.context {
207            Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
208                Some(error.as_ref())
209            }
210            _ => None,
211        }
212    }
213
214    /// Returns a reference to the inner error, if any, downcast to the type provided.
215    pub fn downcast_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
216        self.get_ref()?.downcast_ref()
217    }
218
219    /// Returns a mutable reference to the inner error wrapped by this error, if any.
220    pub fn get_mut(&mut self) -> Option<&mut (dyn std::error::Error + Send + Sync + 'static)> {
221        match &mut self.context {
222            Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
223                Some(error.as_mut())
224            }
225            _ => None,
226        }
227    }
228
229    /// Returns a mutable reference to the inner error, if any, downcasting to the type provided.
230    pub fn downcast_mut<T: std::error::Error + 'static>(&mut self) -> Option<&mut T> {
231        self.get_mut()?.downcast_mut()
232    }
233}
234
235impl std::error::Error for Error {
236    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
237        match &self.context {
238            Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, _) => {
239                Some(&**error)
240            }
241            _ => None,
242        }
243    }
244}
245
246impl From<ErrorKind> for Error {
247    fn from(kind: ErrorKind) -> Self {
248        Self {
249            context: Repr::Simple(kind),
250        }
251    }
252}
253
254impl From<std::io::Error> for Error {
255    fn from(error: std::io::Error) -> Self {
256        Self::new(ErrorKind::Io, error)
257    }
258}
259
260impl From<std::str::ParseBoolError> for Error {
261    fn from(error: std::str::ParseBoolError) -> Self {
262        Self::new(ErrorKind::DataConversion, error)
263    }
264}
265
266impl From<std::num::ParseIntError> for Error {
267    fn from(error: std::num::ParseIntError) -> Self {
268        Self::new(ErrorKind::DataConversion, error)
269    }
270}
271
272impl From<base64::DecodeError> for Error {
273    fn from(error: base64::DecodeError) -> Self {
274        Self::new(ErrorKind::DataConversion, error)
275    }
276}
277
278#[cfg(feature = "json")]
279impl From<serde_json::Error> for Error {
280    fn from(error: serde_json::Error) -> Self {
281        Self::new(ErrorKind::DataConversion, error)
282    }
283}
284
285impl From<std::string::FromUtf8Error> for Error {
286    fn from(error: std::string::FromUtf8Error) -> Self {
287        Self::new(ErrorKind::DataConversion, error)
288    }
289}
290
291impl From<std::str::Utf8Error> for Error {
292    fn from(error: std::str::Utf8Error) -> Self {
293        Self::new(ErrorKind::DataConversion, error)
294    }
295}
296
297impl From<url::ParseError> for Error {
298    fn from(error: url::ParseError) -> Self {
299        Self::new(ErrorKind::DataConversion, error)
300    }
301}
302
303impl From<core::convert::Infallible> for Error {
304    fn from(_: core::convert::Infallible) -> Self {
305        panic!("no error")
306    }
307}
308
309impl Display for Error {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        match &self.context {
312            Repr::Simple(kind) => std::fmt::Display::fmt(&kind, f),
313            Repr::SimpleMessage(_, message) => f.write_str(message),
314            Repr::Custom(Custom { error, .. }) => std::fmt::Display::fmt(&error, f),
315            Repr::CustomMessage(_, message) => f.write_str(message),
316        }
317    }
318}
319
320/// An extension to the [`Result`] type that easy allows creating [`Error`] values from existing errors.
321///
322/// This trait cannot be implemented on custom types and is meant for usage with `Result`.
323pub trait ResultExt<T>: private::Sealed {
324    /// Creates a new error with the specified [`ErrorKind`].
325    fn with_kind(self, kind: ErrorKind) -> Result<T>
326    where
327        Self: Sized;
328
329    /// Creates a new error with the specified [`ErrorKind`] and an additional message.
330    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
331    where
332        Self: Sized,
333        C: Into<Cow<'static, str>>;
334
335    /// Creates a new error with the specified [`ErrorKind`] and a function that returns an additional message.
336    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
337    where
338        Self: Sized,
339        F: FnOnce() -> C,
340        C: Into<Cow<'static, str>>;
341}
342
343mod private {
344    pub trait Sealed {}
345
346    impl<T, E> Sealed for std::result::Result<T, E> where E: std::error::Error + Send + Sync + 'static {}
347}
348
349impl<T, E> ResultExt<T> for std::result::Result<T, E>
350where
351    E: std::error::Error + Send + Sync + 'static,
352{
353    fn with_kind(self, kind: ErrorKind) -> Result<T>
354    where
355        Self: Sized,
356    {
357        self.map_err(|e| Error::new(kind, e))
358    }
359
360    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
361    where
362        Self: Sized,
363        C: Into<Cow<'static, str>>,
364    {
365        self.map_err(|e| Error {
366            context: Repr::CustomMessage(
367                Custom {
368                    error: Box::new(e),
369                    kind,
370                },
371                message.into(),
372            ),
373        })
374    }
375
376    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
377    where
378        Self: Sized,
379        F: FnOnce() -> C,
380        C: Into<Cow<'static, str>>,
381    {
382        self.with_context(kind, f())
383    }
384}
385
386#[derive(Debug)]
387enum Repr {
388    Simple(ErrorKind),
389    SimpleMessage(ErrorKind, Cow<'static, str>),
390    Custom(Custom),
391    CustomMessage(Custom, Cow<'static, str>),
392}
393
394#[derive(Debug)]
395struct Custom {
396    kind: ErrorKind,
397    error: Box<dyn std::error::Error + Send + Sync>,
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    use std::io;
404
405    #[allow(
406        dead_code,
407        unconditional_recursion,
408        clippy::extra_unused_type_parameters
409    )]
410    fn ensure_send<T: Send>() {
411        ensure_send::<Error>();
412    }
413
414    #[derive(thiserror::Error, Debug)]
415    enum IntermediateError {
416        #[error("second error")]
417        Io(#[from] std::io::Error),
418    }
419
420    fn create_error() -> Error {
421        // Create a nested std::io::Error
422        let inner = io::Error::new(io::ErrorKind::BrokenPipe, "third error");
423        let inner: IntermediateError = inner.into();
424        let inner = io::Error::new(io::ErrorKind::ConnectionAborted, inner);
425
426        // Wrap that io::Error in this crate's Error type
427        Error::new(ErrorKind::Io, inner)
428    }
429
430    #[test]
431    fn errors_display_properly() {
432        let error = create_error();
433
434        // Generate the display and error chain
435        let mut error: &dyn std::error::Error = &error;
436        let display = format!("{error}");
437        let mut errors = vec![];
438        while let Some(cause) = error.source() {
439            errors.push(format!("{cause}"));
440            error = cause;
441        }
442
443        assert_eq!(display, "second error");
444        assert_eq!(errors.join(","), "second error,third error");
445
446        let inner = io::Error::new(io::ErrorKind::BrokenPipe, "third error");
447        let error: Result<()> = std::result::Result::<(), std::io::Error>::Err(inner)
448            .with_context(ErrorKind::Io, "oh no broken pipe!");
449        assert_eq!(format!("{}", error.unwrap_err()), "oh no broken pipe!");
450    }
451
452    #[test]
453    fn downcasting_works() {
454        let error = &create_error() as &dyn std::error::Error;
455        assert!(error.is::<Error>());
456        let downcasted = error
457            .source()
458            .unwrap()
459            .downcast_ref::<std::io::Error>()
460            .unwrap();
461        assert_eq!(format!("{downcasted}"), "second error");
462    }
463
464    #[test]
465    fn turn_into_inner_error() {
466        let error = create_error();
467        let inner = error.into_inner().unwrap();
468        let inner = inner.downcast_ref::<std::io::Error>().unwrap();
469        assert_eq!(format!("{inner}"), "second error");
470
471        let error = create_error();
472        let inner = error.get_ref().unwrap();
473        let inner = inner.downcast_ref::<std::io::Error>().unwrap();
474        assert_eq!(format!("{inner}"), "second error");
475
476        let mut error = create_error();
477        let inner = error.get_mut().unwrap();
478        let inner = inner.downcast_ref::<std::io::Error>().unwrap();
479        assert_eq!(format!("{inner}"), "second error");
480    }
481
482    #[test]
483    fn set_result_kind() {
484        let result = std::result::Result::<(), _>::Err(create_error());
485        let result = result.with_kind(ErrorKind::Io);
486        assert_eq!(&ErrorKind::Io, result.unwrap_err().kind());
487    }
488}