feat(types): Add gRPC Richer Error Model support (Examples) (#1300)
* types: remove unnecessary cloning from `ErrorDetails` getters Breaking change. * examples: add `richer-error` examples Following implementation at flemosr/tonic-richer-error.
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user