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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use std::{fmt::Display, path::PathBuf};

use error::HelpMsg;

mod cli_error;
mod error;
mod macros;

pub mod report;

pub use colored;

pub use colored::Color;

/// Wrapper around a dynamic error type with an optional help message.
///
/// `Error` works a lot like `Box<dyn std::error::Error>`, but with these
/// differences:
///
/// - `Error` requires that the error is `Send`, `Sync`, and `'static`.
/// - `Error` is represented as a narrow pointer &mdash; exactly one word in
///   size instead of two.
/// - `Error` may contain a help message in order to suggest further actions a
///   user might take.
#[derive(Debug)]
pub struct Error {
    pub(crate) inner: anyhow::Error,
    pub(crate) help: Option<HelpMsg>,
}

/// Iterator of a chain of source errors.
///
/// This type is the iterator returned by [`Error::chain`].
///
/// # Example
///
/// ```
/// use narrate::Error;
/// use std::io;
///
/// pub fn underlying_io_error_kind(error: &Error) -> Option<io::ErrorKind> {
///     for cause in error.chain() {
///         if let Some(io_error) = cause.downcast_ref::<io::Error>() {
///             return Some(io_error.kind());
///         }
///     }
///     None
/// }
/// ```
#[derive(Clone, Default)]
#[repr(transparent)]
pub struct Chain<'a> {
    inner: anyhow::Chain<'a>,
}

/// `Result<T, Error>`
///
/// This is a reasonable return type to use throughout your application.
///
/// `narrate::Result` may be used with one *or* two type parameters. Therefore
/// you can import it and not worry about which `Result` you are using. Using it
/// with two types is functionally the same as rust's standard `Result` type.
///
/// ```
/// use narrate::Result;
///
/// # /*
/// fn demo1() -> Result<T> {...}
///            // ^ equivalent to std::result::Result<T, narrate::Error>
///
/// fn demo2() -> Result<T, OtherError> {...}
///            // ^ equivalent to std::result::Result<T, OtherError>
/// */
/// ```
///
/// # Example
///
/// ```
/// # pub trait Deserialize {}
/// #
/// # mod serde_json {
/// #     use super::Deserialize;
/// #     use std::io;
/// #
/// #     pub fn from_str<T: Deserialize>(json: &str) -> io::Result<T> {
/// #         unimplemented!()
/// #     }
/// # }
/// #
/// # #[derive(Debug)]
/// # struct ClusterMap;
/// #
/// # impl Deserialize for ClusterMap {}
/// #
/// # fn main() {
/// # run();
/// # }
/// #
/// use narrate::Result;
///
/// fn run() -> Result<()> {
///     # return Ok(());
///     let config = std::fs::read_to_string("cluster.json")?;
///     let map: ClusterMap = serde_json::from_str(&config)?;
///     println!("cluster info: {:#?}", map);
///     Ok(())
/// }
///
/// ```
pub type Result<T, E = Error> = core::result::Result<T, E>;

/// Provides `wrap`, `wrap_help` and `wrap_help_owned` methods for `Result`.
///
/// This trait will be sealed and should not be implemented for types outside of
/// `narrate`.
///
/// # Example
///
/// ```
/// use narrate::{ErrorWrap, Result};
/// use std::fs;
/// use std::path::PathBuf;
///
/// pub struct ImportantThing {
///     path: PathBuf,
/// }
///
/// impl ImportantThing {
///     # /**
///     pub fn detach(&mut self) -> Result<()> {...}
///     # */
///     # fn detach(&mut self) -> Result<()> {
///     #     unimplemented!()
///     # }
/// }
///
/// pub fn do_it(mut it: ImportantThing) -> Result<Vec<u8>> {
///     it.detach().wrap(|| "Failed to detach the important thing")?;
///
///     let path = &it.path;
///     let content = fs::read(path)
///         .wrap(|| format!("Failed to read instrs from {}", path.display()))?;
///
///     Ok(content)
/// }
/// ```
///
pub trait ErrorWrap<T, E>
where
    E: Send + Sync + 'static,
{
    /// Wrap an error value with additional context that is evaluated lazily
    /// only once an error does occur.
    fn wrap<C, F>(self, f: F) -> Result<T, Error>
    where
        C: Display + Send + Sync + 'static,
        F: FnOnce() -> C;

    /// Lazily evaluated error wrapper, with an additional static help message
    fn wrap_help<C, F>(self, f: F, help: &'static str) -> Result<T, Error>
    where
        C: Display + Send + Sync + 'static,
        F: FnOnce() -> C;

    /// Lazily evaluated error wrapper, with an addition owned help message
    fn wrap_help_owned<C, F>(self, f: F, help: String) -> Result<T, Error>
    where
        C: Display + Send + Sync + 'static,
        F: FnOnce() -> C;
}

/// Provide `exit_code` method for errors. Intended to be passed to
/// [`std::process::exit`].
///
/// Conforms to sysexits.h and defaults to `70` for "software error". Implementing
/// this trait for your custom error types allows your application to return the
/// correct code &mdash; even when wrapped in an [`Error`].
pub trait ExitCode {
    /// CLI application exit code
    fn exit_code(&self) -> i32 {
        exitcode::SOFTWARE
    }
}

/// Standard command line application error
#[derive(Debug)]
pub enum CliError {
    /// Invalid configuration
    Config,

    /// Cannot create file
    CreateFile(PathBuf),

    /// Invalid input data
    InputData,

    /// Supplied file not found
    InputFileNotFound(PathBuf),

    /// User not found
    NoUser(String),

    /// Host not found
    NoHost(String),

    /// No permission to perform operation
    OperationPermission(String),

    /// Operating system error
    OsErr,

    /// System file not found
    OsFileNotFound(PathBuf),

    /// Cannot read file
    ReadFile(PathBuf),

    /// Resource not found
    ResourceNotFound(String),

    /// Protocol not possible
    Protocol,

    /// Temporary/non fatal error
    Temporary,

    /// Inccorect usage
    Usage,

    /// Cannot write to file
    WriteFile(PathBuf),
}