Skip to content

Commit

Permalink
Error: adds conversion with tonic::Status
Browse files Browse the repository at this point in the history
  • Loading branch information
Tpt committed Dec 26, 2024
1 parent fc242c0 commit c9b9c5c
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 80 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ serde_json = "1"
tokio = "1.42"
tokio-stream = "0.1.16"
tonic = { version = "0.12.3", default-features = false }
tonic-012 = { package = "tonic", version = "0.12.3", default-features = false }
tonic-build = "0.12.3"
tower-service = "0.3.3"
tower = "0.5.1"
Expand Down
2 changes: 2 additions & 0 deletions error/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ rust-version.workspace = true
axum-07 = ["dep:axum-core-04", "http"]
http = ["dep:http", "dep:serde_json", "serde"]
serde = ["dep:serde"]
tonic-012 = ["dep:tonic-012"]

[dependencies]
axum-core-04 = { workspace = true, optional = true }
http = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = { workspace = true, optional = true }
tonic-012 = { workspace = true, optional = true }
3 changes: 2 additions & 1 deletion error/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Please don't use it directly but rely on `twurst-client` or `twurst-server`that
- `serde` allows to (de)serialize the error using [Serde](https://serde.rs/) following the official Twirp serialization.
- `http` allows to convert between [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html) objects and Twirp errors,
properly deserializing the error if possible, or building an as good as possible equivalent if not.
- `axum-07` implements the [`axum::response::IntoResponse`](https://docs.rs/axum/latest/axum/response/trait.IntoResponse.html) trait on [`TwirpError`].
- `axum-07` implements the [`axum::response::IntoResponse`](https://docs.rs/axum/0.7/axum/response/trait.IntoResponse.html) trait on `TwirpError`.
- `tonic-012` implements `From` conversions between `TwirpError`and [`tonic::Status`](https://docs.rs/tonic/0.12/tonic/struct.Status.html) in both directions.

## License

Expand Down
69 changes: 69 additions & 0 deletions error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,75 @@ impl axum_core_04::response::IntoResponse for TwirpError {
}
}

#[cfg(feature = "tonic-012")]
impl From<TwirpErrorCode> for tonic_012::Code {
#[inline]
fn from(code: TwirpErrorCode) -> Self {
match code {
TwirpErrorCode::Canceled => Self::Cancelled,
TwirpErrorCode::Unknown => Self::Unknown,
TwirpErrorCode::InvalidArgument => Self::InvalidArgument,
TwirpErrorCode::Malformed => Self::InvalidArgument,
TwirpErrorCode::DeadlineExceeded => Self::DeadlineExceeded,
TwirpErrorCode::NotFound => Self::NotFound,
TwirpErrorCode::BadRoute => Self::NotFound,
TwirpErrorCode::AlreadyExists => Self::AlreadyExists,
TwirpErrorCode::PermissionDenied => Self::PermissionDenied,
TwirpErrorCode::Unauthenticated => Self::Unauthenticated,
TwirpErrorCode::ResourceExhausted => Self::ResourceExhausted,
TwirpErrorCode::FailedPrecondition => Self::FailedPrecondition,
TwirpErrorCode::Aborted => Self::Aborted,
TwirpErrorCode::OutOfRange => Self::OutOfRange,
TwirpErrorCode::Unimplemented => Self::Unimplemented,
TwirpErrorCode::Internal => Self::Internal,
TwirpErrorCode::Unavailable => Self::Unavailable,
TwirpErrorCode::Dataloss => Self::DataLoss,
}
}
}

#[cfg(feature = "tonic-012")]
impl From<TwirpError> for tonic_012::Status {
#[inline]
fn from(error: TwirpError) -> Self {
Self::new(error.code().into(), error.into_message())
}
}

#[cfg(feature = "tonic-012")]
impl From<tonic_012::Code> for TwirpErrorCode {
#[inline]
fn from(code: tonic_012::Code) -> TwirpErrorCode {
match code {
tonic_012::Code::Cancelled => Self::Canceled,
tonic_012::Code::Unknown => Self::Unknown,
tonic_012::Code::InvalidArgument => Self::InvalidArgument,
tonic_012::Code::DeadlineExceeded => Self::DeadlineExceeded,
tonic_012::Code::NotFound => Self::NotFound,
tonic_012::Code::AlreadyExists => Self::AlreadyExists,
tonic_012::Code::PermissionDenied => Self::PermissionDenied,
tonic_012::Code::Unauthenticated => Self::Unauthenticated,
tonic_012::Code::ResourceExhausted => Self::ResourceExhausted,
tonic_012::Code::FailedPrecondition => Self::FailedPrecondition,
tonic_012::Code::Aborted => Self::Aborted,
tonic_012::Code::OutOfRange => Self::OutOfRange,
tonic_012::Code::Unimplemented => Self::Unimplemented,
tonic_012::Code::Internal => Self::Internal,
tonic_012::Code::Unavailable => Self::Unavailable,
tonic_012::Code::DataLoss => Self::Dataloss,
tonic_012::Code::Ok => Self::Unknown,
}
}
}

#[cfg(feature = "tonic-012")]
impl From<tonic_012::Status> for TwirpError {
#[inline]
fn from(status: tonic_012::Status) -> TwirpError {
Self::wrap(status.code().into(), status.message().to_string(), status)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license.workspace = true
rust-version.workspace = true

[features]
grpc = ["dep:tonic", "dep:tokio-stream", "dep:pin-project-lite"]
grpc = ["dep:tonic", "dep:tokio-stream", "dep:pin-project-lite", "twurst-error/tonic-012"]

[dependencies]
twurst-error = { workspace = true, features = ["axum-07"] }
Expand Down
87 changes: 9 additions & 78 deletions server/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,7 @@ impl<
fn call(&mut self, request: tonic::Request<I>) -> Self::Future {
let (request, parts) = grpc_to_twirp_request(request);
let result_future = (self.callback)(self.service.clone(), request, parts);
Box::pin(async move {
Ok(tonic::Response::new(
result_future.await.map_err(grpc_status_for_twirp_error)?,
))
})
Box::pin(async move { Ok(tonic::Response::new(result_future.await?)) })
}
}

Expand All @@ -358,12 +354,9 @@ impl<
let (request, parts) = grpc_to_twirp_request(request);
let result_future = (self.callback)(self.service.clone(), request, parts);
Box::pin(async move {
Ok(tonic::Response::new(Box::pin(
result_future
.await
.map_err(grpc_status_for_twirp_error)?
.map(|item| item.map_err(grpc_status_for_twirp_error)),
) as Self::ResponseStream))
Ok(tonic::Response::new(
Box::pin(result_future.await?.map(|item| Ok(item?))) as Self::ResponseStream,
))
})
}
}
Expand All @@ -384,11 +377,7 @@ impl<
let (request, parts) = grpc_to_twirp_request(request);
let request = GrpcClientStream { stream: request };
let result_future = (self.callback)(self.service.clone(), request, parts);
Box::pin(async move {
Ok(tonic::Response::new(
result_future.await.map_err(grpc_status_for_twirp_error)?,
))
})
Box::pin(async move { Ok(tonic::Response::new(result_future.await?)) })
}
}

