Skip to content

evilbluebeaver/tonicerror

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tonicerror

A derive macro that generates From<YourEnum> for tonic::Status from per-variant attributes.

Usage

Add the crate to your Cargo.toml:

[dependencies]
tonicerror = "0.1.0" 
tonic = "0.14"

Derive TonicError on an enum and annotate every variant with #[tonic_error(...)]:

use tonicerror::TonicError;

#[derive(TonicError)]
enum AppError {
    #[tonic_error(code = tonic::Code::InvalidArgument, message = "invalid payload")]
    Invalid,

    #[tonic_error(code = tonic::Code::NotFound, message = "record {0} missing")]
    Missing(u64),

    #[tonic_error(code = tonic::Code::Internal, message = "storage {resource} broken")]
    Storage { resource: String },
}

The macro generates:

impl From<AppError> for tonic::Status {
    fn from(value: AppError) -> Self { ... }
}

Variant attributes

All attributes go inside #[tonic_error(...)].

code = <expr>

Required for non-transparent variants. Any expression that evaluates to tonic::Code.

#[tonic_error(code = tonic::Code::NotFound, message = "not found")]
Missing,

message = "<format string>"

Required for non-transparent variants. Supports std::format!-style placeholders:

Variant kind Placeholder syntax Resolves to
Unit (no placeholders)
Tuple (A, B) {0}, {1}, or {} (auto-increments) fields positionally
Struct { a, b } {a}, {b} fields by name
#[tonic_error(code = tonic::Code::NotFound, message = "record {0} not found")]
Missing(u64),

#[tonic_error(code = tonic::Code::Internal, message = "{resource} failed")]
Storage { resource: String },

Format specifiers are supported: {0:.2}, {name:?}, etc.

transparent

Delegates conversion to the inner type, which must itself implement Into<tonic::Status>. Only valid on single-field tuple variants.

#[tonic_error(transparent)]
Nested(NestedError),

The inner status code and message are passed through unchanged.

Transparent with a message

When combined with message, the message becomes a format string where {} / {0} refers to the inner status message:

#[tonic_error(transparent, message = "request failed: {}")]
Nested(NestedError),

If the inner status message is "forbidden resource", the output message will be "request failed: forbidden resource". The status code is still taken from the inner error.

Field-level #[from]

Placing #[from] on the single field of a tuple variant generates an additional From<Inner> for Enum conversion:

#[tonic_error(transparent, message = "request failed: {}")]
Nested(#[from] NestedError),

This lets you use ? or From::from to wrap the inner error:

let error: AppError = NestedError::Forbidden("x").into();

#[from] is only supported on single-field tuple variants. Placing it on the variant itself (rather than the field) is a compile error.

Full example

use tonicerror::TonicError;

#[derive(TonicError)]
enum AppError {
    #[tonic_error(code = tonic::Code::InvalidArgument, message = "invalid payload")]
    Invalid,

    #[tonic_error(code = tonic::Code::NotFound, message = "record {0} missing")]
    Missing(u64),

    #[tonic_error(code = tonic::Code::Internal, message = "storage {resource} broken")]
    Storage { resource: String },

    #[tonic_error(transparent, message = "downstream: {}")]
    Downstream(#[from] DownstreamError),
}

#[derive(TonicError)]
enum DownstreamError {
    #[tonic_error(code = tonic::Code::Unavailable, message = "service {0} unavailable")]
    Unavailable(&'static str),
}

fn handler() -> Result<(), AppError> {
    Err(DownstreamError::Unavailable("payments"))?
    // AppError::from(DownstreamError::Unavailable("payments"))
    // → tonic::Status { code: Unavailable, message: "downstream: service payments unavailable" }
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages