https://github.com/scottlamb/coded
concrete Rust error type with an `ErrorKind` enum matching Google's "canonical error codes"
https://github.com/scottlamb/coded
Last synced: 5 months ago
JSON representation
concrete Rust error type with an `ErrorKind` enum matching Google's "canonical error codes"
- Host: GitHub
- URL: https://github.com/scottlamb/coded
- Owner: scottlamb
- License: apache-2.0
- Created: 2022-01-10T23:38:15.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2022-11-03T16:12:30.000Z (almost 4 years ago)
- Last Synced: 2026-01-04T00:20:28.312Z (7 months ago)
- Language: Rust
- Homepage: https://crates.io/crates/coded
- Size: 32.2 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE-APACHE.txt
Awesome Lists containing this project
README
# coded
This is a concrete error type with an `ErrorKind` enum matching
Google's "canonical error codes". You may know these from [Google Cloud
errors](https://cloud.google.com/apis/design/errors),
[`absl::Status`](https://abseil.io/docs/cpp/guides/status), or
[gRPC status codes](https://grpc.github.io/grpc/core/md_doc_statuscodes.html).
## Status
The error code enum is exceptionally stable. The overall crate API is a work in
progress. Ideas welcome!
I'll convert [Moonfire NVR](https://github.com/scottlamb/moonfire-nvr) to this
shortly.
## Example
```rust
use coded::{bail, err, Error, ErrorBuilder, ErrorKind, ResultExt};
/// Reads application config from its well-known paths.
fn read_config() -> Result {
for path in &PATHS {
match read_json(p) {
Ok(c) => return Ok(c),
Err(e) if e.kind() == ErrorKind::NotFound => { /* keep trying */ },
// The `bail!` macro is a convenient, flexible shorthand.
Err(e) => bail!(e, msg("can't read {}", p.display()))
}
}
// `bail!` lets us write `NotFound` without `use ErrorKind::*`.
bail!(NotFound, msg("no config file at any of {:?}", PATHS))
}
/// Reads a JSON object from the given path.
///
/// This returns an `ErrorBuilder` rather than an `Error`, avoiding a redundant
/// entry in the error chain when it's wrapped by the caller.
fn read_json(p: &Path) -> Result<(), ErrorBuilder> {
// There's automatic conversion from std::io::Error to coded::ErrorBuilder which
// selects an appropriate ErrorKind.
let raw = std::fs::read(p)?;
// ResultExt::err_kind wraps any std::error::Error impl, using the supplied
// kind. It doesn't add a message.
serde_json::from_str(&raw).err_kind(ErrorKind::InvalidArgument)
}
fn main() {
if let Err(e) = inner_main() {
// `Error::chain` prints not only `e` itself but also the full chain of sources.
eprintln!("Fatal error:\n{}", e.chain());
std::process::exit(1);
}
}
fn inner_main() -> Result<(), Error> {
let config = read_config()?;
// ...
}
```
## When should I use it?
* When you want the advantages of this single well-designed,
general-purpose error code enum:
* *familiarity:* when you use the same error codes widely, the expectations
for handling them are clear.
* *monitoring:* you can meaningfully aggregate errors returned by
different APIs with these codes.
* *stability:* existing error codes and their numbers will never change.
The enum is marked `#[non_exhaustive]` because new codes could be
added, but this hasn't happened since 2015. This is great for RPC
or crate boundaries.
* *gRPC interoperability:* many services (not only Google's) use these
error codes already.
* When you want your errors to emphasize how the caller should handle them
rather than details of your implementation or dependencies. See the blog
post [Rust Error Handling](https://www.unwoundstack.com/blog/rust-error-handling.html).
* When returning `Ok` has to be cheap. A `Result<(), coded::Error>` is
one word, so returning `Ok` is [faster](https://github.com/rust-lang-deprecated/failure/issues/9)
than with larger error types.
* When you want rich human-readable error messages with the code, details,
complete error chain, and more. Currently "more" can be stack traces
(controlled by the application's `Cargo.toml` and environment variables).
In the future, perhaps
[`tracing_error::SpanTrace`](https://crates.io/crates/tracing-error) and/or
arbitrary payloads.
* When you want to return errors easily with the `err!` and `bail!` macros.
## When shouldn't I use it?
* When you don't care about error codes at all. You might be more interested
in [`anyhow`](https://crates.io/crates/anyhow),
[`eyre`](https://crates.io/crates/eyre), or
[`snafu::Whatever`](https://docs.rs/snafu/0.7.0/snafu/struct.Whatever.html).
* When you want an absolutely stable error type *right now*. As written above,
the actual enum values aren't changing, but it's a little early for the rest
of `coded`'s API to reach 1.0. (Note: if there's demand, I could split
the absolutely-stable `coded::{ErrorKind, ToErrorKind}` types into
their own crate. Then you could have a stable error type by wrapping
`coded::Error` in your own crate's public `Error` type.)
* When you need exhaustive enums with custom fields to guide the caller in
handling domain-specific errors, sometimes at the cost of API stability.
* When returning `Err` has to be cheap. `coded::Error` isn't cheap: it
requires heap allocation, and currently stack traces can't be disabled for
particular libraries or call sites.
* When you want to just pass along other crates' errors with `?` without
having to make your own wrapper around those error types and/or
`coded`. Due to [Rust's orphan
rule](https://rust-lang.github.io/chalk/book/clauses/coherence.html),
`coded::ToErrorKind` can only be implemented where the error is defined
or in `coded`. This limits ergonomics. (Once specialization is stable,
`?` *could* pass along other types using `ErrorKind::Unknown`, but this
might be more of a footgun than a help. Likewise, we could fight the
orphan rule with something like
[`inventory`](https://github.com/dtolnay/inventory), but we probably
shouldn't.)
[Error Handling in a Correctness-Critical Rust Project](http://sled.rs/errors.html)
describes how many of these apply to the [`sled`](https://crates.io/crates/sled)
database.
If you need your own error type but hate writing boilerplate, try the derive
macros from [`thiserror`](https://crates.io/crates/thiserror) or
[`snafu`](https://crates.io/crates/snafu)).
## How should I use it?
Return `coded::Error`. Use comments to document the error kinds your API
returns in certain situations. Feel free to add additional error kinds without a
semver break, as callers must match non-exhaustively.
## What's missing?
* The ability to extend the status with typed payloads as `absl::Status`
supports. I'd like to use support baked into the `std::error::Error` trait
for this (see [RFC 2895](https://github.com/rust-lang/rfcs/pull/2895)) but
it doesn't exist yet. `coded` might grow its own API for this in the
meantime.
* Support for serializing and deserializing as protobufs. There are at least
three Rust protobuf libraries ([`prost`](https://crates.io/crates/prost),
[`protobuf`](https://crates.io/crates/protobuf), and
[`quick-protobuf`](https://crates.io/crates/quick-protobuf)). We could
support each via Cargo feature flags.
## License
Apache 2.0 or MIT, at your option.
## Author
Scott Lamb <slamb@slamb.org>