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 — 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 — 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),
}