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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! Error and Result types
#![allow(missing_docs, dead_code)] // FIXME DONOTLAND
use crate::compat::{boxed::Box, error::Error as ErrorTrait};
#[cfg(feature = "std")]
use crate::error::inner::Location;
use serde::{Deserialize, Serialize};

use self::code::ErrorCode;

mod code;
mod inner;

/// A module to export the error code in a meaningful way
pub mod errcode {
    pub use super::code::*;
}

// We box the internal error type if an allocator is available — this is (often
// significantly) more efficient in the success path.
#[cfg(feature = "alloc")]
type ErrorData = Box<inner::ErrorData>;
// When an allocator is not available, we represent the internal error inline.
// It should be smaller in this configuration, which avoids much of the cost.
#[cfg(not(feature = "alloc"))]
type ErrorData = Inner;

pub type Result<T, E = Error> = core::result::Result<T, E>;

/// The type of errors returned by Ockam functions.
///
/// Errors store:
///
/// - An [`ErrorCode`], which abstractly describe the
///   problem and allow easily matching against specific categories of error.
/// - An open-ended payload, to which arbitrary data can be attached.
/// - The "cause", of this error, if it has not been lost to serialization.
/// - Various debugging information, such as a backtrace and spantrace (which is
///   lost over serialization).
#[derive(Serialize, Deserialize)]
pub struct Error(ErrorData);
impl Error {
    /// Construct a new error given ErrorCodes and a cause.
    #[cold]
    #[track_caller]
    #[cfg(feature = "std")]
    pub fn new<E>(origin: code::Origin, kind: code::Kind, cause: E) -> Self
    where
        E: Into<Box<dyn std::error::Error + Send + Sync>>,
    {
        Self(inner::ErrorData::new(ErrorCode::new(origin, kind), cause).into())
    }

    // FIXME: figure out a better solution here...
    #[cold]
    #[track_caller]
    #[cfg(not(feature = "std"))]
    pub fn new<E>(origin: code::Origin, kind: code::Kind, cause: E) -> Self
    where
        E: core::fmt::Display,
    {
        Self(inner::ErrorData::new(ErrorCode::new(origin, kind), cause).into())
    }

    /// Construct a new error with "unknown" error codes.
    ///
    /// This ideally should not be used inside Ockam.
    #[cold]
    #[cfg(feature = "std")]
    #[track_caller]
    pub fn new_unknown<E>(origin: code::Origin, cause: E) -> Self
    where
        E: Into<Box<dyn crate::compat::error::Error + Send + Sync>>,
    {
        Self::new(origin, code::Kind::Unknown, cause)
    }

    /// Construct a new error without an apparent cause
    ///
    /// This constructor should be used for any error occurring
    /// because of a None unwrap.
    #[cold]
    #[track_caller]
    pub fn new_without_cause(origin: code::Origin, kind: code::Kind) -> Self {
        Self(inner::ErrorData::new_without_cause(origin, kind).into())
    }

    /// Return the [`ErrorCode`] that identifies this error.
    pub fn code(&self) -> ErrorCode {
        self.0.code
    }

    /// Return the source location for this error
    #[cfg(feature = "std")]
    pub(super) fn source_location(&self) -> Location {
        self.0.source_loc.clone()
    }

    /// Attach additional unstructured information to the error.
    #[must_use]
    pub fn context(mut self, key: &str, val: impl core::fmt::Display) -> Self {
        self.0.add_context(key, &val);
        self
    }
}

impl core::fmt::Debug for Error {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.0.fmt(f)
    }
}

impl core::fmt::Display for Error {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        #[cfg(feature = "std")]
        match self.source() {
            None => write!(
                f,
                "{}, source location: {}",
                self.code(),
                self.source_location()
            )?,
            Some(e) => write!(
                f,
                "{} ({}, source location: {})",
                e,
                self.code(),
                self.source_location()
            )?,
        }
        #[cfg(not(feature = "std"))]
        write!(f, "{}", self.code())?;
        Ok(())
    }
}

impl ErrorTrait for Error {
    #[cfg(feature = "std")]
    fn source(&self) -> Option<&(dyn ErrorTrait + 'static)> {
        if let Some(e) = self.0.cause() {
            let force_coercion: &(dyn ErrorTrait + 'static) = e;
            Some(force_coercion)
        } else {
            None
        }
    }
}

impl From<core::fmt::Error> for Error {
    #[cfg(feature = "std")]
    #[track_caller]
    fn from(e: core::fmt::Error) -> Self {
        Error::new(code::Origin::Application, code::Kind::Invalid, e)
    }
    #[cfg(not(feature = "std"))]
    #[track_caller]
    fn from(_: core::fmt::Error) -> Self {
        Error::new_without_cause(code::Origin::Application, code::Kind::Invalid)
    }
}

impl From<strum::ParseError> for Error {
    #[cfg(feature = "std")]
    #[track_caller]
    fn from(e: strum::ParseError) -> Self {
        Error::new(code::Origin::Application, code::Kind::Invalid, e)
    }
    #[cfg(not(feature = "std"))]
    #[track_caller]
    fn from(_: strum::ParseError) -> Self {
        Error::new_without_cause(code::Origin::Application, code::Kind::Invalid)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::errcode::{Kind, Origin};

    #[test]
    fn test_error_display() {
        let e = Error::new(Origin::Node, Kind::NotFound, "address not found");
        assert!(e.to_string().contains("address not found (origin: Node, kind: NotFound, source location: implementations/rust/ockam/ockam_core/src/error/mod.rs"))
    }

    #[test]
    fn test_error_without_cause_display() {
        let e = Error::new_without_cause(Origin::Node, Kind::NotFound);
        assert!(e.to_string().contains("origin: Node, kind: NotFound, source location: implementations/rust/ockam/ockam_core/src/error/mod.rs"))
    }
}