subliminal_protos_rust/errors.rs
1use std::{error::Error, fmt::Display};
2
3use axum::{
4 http::StatusCode,
5 response::{IntoResponse, Response},
6 Json,
7};
8use serde_json::{json, Value};
9use tonic::Status;
10
11/// Error types related to a particular service (not the API itself).
12#[derive(Debug)]
13pub enum ServiceErrors {
14 // When a service indicates a resource is not found.
15 NotFound(Status),
16
17 // When a service is unavailable.
18 ServiceUnavailable(Status),
19
20 // When a service returns an internal error.
21 ServiceInternal(Status),
22}
23
24// Implement From<tonic::Status> so that we can convert a tonic::Status into a ServiceErrors variant.
25// This is useful for translating service errors into eventual HTTP responses.
26// We need a separate error enum due to the orphan rule.
27impl From<tonic::Status> for ServiceErrors {
28 fn from(status: tonic::Status) -> Self {
29 match status.code() {
30 tonic::Code::NotFound => ServiceErrors::NotFound(status),
31 tonic::Code::Unavailable => ServiceErrors::ServiceUnavailable(status),
32 _ => ServiceErrors::ServiceInternal(status),
33 }
34 }
35}
36
37/// This happens when a client is unable to connect to a service.
38impl From<tonic::transport::Error> for ServiceErrors {
39 fn from(error: tonic::transport::Error) -> Self {
40 ServiceErrors::ServiceUnavailable(Status::new(tonic::Code::Unavailable, error.to_string()))
41 }
42}
43
44impl Error for ServiceErrors {}
45impl Display for ServiceErrors {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 ServiceErrors::NotFound(e) => {
49 write!(f, "Service indicated resource was not found: {}", e)
50 }
51 ServiceErrors::ServiceUnavailable(e) => {
52 write!(f, "Service is unavailable: {}", e)
53 }
54 ServiceErrors::ServiceInternal(e) => {
55 write!(f, "Service encountered an internal error: {}", e)
56 }
57 }
58 }
59}
60
61/// Implement IntoResponse for HttpResponse so that we can return it from an axum handler.
62/// This is primarily used for outside services that the API proxies requests to, whereas the API itself
63/// returns HttpResponses directly.
64impl IntoResponse for ServiceErrors {
65 fn into_response(self) -> Response {
66 match self {
67 // When this error is encountered, it means a service was unable to find a resource.
68 // There are cases where we want to override this behavior, such as when we want to return
69 // a 503 status code instead of a 404 when trying to get a service location from the registry.
70 ServiceErrors::NotFound(e) => {
71 (StatusCode::NOT_FOUND, error_json(e.message().to_string()))
72 }
73 // When this error is encountered, it means we were unable to connect to a particular service.
74 // Instead of returning a 503 (indicating issues with the API itself), we return a 504 to indicate
75 // there is an issue with proxied services.
76 ServiceErrors::ServiceUnavailable(e) => (
77 StatusCode::GATEWAY_TIMEOUT,
78 error_json(e.message().to_string()),
79 ),
80 // When this error is encountered, it means a service (not the API) encountered an internal error.
81 ServiceErrors::ServiceInternal(e) => {
82 (StatusCode::BAD_GATEWAY, error_json(e.message().to_string()))
83 }
84 }
85 .into_response()
86 }
87}
88
89/// Quick helper to convert an error string into a JSON object.
90fn error_json(error: String) -> Json<Value> {
91 Json(json!({ "error": error }))
92}
93