Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gRPC to HTTP status code mapping with CodeExt #1664

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions tonic-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
authors = [
"Lucio Franco <[email protected]>",
"Rafael Lemos <[email protected]>"
"Rafael Lemos <[email protected]>",
]
categories = ["web-programming", "network-programming", "asynchronous"]
description = """
Expand All @@ -18,6 +18,7 @@ repository = "https://github.com/hyperium/tonic"
version = "0.11.0"

[dependencies]
http = "0.2"
prost = "0.12"
prost-types = "0.12"
tonic = {version = "0.11", path = "../tonic", default-features = false}
tonic = { version = "0.11", path = "../tonic", default-features = false }
13 changes: 9 additions & 4 deletions tonic-types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This crate also introduces the [`StatusExt`] trait and implements it in
[`tonic::Status`], allowing the implementation of the [gRPC Richer Error Model]
with [`tonic`] in a convenient way.

[`CodeExt`] adds [gRPC Richer Error Model] functionality to [`tonic::Code`]. Notably [`http::status::StatusCode`] mapping as described [here](https://cloud.google.com/apis/design/errors#generating_errors) and [here](https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto).

## Usage

Useful protobuf types are available through the [`pb`] module. They can be
Expand Down Expand Up @@ -130,16 +132,19 @@ more direct way of extracting a [`BadRequest`] error message from
[`tonic::Status`].

[`tonic::Status`]: https://docs.rs/tonic/latest/tonic/struct.Status.html
[`tonic::Code`]: https://docs.rs/tonic/latest/tonic/enum.Code.html
[`tonic`]: https://docs.rs/tonic/latest/tonic/
[gRPC Richer Error Model]: https://www.grpc.io/docs/guides/error/
[`pb`]: https://docs.rs/tonic-types/latest/tonic_types/pb/index.html
[`StatusExt`]: https://docs.rs/tonic-types/latest/tonic_types/trait.StatusExt.html
[examples]: https://github.com/hyperium/tonic/tree/master/examples
[`ErrorDetail`]: https://docs.rs/tonic-types/latest/tonic_types/enum.ErrorDetail.html
[`ErrorDetails`]: https://docs.rs/tonic-types/latest/tonic_types/struct.ErrorDetails.html
[error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
[`ErrorDetail`]: https://docs.rs/tonic-types/latest/tonic_types/enum.ErrorDetail.html
[`StatusExt`]: https://docs.rs/tonic-types/latest/tonic_types/trait.StatusExt.html
[`StatusExt::with_error_details_vec`]: https://docs.rs/tonic-types/latest/tonic_types/trait.StatusExt.html#tymethod.with_error_details_vec
[`StatusExt::get_error_details_vec`]: https://docs.rs/tonic-types/latest/tonic_types/trait.StatusExt.html#tymethod.get_error_details_vec
[Richer Error example]: https://github.com/hyperium/tonic/tree/master/examples/src/richer-error
[`StatusExt::get_details_bad_request`]: https://docs.rs/tonic-types/latest/tonic_types/trait.StatusExt.html#tymethod.get_details_bad_request
[`BadRequest`]: https://docs.rs/tonic-types/latest/tonic_types/struct.BadRequest.html
[`CodeExt`]: https://docs.rs/tonic-types/latest/tonic_types/trait.CodeExt.html
[`http::status::StatusCode`]: https://docs.rs/http/latest/http/status/struct.StatusCode.html
[Richer Error example]: https://github.com/hyperium/tonic/tree/master/examples/src/richer-error
[`BadRequest`]: https://docs.rs/tonic-types/latest/tonic_types/struct.BadRequest.html
7 changes: 1 addition & 6 deletions tonic-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,7 @@ pub mod pb {
pub use pb::Status;

mod richer_error;

pub use richer_error::{
BadRequest, DebugInfo, ErrorDetail, ErrorDetails, ErrorInfo, FieldViolation, Help, HelpLink,
LocalizedMessage, PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation,
RequestInfo, ResourceInfo, RetryInfo, RpcStatusExt, StatusExt,
};
pub use richer_error::*;

mod sealed {
pub trait Sealed {}
Expand Down
32 changes: 32 additions & 0 deletions tonic-types/src/richer_error/code_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use http::status::StatusCode as HttpCode;
use tonic::Code;

/// Add gRPC Richer Error Model functionality to [`tonic::Code`].
pub trait CodeExt: crate::sealed::Sealed {
/// gRPC to HTTP status code mappings as described in
/// <https://cloud.google.com/apis/design/errors#generating_errors>.
fn http_status(&self) -> http::status::StatusCode;
}

impl CodeExt for Code {
fn http_status(&self) -> HttpCode {
match self {
Code::Ok => HttpCode::OK,
Code::InvalidArgument | Code::FailedPrecondition | Code::OutOfRange => {
HttpCode::BAD_REQUEST
}
Code::PermissionDenied => HttpCode::FORBIDDEN,
Code::NotFound => HttpCode::NOT_FOUND,
Code::Aborted | Code::AlreadyExists => HttpCode::CONFLICT,
Code::Unauthenticated => HttpCode::UNAUTHORIZED,
Code::ResourceExhausted => HttpCode::TOO_MANY_REQUESTS,
Code::Cancelled => HttpCode::from_u16(499).expect("ivalid HTTP status code"),
Code::DataLoss | Code::Unknown | Code::Internal => HttpCode::INTERNAL_SERVER_ERROR,
Code::Unimplemented => HttpCode::NOT_IMPLEMENTED,
Code::Unavailable => HttpCode::SERVICE_UNAVAILABLE,
Code::DeadlineExceeded => HttpCode::GATEWAY_TIMEOUT,
}
}
}

impl crate::sealed::Sealed for Code {}
10 changes: 3 additions & 7 deletions tonic-types/src/richer_error/error_details/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
use std::{collections::HashMap, time};
pub(super) mod vec;

use super::std_messages::{
BadRequest, DebugInfo, ErrorInfo, FieldViolation, Help, HelpLink, LocalizedMessage,
PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation, RequestInfo,
ResourceInfo, RetryInfo,
};
use std::{collections::HashMap, time};

pub(crate) mod vec;
use super::std_messages::*;

/// Groups the standard error messages structs. Provides associated
/// functions and methods to setup and edit each error message independently.
Expand Down
5 changes: 1 addition & 4 deletions tonic-types/src/richer_error/error_details/vec.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use super::super::std_messages::{
BadRequest, DebugInfo, ErrorInfo, Help, LocalizedMessage, PreconditionFailure, QuotaFailure,
RequestInfo, ResourceInfo, RetryInfo,
};
use super::super::std_messages::*;

/// Wraps the structs corresponding to the standard error messages, allowing
/// the implementation and handling of vectors containing any of them.
Expand Down
33 changes: 33 additions & 0 deletions tonic-types/src/richer_error/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use prost::{
bytes::{Bytes, BytesMut},
DecodeError, Message,
};
use prost_types::Any;
use tonic::Code;

use crate::pb;

pub(super) trait IntoAny {
fn into_any(self) -> Any;
}

pub(super) trait FromAnyRef {
fn from_any_ref(any: &Any) -> Result<Self, DecodeError>
where
Self: Sized;
}

pub(super) fn gen_details_bytes(code: Code, message: &str, details: Vec<Any>) -> Bytes {
let status = pb::Status {
code: code as i32,
message: message.to_owned(),
details,
};

let mut buf = BytesMut::with_capacity(status.encoded_len());

// Should never panic since `buf` is initialized with sufficient capacity
status.encode(&mut buf).unwrap();

buf.freeze()
}
Loading