firebase_rs_sdk/functions/
error.rs1use std::fmt::{Display, Formatter};
2
3use serde_json::Value as JsonValue;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum FunctionsErrorCode {
7 Ok,
8 Cancelled,
9 Unknown,
10 Internal,
11 InvalidArgument,
12 DeadlineExceeded,
13 NotFound,
14 AlreadyExists,
15 PermissionDenied,
16 Unauthenticated,
17 ResourceExhausted,
18 FailedPrecondition,
19 Aborted,
20 OutOfRange,
21 Unimplemented,
22 Unavailable,
23 DataLoss,
24}
25
26impl FunctionsErrorCode {
27 pub fn as_str(&self) -> &'static str {
28 match self {
29 FunctionsErrorCode::Ok => "functions/ok",
30 FunctionsErrorCode::Cancelled => "functions/cancelled",
31 FunctionsErrorCode::Unknown => "functions/unknown",
32 FunctionsErrorCode::Internal => "functions/internal",
33 FunctionsErrorCode::InvalidArgument => "functions/invalid-argument",
34 FunctionsErrorCode::DeadlineExceeded => "functions/deadline-exceeded",
35 FunctionsErrorCode::NotFound => "functions/not-found",
36 FunctionsErrorCode::AlreadyExists => "functions/already-exists",
37 FunctionsErrorCode::PermissionDenied => "functions/permission-denied",
38 FunctionsErrorCode::Unauthenticated => "functions/unauthenticated",
39 FunctionsErrorCode::ResourceExhausted => "functions/resource-exhausted",
40 FunctionsErrorCode::FailedPrecondition => "functions/failed-precondition",
41 FunctionsErrorCode::Aborted => "functions/aborted",
42 FunctionsErrorCode::OutOfRange => "functions/out-of-range",
43 FunctionsErrorCode::Unimplemented => "functions/unimplemented",
44 FunctionsErrorCode::Unavailable => "functions/unavailable",
45 FunctionsErrorCode::DataLoss => "functions/data-loss",
46 }
47 }
48
49 pub fn label(&self) -> &'static str {
50 match self {
51 FunctionsErrorCode::Ok => "ok",
52 FunctionsErrorCode::Cancelled => "cancelled",
53 FunctionsErrorCode::Unknown => "unknown",
54 FunctionsErrorCode::Internal => "internal",
55 FunctionsErrorCode::InvalidArgument => "invalid-argument",
56 FunctionsErrorCode::DeadlineExceeded => "deadline-exceeded",
57 FunctionsErrorCode::NotFound => "not-found",
58 FunctionsErrorCode::AlreadyExists => "already-exists",
59 FunctionsErrorCode::PermissionDenied => "permission-denied",
60 FunctionsErrorCode::Unauthenticated => "unauthenticated",
61 FunctionsErrorCode::ResourceExhausted => "resource-exhausted",
62 FunctionsErrorCode::FailedPrecondition => "failed-precondition",
63 FunctionsErrorCode::Aborted => "aborted",
64 FunctionsErrorCode::OutOfRange => "out-of-range",
65 FunctionsErrorCode::Unimplemented => "unimplemented",
66 FunctionsErrorCode::Unavailable => "unavailable",
67 FunctionsErrorCode::DataLoss => "data-loss",
68 }
69 }
70}
71
72#[derive(Clone, Debug)]
73pub struct FunctionsError {
74 pub code: FunctionsErrorCode,
75 message: String,
76 details: Option<JsonValue>,
77}
78
79impl FunctionsError {
80 pub fn new(code: FunctionsErrorCode, message: impl Into<String>) -> Self {
81 Self {
82 code,
83 message: message.into(),
84 details: None,
85 }
86 }
87
88 pub fn code_str(&self) -> &'static str {
89 self.code.as_str()
90 }
91
92 pub fn message(&self) -> &str {
93 &self.message
94 }
95
96 pub fn details(&self) -> Option<&JsonValue> {
97 self.details.as_ref()
98 }
99
100 pub fn with_details(
101 code: FunctionsErrorCode,
102 message: impl Into<String>,
103 details: Option<JsonValue>,
104 ) -> Self {
105 Self {
106 code,
107 message: message.into(),
108 details,
109 }
110 }
111}
112
113impl Display for FunctionsError {
114 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115 write!(f, "{} ({})", self.message, self.code_str())
116 }
117}
118
119impl std::error::Error for FunctionsError {}
120
121pub type FunctionsResult<T> = Result<T, FunctionsError>;
122
123pub fn invalid_argument(message: impl Into<String>) -> FunctionsError {
124 FunctionsError::new(FunctionsErrorCode::InvalidArgument, message)
125}
126
127pub fn internal_error(message: impl Into<String>) -> FunctionsError {
128 FunctionsError::new(FunctionsErrorCode::Internal, message)
129}
130
131pub(crate) fn error_for_http_response(
132 status: u16,
133 body: Option<&JsonValue>,
134) -> Option<FunctionsError> {
135 use FunctionsErrorCode as Code;
136
137 let mut code = code_for_http_status(status);
138 let mut message: Option<String> = code_message_default(code);
139 let mut details: Option<JsonValue> = None;
140
141 if let Some(JsonValue::Object(map)) = body {
142 if let Some(error_value) = map.get("error") {
143 if let JsonValue::Object(error_obj) = error_value {
144 if let Some(JsonValue::String(status_label)) = error_obj.get("status") {
145 match code_for_backend_status(status_label) {
146 Some(mapped) => {
147 code = mapped;
148 message = Some(status_label.clone());
149 }
150 None => {
151 return Some(FunctionsError::new(
152 Code::Internal,
153 "Received unknown error status from Functions backend",
154 ));
155 }
156 }
157 }
158
159 if let Some(JsonValue::String(msg)) = error_obj.get("message") {
160 message = Some(msg.clone());
161 }
162
163 if let Some(value) = error_obj.get("details") {
164 details = Some(value.clone());
165 }
166 }
167 }
168 }
169
170 if matches!(code, Code::Ok) {
171 return None;
172 }
173
174 Some(FunctionsError::with_details(
175 code,
176 message.unwrap_or_else(|| code.label().to_string()),
177 details,
178 ))
179}
180
181fn code_message_default(code: FunctionsErrorCode) -> Option<String> {
182 match code {
183 FunctionsErrorCode::Ok => Some("ok".to_string()),
184 FunctionsErrorCode::Internal => Some("internal".to_string()),
185 FunctionsErrorCode::InvalidArgument => Some("invalid-argument".to_string()),
186 FunctionsErrorCode::DeadlineExceeded => Some("deadline-exceeded".to_string()),
187 FunctionsErrorCode::NotFound => Some("not-found".to_string()),
188 FunctionsErrorCode::AlreadyExists => Some("already-exists".to_string()),
189 FunctionsErrorCode::PermissionDenied => Some("permission-denied".to_string()),
190 FunctionsErrorCode::Unauthenticated => Some("unauthenticated".to_string()),
191 FunctionsErrorCode::ResourceExhausted => Some("resource-exhausted".to_string()),
192 FunctionsErrorCode::FailedPrecondition => Some("failed-precondition".to_string()),
193 FunctionsErrorCode::Aborted => Some("aborted".to_string()),
194 FunctionsErrorCode::OutOfRange => Some("out-of-range".to_string()),
195 FunctionsErrorCode::Unimplemented => Some("unimplemented".to_string()),
196 FunctionsErrorCode::Unavailable => Some("unavailable".to_string()),
197 FunctionsErrorCode::DataLoss => Some("data-loss".to_string()),
198 FunctionsErrorCode::Cancelled => Some("cancelled".to_string()),
199 FunctionsErrorCode::Unknown => Some("unknown".to_string()),
200 }
201}
202
203fn code_for_http_status(status: u16) -> FunctionsErrorCode {
204 use FunctionsErrorCode as Code;
205
206 if (200..300).contains(&status) {
207 return Code::Ok;
208 }
209
210 match status {
211 0 => Code::Internal,
212 400 => Code::InvalidArgument,
213 401 => Code::Unauthenticated,
214 403 => Code::PermissionDenied,
215 404 => Code::NotFound,
216 409 => Code::Aborted,
217 429 => Code::ResourceExhausted,
218 499 => Code::Cancelled,
219 500 => Code::Internal,
220 501 => Code::Unimplemented,
221 503 => Code::Unavailable,
222 504 => Code::DeadlineExceeded,
223 _ => Code::Unknown,
224 }
225}
226
227fn code_for_backend_status(status: &str) -> Option<FunctionsErrorCode> {
228 use FunctionsErrorCode as Code;
229
230 match status {
231 "OK" => Some(Code::Ok),
232 "CANCELLED" => Some(Code::Cancelled),
233 "UNKNOWN" => Some(Code::Unknown),
234 "INVALID_ARGUMENT" => Some(Code::InvalidArgument),
235 "DEADLINE_EXCEEDED" => Some(Code::DeadlineExceeded),
236 "NOT_FOUND" => Some(Code::NotFound),
237 "ALREADY_EXISTS" => Some(Code::AlreadyExists),
238 "PERMISSION_DENIED" => Some(Code::PermissionDenied),
239 "UNAUTHENTICATED" => Some(Code::Unauthenticated),
240 "RESOURCE_EXHAUSTED" => Some(Code::ResourceExhausted),
241 "FAILED_PRECONDITION" => Some(Code::FailedPrecondition),
242 "ABORTED" => Some(Code::Aborted),
243 "OUT_OF_RANGE" => Some(Code::OutOfRange),
244 "UNIMPLEMENTED" => Some(Code::Unimplemented),
245 "INTERNAL" => Some(Code::Internal),
246 "UNAVAILABLE" => Some(Code::Unavailable),
247 "DATA_LOSS" => Some(Code::DataLoss),
248 _ => None,
249 }
250}