Expand All @@ -411,12 +400,9 @@ impl<
let request = GrpcClientStream { stream: request };
let result_future = (self.callback)(self.service.clone(), request, parts);
Box::pin(async move {
Ok(tonic::Response::new(Box::pin(
result_future
.await
.map_err(grpc_status_for_twirp_error)?
.map(|item| item.map_err(grpc_status_for_twirp_error)),
) as Self::ResponseStream))
Ok(tonic::Response::new(
Box::pin(result_future.await?.map(|item| Ok(item?))) as Self::ResponseStream,
))
})
}
}
Expand Down Expand Up @@ -455,69 +441,14 @@ impl<O> Stream for GrpcClientStream<O> {
.project()
.stream
.poll_next(cx)
.map(|opt| opt.map(|r| r.map_err(twirp_error_for_grpc_status)))
.map(|opt| opt.map(|r| Ok(r?)))
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.stream.size_hint()
}
}

#[cfg(feature = "grpc")]
fn grpc_status_for_twirp_error(error: TwirpError) -> tonic::Status {
tonic::Status::new(
// TODO: extract this into proper `From` impl
match error.code() {
TwirpErrorCode::Canceled => tonic::Code::Cancelled,
TwirpErrorCode::Unknown => tonic::Code::Unknown,
TwirpErrorCode::InvalidArgument => tonic::Code::InvalidArgument,
TwirpErrorCode::Malformed => tonic::Code::InvalidArgument,
TwirpErrorCode::DeadlineExceeded => tonic::Code::DeadlineExceeded,
TwirpErrorCode::NotFound => tonic::Code::NotFound,
TwirpErrorCode::BadRoute => tonic::Code::NotFound,
TwirpErrorCode::AlreadyExists => tonic::Code::AlreadyExists,
TwirpErrorCode::PermissionDenied => tonic::Code::PermissionDenied,
TwirpErrorCode::Unauthenticated => tonic::Code::Unauthenticated,
TwirpErrorCode::ResourceExhausted => tonic::Code::ResourceExhausted,
TwirpErrorCode::FailedPrecondition => tonic::Code::FailedPrecondition,
TwirpErrorCode::Aborted => tonic::Code::Aborted,
TwirpErrorCode::OutOfRange => tonic::Code::OutOfRange,
TwirpErrorCode::Unimplemented => tonic::Code::Unimplemented,
TwirpErrorCode::Internal => tonic::Code::Internal,
TwirpErrorCode::Unavailable => tonic::Code::Unavailable,
TwirpErrorCode::Dataloss => tonic::Code::DataLoss,
},
error.into_message(),
)
}

