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

feat(types): Add gRPC Richer Error Model support (Examples) #1300

Merged
merged 2 commits into from
Mar 14, 2023
Merged
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
24 changes: 23 additions & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,26 @@ name = "json-codec-server"
path = "src/json-codec/server.rs"
required-features = ["json-codec"]

[[bin]]
name = "richer-error-client"
path = "src/richer-error/client.rs"
required-features = ["types"]

[[bin]]
name = "richer-error-server"
path = "src/richer-error/server.rs"
required-features = ["types"]

[[bin]]
name = "richer-error-client-vec"
path = "src/richer-error/client_vec.rs"
required-features = ["types"]

[[bin]]
name = "richer-error-server-vec"
path = "src/richer-error/server_vec.rs"
required-features = ["types"]

[features]
gcp = ["dep:prost-types", "tonic/tls"]
routeguide = ["dep:async-stream", "dep:futures", "tokio-stream", "dep:rand", "dep:serde", "dep:serde_json"]
Expand All @@ -254,8 +274,9 @@ tls-rustls = ["dep:hyper", "dep:hyper-rustls", "dep:tower", "tower-http/util", "
dynamic-load-balance = ["dep:tower"]
timeout = ["tokio/time", "dep:tower"]
tls-client-auth = ["tonic/tls"]
types = ["dep:tonic-types"]

full = ["gcp", "routeguide", "reflection", "autoreload", "health", "grpc-web", "tracing", "hyper-warp", "hyper-warp-multiplex", "uds", "streaming", "mock", "tower", "json-codec", "compression", "tls", "tls-rustls", "dynamic-load-balance", "timeout", "tls-client-auth"]
full = ["gcp", "routeguide", "reflection", "autoreload", "health", "grpc-web", "tracing", "hyper-warp", "hyper-warp-multiplex", "uds", "streaming", "mock", "tower", "json-codec", "compression", "tls", "tls-rustls", "dynamic-load-balance", "timeout", "tls-client-auth", "types"]
default = ["full"]

[dependencies]
Expand All @@ -267,6 +288,7 @@ tonic = { path = "../tonic" }
tonic-web = { path = "../tonic-web", optional = true }
tonic-health = { path = "../tonic-health", optional = true }
tonic-reflection = { path = "../tonic-reflection", optional = true }
tonic-types = { path = "../tonic-types", optional = true }
async-stream = { version = "0.3", optional = true }
futures = { version = "0.3", default-features = false, optional = true }
tokio-stream = { version = "0.1", optional = true }
Expand Down
30 changes: 30 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,33 @@ The autoload example requires the following crates installed globally:

* [systemfd](https://crates.io/crates/systemfd)
* [cargo-watch](https://crates.io/crates/cargo-watch)

## Richer Error

Both clients and both servers do the same thing, but using the two different
approaches. Run one of the servers in one terminal, and then run the clients
in another.

### Client using the `ErrorDetails` struct

```bash
$ cargo run --bin richer-error-client
```

### Client using a vector of error message types

```bash
$ cargo run --bin richer-error-client-vec
```

### Server using the `ErrorDetails` struct

```bash
$ cargo run --bin richer-error-server
```

### Server using a vector of error message types

```bash
$ cargo run --bin richer-error-server-vec
```
52 changes: 52 additions & 0 deletions examples/src/richer-error/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use tonic_types::StatusExt;

use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://[::1]:50051").await?;

let request = tonic::Request::new(HelloRequest {
// Valid request
// name: "Tonic".into(),
// Name cannot be empty
name: "".into(),
// Name is too long
// name: "some excessively long name".into(),
});

let response = match client.say_hello(request).await {
Ok(response) => response,
Err(status) => {
println!(" Error status received. Extracting error details...\n");

let err_details = status.get_error_details();

if let Some(bad_request) = err_details.bad_request() {
// Handle bad_request details
println!(" {:?}", bad_request);
}
if let Some(help) = err_details.help() {
// Handle help details
println!(" {:?}", help);
}
if let Some(localized_message) = err_details.localized_message() {
// Handle localized_message details
println!(" {:?}", localized_message);
}

println!();

return Ok(());
}
};

println!(" Successfull response received.\n\n {:?}\n", response);

Ok(())
}
58 changes: 58 additions & 0 deletions examples/src/richer-error/client_vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use tonic_types::{ErrorDetail, StatusExt};

use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://[::1]:50051").await?;

let request = tonic::Request::new(HelloRequest {
// Valid request
// name: "Tonic".into(),
// Name cannot be empty
name: "".into(),
// Name is too long
// name: "some excessively long name".into(),
});

let response = match client.say_hello(request).await {
Ok(response) => response,
Err(status) => {
println!(" Error status received. Extracting error details...\n");

let err_details = status.get_error_details_vec();

for (i, err_detail) in err_details.iter().enumerate() {
println!("err_detail[{i}]");
match err_detail {
ErrorDetail::BadRequest(bad_request) => {
// Handle bad_request details
println!(" {:?}", bad_request);
}
ErrorDetail::Help(help) => {
// Handle help details
println!(" {:?}", help);
}
ErrorDetail::LocalizedMessage(localized_message) => {
// Handle localized_message details
println!(" {:?}", localized_message);
}
_ => {}
}
}

println!();

return Ok(());
}
};

println!(" Successfull response received.\n\n {:?}\n", response);

Ok(())
}
71 changes: 71 additions & 0 deletions examples/src/richer-error/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use tonic::{transport::Server, Code, Request, Response, Status};
use tonic_types::{ErrorDetails, StatusExt};

use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[derive(Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
println!("Got a request from {:?}", request.remote_addr());

// Extract request data
let name = request.into_inner().name;

// Create empty ErrorDetails struct
let mut err_details = ErrorDetails::new();

// Add error details conditionally
if name.is_empty() {
err_details.add_bad_request_violation("name", "name cannot be empty");
} else if name.len() > 20 {
err_details.add_bad_request_violation("name", "name is too long");
}

if err_details.has_bad_request_violations() {
// Add aditional error details if necessary
err_details
.add_help_link("description of link", "https://resource.example.local")
.set_localized_message("en-US", "message for the user");

// Generate error status
let status = Status::with_error_details(
Code::InvalidArgument,
"request contains invalid arguments",
err_details,
);

return Err(status);
}

let reply = hello_world::HelloReply {
message: format!("Hello {}!", name),
};
Ok(Response::new(reply))
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse().unwrap();
let greeter = MyGreeter::default();

println!("GreeterServer listening on {}", addr);

Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;

Ok(())
}
71 changes: 71 additions & 0 deletions examples/src/richer-error/server_vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use tonic::{transport::Server, Code, Request, Response, Status};
use tonic_types::{BadRequest, Help, LocalizedMessage, StatusExt};

use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[derive(Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
println!("Got a request from {:?}", request.remote_addr());

// Extract request data
let name = request.into_inner().name;

// Create empty BadRequest struct
let mut bad_request = BadRequest::new(vec![]);

// Add violations conditionally
if name.is_empty() {
bad_request.add_violation("name", "name cannot be empty");
} else if name.len() > 20 {
bad_request.add_violation("name", "name is too long");
}

if !bad_request.is_empty() {
// Add aditional error details if necessary
let help = Help::with_link("description of link", "https://resource.example.local");

let localized_message = LocalizedMessage::new("en-US", "message for the user");

// Generate error status
let status = Status::with_error_details_vec(
Code::InvalidArgument,
"request contains invalid arguments",
vec![bad_request.into(), help.into(), localized_message.into()],
);

return Err(status);
}

let reply = hello_world::HelloReply {
message: format!("Hello {}!", name),
};
Ok(Response::new(reply))
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse().unwrap();
let greeter = MyGreeter::default();

println!("GreeterServer listening on {}", addr);

Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;

Ok(())
}
Loading