scufflecloud_ext/
std_ext.rs

1use std::fmt::Display;
2
3use tonic_types::{ErrorDetails, StatusExt};
4
5pub 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, details: ErrorDetails) -> tonic::Status {
26        // This function is called extremely often in our code base. Since its generic over `D` llvm generates a lot of code.
27        // So if we take most of the function and wrap it in an inner function (with no generics), we significantly reduce the amount
28        // of codegen.
29        fn into_tonic_err_inner(err: String, code: tonic::Code, msg: &str, mut details: ErrorDetails) -> tonic::Status {
30            tracing::error!(err = %err, "{}", msg);
31            details.set_debug_info(vec![], err);
32            tonic::Status::with_error_details(code, msg, details)
33        }
34
35        into_tonic_err_inner(self.to_string(), code, msg, details)
36    }
37}
38
39pub trait ResultExt<T>: Sized {
40    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status>;
41
42    fn into_tonic_internal_err(self, msg: &str) -> Result<T, tonic::Status> {
43        self.into_tonic_err(tonic::Code::Internal, msg, ErrorDetails::new())
44    }
45
46    fn into_tonic_err_with_field_violation(self, field: &str, msg: &str) -> Result<T, tonic::Status> {
47        self.into_tonic_err(
48            tonic::Code::InvalidArgument,
49            format!("{field}: {msg}").as_str(),
50            ErrorDetails::with_bad_request_violation(field, msg),
51        )
52    }
53}
54
55impl<T, E> ResultExt<T> for Result<T, E>
56where
57    E: DisplayExt,
58{
59    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status> {
60        match self {
61            Ok(value) => Ok(value),
62            Err(e) => Err(e.into_tonic_err(code, msg, details)),
63        }
64    }
65}
66
67pub trait OptionExt<T>: Sized {
68    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status>;
69
70    fn into_tonic_not_found(self, msg: &str) -> Result<T, tonic::Status> {
71        self.into_tonic_err(tonic::Code::NotFound, msg, ErrorDetails::new())
72    }
73
74    fn into_tonic_internal_err(self, msg: &str) -> Result<T, tonic::Status> {
75        self.into_tonic_err(tonic::Code::Internal, msg, ErrorDetails::new())
76    }
77
78    fn require(self, field: &str) -> Result<T, tonic::Status> {
79        self.into_tonic_err(
80            tonic::Code::InvalidArgument,
81            format!("missing {field}").as_str(),
82            tonic_types::ErrorDetails::with_bad_request_violation(field, "not set"),
83        )
84    }
85}
86
87impl<T> OptionExt<T> for Option<T> {
88    fn into_tonic_err(self, code: tonic::Code, msg: &str, details: ErrorDetails) -> Result<T, tonic::Status> {
89        self.ok_or_else(|| {
90            tracing::error!("{}", msg);
91            tonic::Status::with_error_details(code, msg, details)
92        })
93    }
94}