1use crate::rpc_support::{GestaltError, gestalt_error_code, http_status_to_gestalt_code};
4
5pub const RESPONSE_KIND_HEADER: &str = "X-Gestalt-Response-Kind";
7pub const RESPONSE_KIND_OPERATION_RESULT: &str = "operation-result";
9
10pub fn is_operation_result(headers: &http::HeaderMap) -> bool {
12 headers
13 .get(RESPONSE_KIND_HEADER)
14 .and_then(|value| value.to_str().ok())
15 == Some(RESPONSE_KIND_OPERATION_RESULT)
16}
17
18pub fn parse_gateway_error(status: u16, body: &[u8]) -> GestaltError {
20 let parsed = if body.is_empty() {
21 None
22 } else {
23 serde_json::from_slice::<serde_json::Value>(body).ok()
24 };
25 let code = resolve_gateway_error_code(status, parsed.as_ref());
26 let message = parsed
27 .as_ref()
28 .and_then(extract_gateway_error_message)
29 .unwrap_or_else(|| format!("request failed with status {status}"));
30 GestaltError::new(code, message)
31}
32
33fn extract_gateway_error_message(parsed: &serde_json::Value) -> Option<String> {
34 for key in ["message", "error"] {
35 if let Some(text) = parsed.get(key).and_then(|value| value.as_str()) {
36 let text = text.trim();
37 if !text.is_empty() {
38 return Some(text.to_string());
39 }
40 }
41 }
42 None
43}
44
45fn resolve_gateway_error_code(status: u16, parsed: Option<&serde_json::Value>) -> i32 {
46 let status = status as i32;
47 let Some(parsed) = parsed else {
48 return http_status_to_gestalt_code(status);
49 };
50 match parsed.get("code") {
51 Some(serde_json::Value::Number(number)) => number
52 .as_i64()
53 .and_then(|value| i32::try_from(value).ok())
54 .unwrap_or_else(|| http_status_to_gestalt_code(status)),
55 Some(serde_json::Value::String(name)) => grpc_code_name_to_gestalt_code(name)
56 .unwrap_or_else(|| http_status_to_gestalt_code(status)),
57 _ => http_status_to_gestalt_code(status),
58 }
59}
60
61fn grpc_code_name_to_gestalt_code(name: &str) -> Option<i32> {
62 Some(match name {
63 "Canceled" => gestalt_error_code::CANCELLED,
64 "Unknown" => gestalt_error_code::UNKNOWN,
65 "InvalidArgument" => gestalt_error_code::INVALID_ARGUMENT,
66 "DeadlineExceeded" => gestalt_error_code::DEADLINE_EXCEEDED,
67 "NotFound" => gestalt_error_code::NOT_FOUND,
68 "AlreadyExists" => gestalt_error_code::ALREADY_EXISTS,
69 "PermissionDenied" => gestalt_error_code::PERMISSION_DENIED,
70 "ResourceExhausted" => gestalt_error_code::RESOURCE_EXHAUSTED,
71 "FailedPrecondition" => gestalt_error_code::FAILED_PRECONDITION,
72 "Aborted" => gestalt_error_code::ABORTED,
73 "OutOfRange" => gestalt_error_code::OUT_OF_RANGE,
74 "Unimplemented" => gestalt_error_code::UNIMPLEMENTED,
75 "Internal" => gestalt_error_code::INTERNAL,
76 "Unavailable" => gestalt_error_code::UNAVAILABLE,
77 "DataLoss" => gestalt_error_code::DATA_LOSS,
78 "Unauthenticated" => gestalt_error_code::UNAUTHENTICATED,
79 _ => return None,
80 })
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn parses_gateway_error_field_and_string_code() {
89 let body = br#"{"code":"Unauthenticated","error":"Not authenticated"}"#;
90 let err = parse_gateway_error(401, body);
91 assert_eq!(err.code, gestalt_error_code::UNAUTHENTICATED);
92 assert_eq!(err.message, "Not authenticated");
93 }
94}