scufflecloud_core/
std_ext.rs

1use std::fmt::Display;
2
3use tonic_types::{ErrorDetails, StatusExt};
4
5pub(crate) trait DisplayExt: Sized {
6    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> tonic::Status;
7
8    fn into_tonic_internal_err(self, msg: &str) -> tonic::Status {
9        self.into_tonic_err(tonic::Code::Internal, msg, ErrorDetails::new())
10    }
11
12    fn into_tonic_err_with_field_violation(self, field: &str, msg: &str) -> tonic::Status {
13        self.into_tonic_err(
14            tonic::Code::InvalidArgument,
15            format!("{field}: {msg}").as_str(),
16            ErrorDetails::with_bad_request_violation(field, msg),
17        )
18    }
19}
20
21impl<D> DisplayExt for D
22where
23    D: Display,
24{
25    fn into_tonic_err(self, code: tonic::Code, msg: &str, mut details: ErrorDetails) -> tonic::Status {
26        tracing::error!(err = %self, "{}", msg);
27        details.set_debug_info(vec![], self.to_string());
28        tonic::Status::with_error_details(code, msg, details)
29    }
30}
31
32pub(crate) trait ResultExt<T>: Sized {
33    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status>;
34
35    fn into_tonic_internal_err(self, msg: &str) -> Result<T, tonic::Status> {
36        self.into_tonic_err(tonic::Code::Internal, msg, ErrorDetails::new())
37    }
38
39    fn into_tonic_err_with_field_violation(self, field: &str, msg: &str) -> Result<T, tonic::Status> {
40        self.into_tonic_err(
41            tonic::Code::InvalidArgument,
42            format!("{field}: {msg}").as_str(),
43            ErrorDetails::with_bad_request_violation(field, msg),
44        )
45    }
46}
47
48impl<T, E> ResultExt<T> for Result<T, E>
49where
50    E: DisplayExt,
51{
52    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status> {
53        match self {
54            Ok(value) => Ok(value),
55            Err(e) => Err(e.into_tonic_err(code, msg, details)),
56        }
57    }
58}
59
60pub(crate) trait OptionExt<T>: Sized {
61    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status>;
62
63    fn into_tonic_not_found(self, msg: &str) -> Result<T, tonic::Status> {
64        self.into_tonic_err(tonic::Code::NotFound, msg, ErrorDetails::new())
65    }
66
67    fn into_tonic_internal_err(self, msg: &str) -> Result<T, tonic::Status> {
68        self.into_tonic_err(tonic::Code::Internal, msg, ErrorDetails::new())
69    }
70
71    fn require(self, field: &str) -> Result<T, tonic::Status> {
72        self.into_tonic_err(
73            tonic::Code::InvalidArgument,
74            format!("missing {field}").as_str(),
75            tonic_types::ErrorDetails::with_bad_request_violation(field, "not set"),
76        )
77    }
78}
79
80impl<T> OptionExt<T> for Option<T> {
81    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status> {
82        self.ok_or_else(|| {
83            tracing::error!("{}", msg);
84            tonic::Status::with_error_details(code, msg, details)
85        })
86    }
87}