rama_http/layer/classify/
grpc_errors_as_failures.rs1use super::{ClassifiedResponse, ClassifyEos, ClassifyResponse, SharedClassifier};
2use crate::{HeaderMap, Response};
3use bitflags::bitflags;
4use std::{fmt, num::NonZeroI32};
5
6#[derive(Clone, Copy, Debug)]
12pub enum GrpcCode {
13 Ok,
15 Cancelled,
17 Unknown,
19 InvalidArgument,
21 DeadlineExceeded,
23 NotFound,
25 AlreadyExists,
27 PermissionDenied,
29 ResourceExhausted,
31 FailedPrecondition,
33 Aborted,
35 OutOfRange,
37 Unimplemented,
39 Internal,
41 Unavailable,
43 DataLoss,
45 Unauthenticated,
47}
48
49impl GrpcCode {
50 pub(crate) const fn into_bitmask(self) -> GrpcCodeBitmask {
51 match self {
52 Self::Ok => GrpcCodeBitmask::OK,
53 Self::Cancelled => GrpcCodeBitmask::CANCELLED,
54 Self::Unknown => GrpcCodeBitmask::UNKNOWN,
55 Self::InvalidArgument => GrpcCodeBitmask::INVALID_ARGUMENT,
56 Self::DeadlineExceeded => GrpcCodeBitmask::DEADLINE_EXCEEDED,
57 Self::NotFound => GrpcCodeBitmask::NOT_FOUND,
58 Self::AlreadyExists => GrpcCodeBitmask::ALREADY_EXISTS,
59 Self::PermissionDenied => GrpcCodeBitmask::PERMISSION_DENIED,
60 Self::ResourceExhausted => GrpcCodeBitmask::RESOURCE_EXHAUSTED,
61 Self::FailedPrecondition => GrpcCodeBitmask::FAILED_PRECONDITION,
62 Self::Aborted => GrpcCodeBitmask::ABORTED,
63 Self::OutOfRange => GrpcCodeBitmask::OUT_OF_RANGE,
64 Self::Unimplemented => GrpcCodeBitmask::UNIMPLEMENTED,
65 Self::Internal => GrpcCodeBitmask::INTERNAL,
66 Self::Unavailable => GrpcCodeBitmask::UNAVAILABLE,
67 Self::DataLoss => GrpcCodeBitmask::DATA_LOSS,
68 Self::Unauthenticated => GrpcCodeBitmask::UNAUTHENTICATED,
69 }
70 }
71}
72
73bitflags! {
74 #[derive(Debug, Clone, Copy)]
75 pub(crate) struct GrpcCodeBitmask: u32 {
76 const OK = 0b00000000000000001;
77 const CANCELLED = 0b00000000000000010;
78 const UNKNOWN = 0b00000000000000100;
79 const INVALID_ARGUMENT = 0b00000000000001000;
80 const DEADLINE_EXCEEDED = 0b00000000000010000;
81 const NOT_FOUND = 0b00000000000100000;
82 const ALREADY_EXISTS = 0b00000000001000000;
83 const PERMISSION_DENIED = 0b00000000010000000;
84 const RESOURCE_EXHAUSTED = 0b00000000100000000;
85 const FAILED_PRECONDITION = 0b00000001000000000;
86 const ABORTED = 0b00000010000000000;
87 const OUT_OF_RANGE = 0b00000100000000000;
88 const UNIMPLEMENTED = 0b00001000000000000;
89 const INTERNAL = 0b00010000000000000;
90 const UNAVAILABLE = 0b00100000000000000;
91 const DATA_LOSS = 0b01000000000000000;
92 const UNAUTHENTICATED = 0b10000000000000000;
93 }
94}
95
96impl GrpcCodeBitmask {
97 fn try_from_u32(code: u32) -> Option<Self> {
98 match code {
99 0 => Some(Self::OK),
100 1 => Some(Self::CANCELLED),
101 2 => Some(Self::UNKNOWN),
102 3 => Some(Self::INVALID_ARGUMENT),
103 4 => Some(Self::DEADLINE_EXCEEDED),
104 5 => Some(Self::NOT_FOUND),
105 6 => Some(Self::ALREADY_EXISTS),
106 7 => Some(Self::PERMISSION_DENIED),
107 8 => Some(Self::RESOURCE_EXHAUSTED),
108 9 => Some(Self::FAILED_PRECONDITION),
109 10 => Some(Self::ABORTED),
110 11 => Some(Self::OUT_OF_RANGE),
111 12 => Some(Self::UNIMPLEMENTED),
112 13 => Some(Self::INTERNAL),
113 14 => Some(Self::UNAVAILABLE),
114 15 => Some(Self::DATA_LOSS),
115 16 => Some(Self::UNAUTHENTICATED),
116 _ => None,
117 }
118 }
119}
120
121#[derive(Debug, Clone)]
135pub struct GrpcErrorsAsFailures {
136 success_codes: GrpcCodeBitmask,
137}
138
139impl Default for GrpcErrorsAsFailures {
140 fn default() -> Self {
141 Self::new()
142 }
143}
144
145impl GrpcErrorsAsFailures {
146 pub const fn new() -> Self {
148 Self {
149 success_codes: GrpcCodeBitmask::OK,
150 }
151 }
152
153 pub fn with_success(mut self, code: GrpcCode) -> Self {
172 self.success_codes |= code.into_bitmask();
173 self
174 }
175
176 pub fn set_success(&mut self, code: GrpcCode) -> &mut Self {
180 self.success_codes |= code.into_bitmask();
181 self
182 }
183
184 pub fn make_classifier() -> SharedClassifier<Self> {
188 SharedClassifier::new(Self::new())
189 }
190}
191
192impl ClassifyResponse for GrpcErrorsAsFailures {
193 type FailureClass = GrpcFailureClass;
194 type ClassifyEos = GrpcEosErrorsAsFailures;
195
196 fn classify_response<B>(
197 self,
198 res: &Response<B>,
199 ) -> ClassifiedResponse<Self::FailureClass, Self::ClassifyEos> {
200 match classify_grpc_metadata(res.headers(), self.success_codes) {
201 ParsedGrpcStatus::Success
202 | ParsedGrpcStatus::HeaderNotString
203 | ParsedGrpcStatus::HeaderNotInt => ClassifiedResponse::Ready(Ok(())),
204 ParsedGrpcStatus::NonSuccess(status) => {
205 ClassifiedResponse::Ready(Err(GrpcFailureClass::Code(status)))
206 }
207 ParsedGrpcStatus::GrpcStatusHeaderMissing => {
208 ClassifiedResponse::RequiresEos(GrpcEosErrorsAsFailures {
209 success_codes: self.success_codes,
210 })
211 }
212 }
213 }
214
215 fn classify_error<E>(self, error: &E) -> Self::FailureClass
216 where
217 E: fmt::Display,
218 {
219 GrpcFailureClass::Error(error.to_string())
220 }
221}
222
223#[derive(Debug, Clone)]
225pub struct GrpcEosErrorsAsFailures {
226 success_codes: GrpcCodeBitmask,
227}
228
229impl ClassifyEos for GrpcEosErrorsAsFailures {
230 type FailureClass = GrpcFailureClass;
231
232 fn classify_eos(self, trailers: Option<&HeaderMap>) -> Result<(), Self::FailureClass> {
233 if let Some(trailers) = trailers {
234 match classify_grpc_metadata(trailers, self.success_codes) {
235 ParsedGrpcStatus::Success
236 | ParsedGrpcStatus::GrpcStatusHeaderMissing
237 | ParsedGrpcStatus::HeaderNotString
238 | ParsedGrpcStatus::HeaderNotInt => Ok(()),
239 ParsedGrpcStatus::NonSuccess(status) => Err(GrpcFailureClass::Code(status)),
240 }
241 } else {
242 Ok(())
243 }
244 }
245
246 fn classify_error<E>(self, error: &E) -> Self::FailureClass
247 where
248 E: fmt::Display,
249 {
250 GrpcFailureClass::Error(error.to_string())
251 }
252}
253
254impl Default for GrpcEosErrorsAsFailures {
255 fn default() -> Self {
256 Self {
257 success_codes: GrpcCodeBitmask::OK,
258 }
259 }
260}
261
262#[derive(Debug)]
264pub enum GrpcFailureClass {
265 Code(std::num::NonZeroI32),
267 Error(String),
269}
270
271impl fmt::Display for GrpcFailureClass {
272 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
273 match self {
274 Self::Code(code) => write!(f, "Code: {}", code),
275 Self::Error(error) => write!(f, "Error: {}", error),
276 }
277 }
278}
279
280#[allow(clippy::match_result_ok)]
281pub(crate) fn classify_grpc_metadata(
282 headers: &HeaderMap,
283 success_codes: GrpcCodeBitmask,
284) -> ParsedGrpcStatus {
285 macro_rules! or_else {
286 ($expr:expr, $other:ident) => {
287 if let Some(value) = $expr {
288 value
289 } else {
290 return ParsedGrpcStatus::$other;
291 }
292 };
293 }
294
295 let status = or_else!(headers.get("grpc-status"), GrpcStatusHeaderMissing);
296 let status = or_else!(status.to_str().ok(), HeaderNotString);
297 let status = or_else!(status.parse::<i32>().ok(), HeaderNotInt);
298
299 if GrpcCodeBitmask::try_from_u32(status as _)
300 .filter(|code| success_codes.contains(*code))
301 .is_some()
302 {
303 ParsedGrpcStatus::Success
304 } else {
305 ParsedGrpcStatus::NonSuccess(NonZeroI32::new(status).unwrap())
306 }
307}
308
309#[derive(Debug, PartialEq, Eq, Hash)]
310pub(crate) enum ParsedGrpcStatus {
311 Success,
312 NonSuccess(NonZeroI32),
313 GrpcStatusHeaderMissing,
314 HeaderNotString,
316 HeaderNotInt,
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 macro_rules! classify_grpc_metadata_test {
324 (
325 name: $name:ident,
326 status: $status:expr,
327 success_flags: $success_flags:expr,
328 expected: $expected:expr,
329 ) => {
330 #[test]
331 fn $name() {
332 let mut headers = HeaderMap::new();
333 headers.insert("grpc-status", $status.parse().unwrap());
334 let status = classify_grpc_metadata(&headers, $success_flags);
335 assert_eq!(status, $expected);
336 }
337 };
338 }
339
340 classify_grpc_metadata_test! {
341 name: basic_ok,
342 status: "0",
343 success_flags: GrpcCodeBitmask::OK,
344 expected: ParsedGrpcStatus::Success,
345 }
346
347 classify_grpc_metadata_test! {
348 name: basic_error,
349 status: "1",
350 success_flags: GrpcCodeBitmask::OK,
351 expected: ParsedGrpcStatus::NonSuccess(NonZeroI32::new(1).unwrap()),
352 }
353
354 classify_grpc_metadata_test! {
355 name: two_success_codes_first_matches,
356 status: "0",
357 success_flags: GrpcCodeBitmask::OK | GrpcCodeBitmask::INVALID_ARGUMENT,
358 expected: ParsedGrpcStatus::Success,
359 }
360
361 classify_grpc_metadata_test! {
362 name: two_success_codes_second_matches,
363 status: "3",
364 success_flags: GrpcCodeBitmask::OK | GrpcCodeBitmask::INVALID_ARGUMENT,
365 expected: ParsedGrpcStatus::Success,
366 }
367
368 classify_grpc_metadata_test! {
369 name: two_success_codes_none_matches,
370 status: "16",
371 success_flags: GrpcCodeBitmask::OK | GrpcCodeBitmask::INVALID_ARGUMENT,
372 expected: ParsedGrpcStatus::NonSuccess(NonZeroI32::new(16).unwrap()),
373 }
374}