identity_core/common/
single_struct_error.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::borrow::Cow;
use std::error::Error;
use std::fmt::Debug;
use std::fmt::Display;

/// A container implementing the [`std::error::Error`] trait.
///
/// Instances always carry a corresponding `kind` of type `T` and may be extended with a custom error
/// message and source.
///
/// This type is mainly designed to accommodate for the [single struct error design pattern](https://nrc.github.io/error-docs/error-design/error-type-design.html#single-struct-style).
///
/// When used in a specialized context it is recommended to use a type alias (i.e. `type MyError =
/// SingleStructError<MyErrorKind>`).
#[derive(Debug)]
pub struct SingleStructError<T: Debug + Display> {
  repr: Repr<T>,
}

impl<T: Display + Debug> Display for SingleStructError<T> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self.repr {
      Repr::Simple(ref kind) => write!(f, "{kind}"),
      Repr::Extensive(ref extensive) => {
        write!(f, "{}", &extensive.kind)?;
        let Some(ref message) = extensive.message else {
          return Ok(());
        };
        write!(f, " message: {}", message.as_ref())
      }
    }
  }
}

impl<T: Debug + Display> Error for SingleStructError<T> {
  fn source(&self) -> Option<&(dyn Error + 'static)> {
    self.extensive().and_then(|err| {
      err
        .source
        .as_ref()
        .map(|source| source.as_ref() as &(dyn Error + 'static))
    })
  }
}

#[derive(Debug)]
struct Extensive<T: Debug + Display> {
  kind: T,
  source: Option<Box<dyn Error + Send + Sync + 'static>>,
  message: Option<Cow<'static, str>>,
}

#[derive(Debug)]
enum Repr<T: Debug + Display> {
  Simple(T),
  Extensive(Box<Extensive<T>>),
}

impl<T: Debug + Display> From<T> for SingleStructError<T> {
  fn from(kind: T) -> Self {
    Self::new(kind)
  }
}

impl<T: Debug + Display> From<Box<Extensive<T>>> for SingleStructError<T> {
  fn from(extensive: Box<Extensive<T>>) -> Self {
    Self {
      repr: Repr::Extensive(extensive),
    }
  }
}

impl<T: Debug + Display> SingleStructError<T> {
  /// Constructs a new [`SingleStructError`].  
  pub fn new(kind: T) -> Self {
    Self {
      repr: Repr::Simple(kind),
    }
  }

  /// Returns a reference to the corresponding `kind` of this error.
  pub fn kind(&self) -> &T {
    match self.repr {
      Repr::Simple(ref cause) => cause,
      Repr::Extensive(ref extensive) => &extensive.kind,
    }
  }

  /// Converts this error into the corresponding `kind` of this error.
  pub fn into_kind(self) -> T {
    match self.repr {
      Repr::Simple(cause) => cause,
      Repr::Extensive(extensive) => extensive.kind,
    }
  }

  /// Returns a reference to the custom message of the [`SingleStructError`] if it was set.
  pub fn custom_message(&self) -> Option<&str> {
    self
      .extensive()
      .into_iter()
      .flat_map(|extensive| extensive.message.as_deref())
      .next()
  }

  /// Returns a reference to the attached source of the [`SingleStructError`] if it was set.
  pub fn source_ref(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
    self.extensive().and_then(|extensive| extensive.source.as_deref())
  }

  /// Converts this error into the source error if it was set.
  pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync + 'static>> {
    self.into_extensive().source
  }

  fn extensive(&self) -> Option<&Extensive<T>> {
    match self.repr {
      Repr::Extensive(ref extensive) => Some(extensive.as_ref()),
      _ => None,
    }
  }

  fn into_extensive(self) -> Box<Extensive<T>> {
    match self.repr {
      Repr::Extensive(extensive) => extensive,
      Repr::Simple(kind) => Box::new(Extensive {
        kind,
        source: None,
        message: None,
      }),
    }
  }

  /// Updates the `source` of the [`SingleStructError`].
  pub fn with_source(self, source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
    self._with_source(source.into())
  }

  fn _with_source(self, source: Box<dyn Error + Send + Sync + 'static>) -> Self {
    let mut extensive = self.into_extensive();
    extensive.as_mut().source = Some(source);
    Self::from(extensive)
  }

  /// Updates the custom message of the [`SingleStructError`].
  pub fn with_custom_message(self, message: impl Into<Cow<'static, str>>) -> Self {
    self._with_custom_message(message.into())
  }

  fn _with_custom_message(self, message: Cow<'static, str>) -> Self {
    let mut extensive = self.into_extensive();
    extensive.as_mut().message = Some(message);
    Self::from(extensive)
  }
}