firebase_rs_sdk/functions/
error.rs

1use 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}