#[cfg(feature = "grpc")]
fn twirp_error_for_grpc_status(status: tonic::Status) -> TwirpError {
TwirpError::wrap(
match status.code() {
tonic::Code::Cancelled => TwirpErrorCode::Canceled,
tonic::Code::Unknown => TwirpErrorCode::Unknown,
tonic::Code::InvalidArgument => TwirpErrorCode::InvalidArgument,
tonic::Code::DeadlineExceeded => TwirpErrorCode::DeadlineExceeded,
tonic::Code::NotFound => TwirpErrorCode::NotFound,
tonic::Code::AlreadyExists => TwirpErrorCode::AlreadyExists,
tonic::Code::PermissionDenied => TwirpErrorCode::PermissionDenied,
tonic::Code::Unauthenticated => TwirpErrorCode::Unauthenticated,
tonic::Code::ResourceExhausted => TwirpErrorCode::ResourceExhausted,
tonic::Code::FailedPrecondition => TwirpErrorCode::FailedPrecondition,
tonic::Code::Aborted => TwirpErrorCode::Aborted,
tonic::Code::OutOfRange => TwirpErrorCode::OutOfRange,
tonic::Code::Unimplemented => TwirpErrorCode::Unimplemented,
tonic::Code::Internal => TwirpErrorCode::Internal,
tonic::Code::Unavailable => TwirpErrorCode::Unavailable,
tonic::Code::DataLoss => TwirpErrorCode::Dataloss,
tonic::Code::Ok => TwirpErrorCode::Unknown,
},
status.message().to_string(),
status,
)
}

pub async fn twirp_error_from_response(response: impl IntoResponse) -> TwirpError {
let (parts, body) = response.into_response().into_parts();
let body = match body.collect().await {
Expand Down

0 comments on commit c9b9c5c

Please sign in to comment.