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
//! [`Status`]: An `Error` container for Rust.
//!
//! An `Error` container lowers the overhead for reporting the status via `Result<_, E>`.
//!
//! Unlike the error-wrapping pattern found in `cargo` and generalized in `anyhow`, the pattern
//! implemented in [`Status`] comes from projects which try to address the
//! following requirements:
//! - Programmatically respond to both the [`Kind`] of status and the metadata, or [`Context`], of
//!   the status.
//! - Dealing with error-sites not knowing enough to describe the error but allowing the
//!   [`Context`] to be built gradually when unwinding where there is relevant information to
//!   add.
//! - Localizing the rendered message.
//! - Allowing an application to make some phrasing native to its UX.
//! - Preserving all of this while passing through FFI, IPC, and RPC (TODO [#1](https://github.com/epage/status/issues/1) [#2](https://github.com/epage/status/issues/2)).
//!
//! These requirements are addressed by trading off the usability of per-site custom messages with
//! messages built up from common building blocks.  The [`Kind`] serves as a static description of the
//! error that comes from a general, fixed collection.  Describing the exact problem and tailored
//! remediation is the responsibility of the [`Context`] which maps general, fixed keys with
//! runtime-generated data.
//!
//! [`Status`] grows with your application:
//!
//! # Prototyping
//!
//! When prototyping, you tend to focus on your proof of concept and not worry about handling all
//! corner cases or providing a clean API.  `status` helps you with this by providing an ad-hoc
//! [`Kind`], [`Unkind`], and an [`AdhocContext`].
//!
//! ```rust
//! # use std::path::Path;
//! type Status = status::Status;
//! type Result<T, E = Status> = std::result::Result<T, E>;
//!
//! fn read_file(path: &Path) -> Result<String> {
//!     std::fs::read_to_string(path)
//!         .map_err(|e| {
//!             Status::new("Failed to read file")
//!                 .with_internal(e)
//!                 .context_with(|c| c.insert("Expected value", 5))
//!         })
//! }
//!
//! fn main() -> Result<(), status::TerminatingStatus> {
//!     let content = read_file(Path::new("Cargo.toml"))?;
//!     println!("{}", content);
//!     Ok(())
//! }
//! ```
//!
//! # Maturing the code
//!
//! After prototyping, you probably want to start handling all the corner cases and have a more
//! strictly defined API.  You can do this by using an `enum` for your [`Kind`].  As you play
//! whack-a-mole in cleaning up error handling, you might want to still allow some ad-hoc
//! [`Kind`]s.
//!
//! ```rust
//! # use std::path::Path;
//! #[derive(Copy, Clone, Debug, derive_more::Display, derive_more::From)]
//! enum ErrorKind {
//!   #[display(fmt = "Failed to parse")]
//!   Parse,
//!   #[display(fmt = "{}", "_0")]
//!   #[doc(hidden)]
//!   __Other(&'static str),
//! }
//! type Status = status::Status<ErrorKind>;
//! type Result<T, E = Status> = std::result::Result<T, E>;
//!
//! fn read_file(path: &Path) -> Result<String, Status> {
//!     std::fs::read_to_string(path)
//!         .map_err(|e| {
//!             Status::new("Failed to read file").with_internal(e)
//!         })
//! }
//!
//! # fn main() -> Result<(), status::TerminatingStatus<Status>> {
//! #     let content = read_file(Path::new("Cargo.toml"))?;
//! #     println!("{}", content);
//! #     Ok(())
//! # }
//! ```
//!
//! And once you are done, you might choose to remove `ErrorKind::__Other` completely:
//!
//! ```rust
//! # use std::path::Path;
//! #[derive(Copy, Clone, Debug, derive_more::Display)]
//! enum ErrorKind {
//!   #[display(fmt = "Failed to read file")]
//!   Read,
//!   #[display(fmt = "Failed to parse")]
//!   Parse,
//! }
//! # type Status = status::Status<ErrorKind>;
//! # type Result<T, E = Status> = std::result::Result<T, E>;
//! #
//! fn read_file(path: &Path) -> Result<String, Status> {
//!     std::fs::read_to_string(path)
//!         .map_err(|e| {
//!             Status::new(ErrorKind::Read).with_internal(e)
//!         })
//! }
//! #
//! # fn main() -> Result<(), status::TerminatingStatus<Status>> {
//! #     let content = read_file(Path::new("Cargo.toml"))?;
//! #     println!("{}", content);
//! #     Ok(())
//! # }
//! ```
//!
//! The same progressions happens with [`Context`], from [`AdhocContext`] to hand-written
//! [`Context`].
//!
//! # FAQ
//!
//! ## Why `Status`?
//!
//! For an "error" crate that wanted to focus on the programmatic use-case, the typical synonyms for
//! "error" were too strong  because one man's error is another man's expected case.  For example,
//! you might have a case where you need to silence some "errors" and move on.
//!
//! ## When should my [`Kind`] be an `enum` or an error code?
//!
//! In the above examples, `enum`s were used when maturing the code but error codes still have a
//! place.
//!
//! `enum`s:
//! - Strictly typed
//!
//! Error codes:
//! - FFI, IPC, and RPC between binaries with different iterations of the error codes.
//! - Localizaton: An approach to localization is lookup tables. Keeping them separate from the
//!   application allows updating them without recompiling.
//! - Interoperating with an ecosystem standardized on an error code system (like `HRESULT`).
//!
//! When using error codes, be sure to wrap them in a newtype to avoid mixing meanings.

#![warn(missing_docs)]
#![warn(missing_debug_implementations)]

mod macros;

mod chain;
mod context;
mod ext;
mod internal;
mod kind;
mod status;
mod term;

#[cfg(not(feature = "std"))]
compile_error!("no_std support is not implemented yet");

pub use crate::chain::*;
pub use crate::context::*;
pub use crate::ext::*;
pub use crate::internal::*;
pub use crate::kind::*;
pub use crate::status::*;
pub use crate::term::*;

pub(crate) type StdError = dyn std::error::Error + 'static;

// Is there a case for having `send_sync` off?
// If not, we should probably add the wrapper from failure,
// https://github.com/rust-lang-nursery/failure/blob/master/src/sync_failure.rs
#[cfg(feature = "send_sync")]
pub(crate) type StrictError = dyn std::error::Error + Send + Sync + 'static;
#[cfg(not(feature = "send_sync"))]
pub(crate) type StrictError = dyn std::error::Error + 'static;