tako_rs_core/grpc/
status.rs1use http::HeaderMap;
5use http::StatusCode;
6
7use crate::body::TakoBody;
8use crate::types::Response;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum GrpcStatusCode {
16 Ok = 0,
17 Cancelled = 1,
18 Unknown = 2,
19 InvalidArgument = 3,
20 DeadlineExceeded = 4,
21 NotFound = 5,
22 AlreadyExists = 6,
23 PermissionDenied = 7,
24 ResourceExhausted = 8,
25 FailedPrecondition = 9,
26 Aborted = 10,
27 OutOfRange = 11,
28 Unimplemented = 12,
29 Internal = 13,
30 Unavailable = 14,
31 DataLoss = 15,
32 Unauthenticated = 16,
33}
34
35fn percent_encode_grpc_message(s: &str) -> String {
44 let mut out = String::with_capacity(s.len());
45 for &b in s.as_bytes() {
46 if (0x20..=0x7E).contains(&b) && b != b'%' {
47 out.push(b as char);
48 } else {
49 out.push('%');
50 out.push(hex_upper(b >> 4));
51 out.push(hex_upper(b & 0x0F));
52 }
53 }
54 out
55}
56
57#[inline]
58fn hex_upper(n: u8) -> char {
59 match n {
60 0..=9 => (b'0' + n) as char,
61 10..=15 => (b'A' + n - 10) as char,
62 _ => unreachable!("hex_upper called with value > 15"),
63 }
64}
65
66pub(crate) fn build_grpc_error_response(status: GrpcStatusCode, message: &str) -> Response {
67 let mut resp = Response::new(TakoBody::empty());
68 *resp.status_mut() = StatusCode::OK; resp.headers_mut().insert(
70 http::header::CONTENT_TYPE,
71 http::HeaderValue::from_static("application/grpc"),
72 );
73 if let Ok(val) = http::HeaderValue::from_str(&(status as u8).to_string()) {
74 resp.headers_mut().insert("grpc-status", val);
75 }
76 if !message.is_empty()
77 && let Ok(val) = http::HeaderValue::from_str(&percent_encode_grpc_message(message))
78 {
79 resp.headers_mut().insert("grpc-message", val);
80 }
81 resp
82}
83
84#[derive(Debug, Clone)]
86pub struct GrpcStatus {
87 pub code: GrpcStatusCode,
88 pub message: Option<String>,
89}
90
91impl GrpcStatus {
92 pub fn ok() -> Self {
93 Self {
94 code: GrpcStatusCode::Ok,
95 message: None,
96 }
97 }
98
99 pub fn error(code: GrpcStatusCode, message: impl Into<String>) -> Self {
100 Self {
101 code,
102 message: Some(message.into()),
103 }
104 }
105
106 pub(crate) fn write_trailers(&self) -> HeaderMap {
107 let mut t = HeaderMap::new();
108 if let Ok(v) = http::HeaderValue::from_str(&(self.code as u8).to_string()) {
109 t.insert("grpc-status", v);
110 }
111 if let Some(msg) = self.message.as_deref()
112 && let Ok(v) = http::HeaderValue::from_str(&percent_encode_grpc_message(msg))
113 {
114 t.insert("grpc-message", v);
115 }
116 t
117 }
118}