google_cloud_gax/error/
rpc.rs

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::error::Error;
16use serde::{Deserialize, Serialize};
17
18/// The [Status] type defines a logical error model that is suitable for
19/// different programming environments, including REST APIs and RPC APIs. Each
20/// [Status] message contains three pieces of data: error code, error message,
21/// and error details.
22///
23/// You can find out more about this error model and how to work with it in the
24/// [API Design Guide](https://cloud.google.com/apis/design/errors).
25#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
26#[serde(default, rename_all = "camelCase")]
27#[non_exhaustive]
28pub struct Status {
29    /// The status code.
30    pub code: Code,
31
32    /// A developer-facing error message, which should be in English. Any
33    /// user-facing error message should be localized and sent in the
34    /// [Status] `details` field.
35    pub message: String,
36
37    /// A list of messages that carry the error details. There is a common set
38    /// of message types for APIs to use.
39    pub details: Vec<StatusDetails>,
40}
41
42impl Status {
43    /// Sets the value for [code][Status::code].
44    pub fn set_code<T: Into<Code>>(mut self, v: T) -> Self {
45        self.code = v.into();
46        self
47    }
48
49    /// Sets the value for [message][Status::message].
50    pub fn set_message<T: Into<String>>(mut self, v: T) -> Self {
51        self.message = v.into();
52        self
53    }
54
55    /// Sets the value for [details][Status::details].
56    pub fn set_details<T, I>(mut self, v: T) -> Self
57    where
58        T: IntoIterator<Item = I>,
59        I: Into<StatusDetails>,
60    {
61        self.details = v.into_iter().map(|v| v.into()).collect();
62        self
63    }
64}
65
66/// The canonical error codes for APIs.
67//
68/// Sometimes multiple error codes may apply.  Services should return
69/// the most specific error code that applies.  For example, prefer
70/// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply.
71/// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`.
72#[derive(Clone, Copy, Debug, PartialEq)]
73#[non_exhaustive]
74pub enum Code {
75    /// Not an error; returned on success.
76    ///
77    /// HTTP Mapping: 200 OK
78    Ok = 0,
79
80    /// The operation was cancelled, typically by the caller.
81    ///
82    /// HTTP Mapping: 499 Client Closed Request
83    Cancelled = 1,
84
85    /// Unknown error.  For example, this error may be returned when
86    /// a `Status` value received from another address space belongs to
87    /// an error space that is not known in this address space.  Also
88    /// errors raised by APIs that do not return enough error information
89    /// may be converted to this error.
90    ///
91    /// HTTP Mapping: 500 Internal Server Error
92    Unknown = 2,
93
94    /// The client specified an invalid argument.  Note that this differs
95    /// from `FAILED_PRECONDITION`.  `INVALID_ARGUMENT` indicates arguments
96    /// that are problematic regardless of the state of the system
97    /// (e.g., a malformed file name).
98    ///
99    /// HTTP Mapping: 400 Bad Request
100    InvalidArgument = 3,
101
102    /// The deadline expired before the operation could complete. For operations
103    /// that change the state of the system, this error may be returned
104    /// even if the operation has completed successfully.  For example, a
105    /// successful response from a server could have been delayed long
106    /// enough for the deadline to expire.
107    ///
108    /// HTTP Mapping: 504 Gateway Timeout
109    DeadlineExceeded = 4,
110
111    /// Some requested entity (e.g., file or directory) was not found.
112    ///
113    /// Note to server developers: if a request is denied for an entire class
114    /// of users, such as gradual feature rollout or undocumented allowlist,
115    /// `NOT_FOUND` may be used. If a request is denied for some users within
116    /// a class of users, such as user-based access control, `PERMISSION_DENIED`
117    /// must be used.
118    ///
119    /// HTTP Mapping: 404 Not Found
120    NotFound = 5,
121
122    /// The entity that a client attempted to create (e.g., file or directory)
123    /// already exists.
124    ///
125    /// HTTP Mapping: 409 Conflict
126    AlreadyExists = 6,
127
128    /// The caller does not have permission to execute the specified
129    /// operation. `PERMISSION_DENIED` must not be used for rejections
130    /// caused by exhausting some resource (use `RESOURCE_EXHAUSTED`
131    /// instead for those errors). `PERMISSION_DENIED` must not be
132    /// used if the caller can not be identified (use `UNAUTHENTICATED`
133    /// instead for those errors). This error code does not imply the
134    /// request is valid or the requested entity exists or satisfies
135    /// other pre-conditions.
136    ///
137    /// HTTP Mapping: 403 Forbidden
138    PermissionDenied = 7,
139
140    /// Some resource has been exhausted, perhaps a per-user quota, or
141    /// perhaps the entire file system is out of space.
142    ///
143    /// HTTP Mapping: 429 Too Many Requests
144    ResourceExhausted = 8,
145
146    /// The operation was rejected because the system is not in a state
147    /// required for the operation's execution.  For example, the directory
148    /// to be deleted is non-empty, an rmdir operation is applied to
149    /// a non-directory, etc.
150    ///
151    /// Service implementors can use the following guidelines to decide
152    /// between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:
153    /// 1. Use `UNAVAILABLE` if the client can retry just the failing call.
154    /// 1. Use `ABORTED` if the client should retry at a higher level. For
155    ///    example, when a client-specified test-and-set fails, indicating the
156    ///    client should restart a read-modify-write sequence.
157    /// 1. Use `FAILED_PRECONDITION` if the client should not retry until
158    ///    the system state has been explicitly fixed. For example, if an "rmdir"
159    ///    fails because the directory is non-empty, `FAILED_PRECONDITION`
160    ///    should be returned since the client should not retry unless
161    ///    the files are deleted from the directory.
162    ///
163    /// HTTP Mapping: 400 Bad Request
164    FailedPrecondition = 9,
165
166    /// The operation was aborted, typically due to a concurrency issue such as
167    /// a sequencer check failure or transaction abort.
168    ///
169    /// See the guidelines above for deciding between `FAILED_PRECONDITION`,
170    /// `ABORTED`, and `UNAVAILABLE`.
171    ///
172    /// HTTP Mapping: 409 Conflict
173    ///
174    /// HTTP Mapping: 400 Bad Request
175    Aborted = 10,
176
177    /// The operation was attempted past the valid range.  E.g., seeking or
178    /// reading past end-of-file.
179    ///
180    /// Unlike `INVALID_ARGUMENT`, this error indicates a problem that may
181    /// be fixed if the system state changes. For example, a 32-bit file
182    /// system will generate `INVALID_ARGUMENT` if asked to read at an
183    /// offset that is not in the range [0,2^32-1], but it will generate
184    /// `OUT_OF_RANGE` if asked to read from an offset past the current
185    /// file size.
186    ///
187    /// There is a fair bit of overlap between `FAILED_PRECONDITION` and
188    /// `OUT_OF_RANGE`.  We recommend using `OUT_OF_RANGE` (the more specific
189    /// error) when it applies so that callers who are iterating through
190    /// a space can easily look for an `OUT_OF_RANGE` error to detect when
191    /// they are done.
192    ///
193    /// HTTP Mapping: 400 Bad Request
194    OutOfRange = 11,
195
196    /// The operation is not implemented or is not supported/enabled in this
197    /// service.
198    ///
199    /// HTTP Mapping: 501 Not Implemented
200    Unimplemented = 12,
201
202    /// Internal errors.  This means that some invariants expected by the
203    /// underlying system have been broken.  This error code is reserved
204    /// for serious errors.
205    ///
206    /// HTTP Mapping: 500 Internal Server Error
207    Internal = 13,
208
209    /// The service is currently unavailable.  This is most likely a
210    /// transient condition, which can be corrected by retrying with
211    /// a backoff. Note that it is not always safe to retry
212    /// non-idempotent operations.
213    ///
214    /// See the guidelines above for deciding between `FAILED_PRECONDITION`,
215    /// `ABORTED`, and `UNAVAILABLE`.
216    ///
217    /// HTTP Mapping: 503 Service Unavailable
218    Unavailable = 14,
219
220    /// Unrecoverable data loss or corruption.
221    ///
222    /// HTTP Mapping: 500 Internal Server Error
223    DataLoss = 15,
224
225    /// The request does not have valid authentication credentials for the
226    /// operation.
227    ///
228    /// HTTP Mapping: 401 Unauthorized
229    Unauthenticated = 16,
230}
231
232impl Code {
233    pub fn name(&self) -> &str {
234        match self {
235            Code::Ok => "OK",
236            Code::Cancelled => "CANCELLED",
237            Code::Unknown => "UNKNOWN",
238            Code::InvalidArgument => "INVALID_ARGUMENT",
239            Code::DeadlineExceeded => "DEADLINE_EXCEEDED",
240            Code::NotFound => "NOT_FOUND",
241            Code::AlreadyExists => "ALREADY_EXISTS",
242            Code::PermissionDenied => "PERMISSION_DENIED",
243            Code::ResourceExhausted => "RESOURCE_EXHAUSTED",
244            Code::FailedPrecondition => "FAILED_PRECONDITION",
245            Code::Aborted => "ABORTED",
246            Code::OutOfRange => "OUT_OF_RANGE",
247            Code::Unimplemented => "UNIMPLEMENTED",
248            Code::Internal => "INTERNAL",
249            Code::Unavailable => "UNAVAILABLE",
250            Code::DataLoss => "DATA_LOSS",
251            Code::Unauthenticated => "UNAUTHENTICATED",
252        }
253    }
254}
255
256impl Default for Code {
257    fn default() -> Self {
258        Self::Unknown
259    }
260}
261
262impl std::convert::From<i32> for Code {
263    fn from(value: i32) -> Self {
264        match value {
265            0 => Code::Ok,
266            1 => Code::Cancelled,
267            2 => Code::Unknown,
268            3 => Code::InvalidArgument,
269            4 => Code::DeadlineExceeded,
270            5 => Code::NotFound,
271            6 => Code::AlreadyExists,
272            7 => Code::PermissionDenied,
273            8 => Code::ResourceExhausted,
274            9 => Code::FailedPrecondition,
275            10 => Code::Aborted,
276            11 => Code::OutOfRange,
277            12 => Code::Unimplemented,
278            13 => Code::Internal,
279            14 => Code::Unavailable,
280            15 => Code::DataLoss,
281            16 => Code::Unauthenticated,
282            _ => Code::default(),
283        }
284    }
285}
286
287impl std::convert::From<Code> for String {
288    fn from(value: Code) -> String {
289        value.name().to_string()
290    }
291}
292
293impl std::fmt::Display for Code {
294    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295        f.write_str(self.name())
296    }
297}
298
299impl std::convert::TryFrom<&str> for Code {
300    type Error = String;
301    fn try_from(value: &str) -> std::result::Result<Code, Self::Error> {
302        match value {
303            "OK" => Ok(Code::Ok),
304            "CANCELLED" => Ok(Code::Cancelled),
305            "UNKNOWN" => Ok(Code::Unknown),
306            "INVALID_ARGUMENT" => Ok(Code::InvalidArgument),
307            "DEADLINE_EXCEEDED" => Ok(Code::DeadlineExceeded),
308            "NOT_FOUND" => Ok(Code::NotFound),
309            "ALREADY_EXISTS" => Ok(Code::AlreadyExists),
310            "PERMISSION_DENIED" => Ok(Code::PermissionDenied),
311            "RESOURCE_EXHAUSTED" => Ok(Code::ResourceExhausted),
312            "FAILED_PRECONDITION" => Ok(Code::FailedPrecondition),
313            "ABORTED" => Ok(Code::Aborted),
314            "OUT_OF_RANGE" => Ok(Code::OutOfRange),
315            "UNIMPLEMENTED" => Ok(Code::Unimplemented),
316            "INTERNAL" => Ok(Code::Internal),
317            "UNAVAILABLE" => Ok(Code::Unavailable),
318            "DATA_LOSS" => Ok(Code::DataLoss),
319            "UNAUTHENTICATED" => Ok(Code::Unauthenticated),
320            _ => Err(format!("unknown status code value {value}")),
321        }
322    }
323}
324
325impl Serialize for Code {
326    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
327    where
328        S: serde::Serializer,
329    {
330        serializer.serialize_i32(*self as i32)
331    }
332}
333
334impl<'de> Deserialize<'de> for Code {
335    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
336    where
337        D: serde::Deserializer<'de>,
338    {
339        i32::deserialize(deserializer).map(Code::from)
340    }
341}
342
343/// A helper class to deserialized wrapped Status messages.
344#[derive(Clone, Debug, Deserialize)]
345struct ErrorWrapper {
346    error: WrapperStatus,
347}
348
349#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
350#[serde(default)]
351#[non_exhaustive]
352struct WrapperStatus {
353    pub code: i32,
354    pub message: String,
355    pub status: Option<String>,
356    pub details: Vec<StatusDetails>,
357}
358
359impl TryFrom<&bytes::Bytes> for Status {
360    type Error = Error;
361
362    fn try_from(value: &bytes::Bytes) -> Result<Self, Self::Error> {
363        let wrapper = serde_json::from_slice::<ErrorWrapper>(value)
364            .map(|w| w.error)
365            .map_err(Error::deser)?;
366        let code = match wrapper.status.as_deref().map(Code::try_from) {
367            Some(Ok(code)) => code,
368            Some(Err(_)) | None => Code::Unknown,
369        };
370        Ok(Status {
371            code,
372            message: wrapper.message,
373            details: wrapper.details,
374        })
375    }
376}
377
378impl From<rpc::model::Status> for Status {
379    fn from(value: rpc::model::Status) -> Self {
380        Self {
381            code: value.code.into(),
382            message: value.message,
383            details: value.details.into_iter().map(StatusDetails::from).collect(),
384        }
385    }
386}
387
388impl From<&rpc::model::Status> for Status {
389    fn from(value: &rpc::model::Status) -> Self {
390        Self {
391            code: value.code.into(),
392            message: value.message.clone(),
393            details: value.details.iter().map(StatusDetails::from).collect(),
394        }
395    }
396}
397
398/// The type of details associated with [Status].
399///
400/// Google cloud RPCs often return a detailed error description. This details
401/// can be used to better understand the root cause of the problem.
402#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
403#[serde(rename_all = "camelCase")]
404#[non_exhaustive]
405#[serde(tag = "@type")]
406pub enum StatusDetails {
407    #[serde(rename = "type.googleapis.com/google.rpc.BadRequest")]
408    BadRequest(rpc::model::BadRequest),
409    #[serde(rename = "type.googleapis.com/google.rpc.DebugInfo")]
410    DebugInfo(rpc::model::DebugInfo),
411    #[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
412    ErrorInfo(rpc::model::ErrorInfo),
413    #[serde(rename = "type.googleapis.com/google.rpc.Help")]
414    Help(rpc::model::Help),
415    #[serde(rename = "type.googleapis.com/google.rpc.LocalizedMessage")]
416    LocalizedMessage(rpc::model::LocalizedMessage),
417    #[serde(rename = "type.googleapis.com/google.rpc.PreconditionFailure")]
418    PreconditionFailure(rpc::model::PreconditionFailure),
419    #[serde(rename = "type.googleapis.com/google.rpc.QuotaFailure")]
420    QuotaFailure(rpc::model::QuotaFailure),
421    #[serde(rename = "type.googleapis.com/google.rpc.RequestInfo")]
422    RequestInfo(rpc::model::RequestInfo),
423    #[serde(rename = "type.googleapis.com/google.rpc.ResourceInfo")]
424    ResourceInfo(rpc::model::ResourceInfo),
425    #[serde(rename = "type.googleapis.com/google.rpc.RetryInfo")]
426    RetryInfo(rpc::model::RetryInfo),
427    #[serde(untagged)]
428    Other(wkt::Any),
429}
430
431impl From<wkt::Any> for StatusDetails {
432    fn from(value: wkt::Any) -> Self {
433        macro_rules! try_convert {
434            ($($variant:ident),*) => {
435                $(
436                    if let Ok(v) = value.to_msg::<rpc::model::$variant>() {
437                        return StatusDetails::$variant(v);
438                    }
439                )*
440            };
441        }
442
443        try_convert!(
444            BadRequest,
445            DebugInfo,
446            ErrorInfo,
447            Help,
448            LocalizedMessage,
449            PreconditionFailure,
450            QuotaFailure,
451            RequestInfo,
452            ResourceInfo,
453            RetryInfo
454        );
455
456        StatusDetails::Other(value)
457    }
458}
459
460impl From<&wkt::Any> for StatusDetails {
461    fn from(value: &wkt::Any) -> Self {
462        macro_rules! try_convert {
463            ($($variant:ident),*) => {
464                $(
465                    if let Ok(v) = value.to_msg::<rpc::model::$variant>() {
466                        return StatusDetails::$variant(v);
467                    }
468                )*
469            };
470        }
471
472        try_convert!(
473            BadRequest,
474            DebugInfo,
475            ErrorInfo,
476            Help,
477            LocalizedMessage,
478            PreconditionFailure,
479            QuotaFailure,
480            RequestInfo,
481            ResourceInfo,
482            RetryInfo
483        );
484
485        StatusDetails::Other(value.clone())
486    }
487}
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492    use anyhow::Result;
493    use rpc::model::BadRequest;
494    use rpc::model::DebugInfo;
495    use rpc::model::ErrorInfo;
496    use rpc::model::Help;
497    use rpc::model::LocalizedMessage;
498    use rpc::model::PreconditionFailure;
499    use rpc::model::QuotaFailure;
500    use rpc::model::RequestInfo;
501    use rpc::model::ResourceInfo;
502    use rpc::model::RetryInfo;
503    use serde_json::json;
504    use test_case::test_case;
505
506    #[test]
507    fn status_basic_setters() {
508        let got = Status::default()
509            .set_code(Code::Unimplemented)
510            .set_message("test-message");
511        let want = Status {
512            code: Code::Unimplemented,
513            message: "test-message".into(),
514            ..Default::default()
515        };
516        assert_eq!(got, want);
517
518        let got = Status::default()
519            .set_code(Code::Unimplemented as i32)
520            .set_message("test-message");
521        let want = Status {
522            code: Code::Unimplemented,
523            message: "test-message".into(),
524            ..Default::default()
525        };
526        assert_eq!(got, want);
527    }
528
529    #[test]
530    fn status_detail_setter() -> Result<()> {
531        let d0 = StatusDetails::ErrorInfo(rpc::model::ErrorInfo::new().set_reason("test-reason"));
532        let d1 = StatusDetails::Help(
533            rpc::model::Help::new().set_links([rpc::model::help::Link::new().set_url("test-url")]),
534        );
535        let want = Status {
536            details: vec![d0.clone(), d1.clone()],
537            ..Default::default()
538        };
539
540        let got = Status::default().set_details([d0, d1]);
541        assert_eq!(got, want);
542
543        let a0 = wkt::Any::from_msg(&rpc::model::ErrorInfo::new().set_reason("test-reason"))?;
544        let a1 = wkt::Any::from_msg(
545            &rpc::model::Help::new().set_links([rpc::model::help::Link::new().set_url("test-url")]),
546        )?;
547        let got = Status::default().set_details(&[a0, a1]);
548        assert_eq!(got, want);
549
550        Ok(())
551    }
552
553    #[test]
554    fn serialization_all_variants() {
555        let status =
556            Status {
557                code: Code::Unimplemented,
558                message: "test".to_string(),
559
560                details: vec![
561                    StatusDetails::BadRequest(BadRequest::default().set_field_violations(
562                        vec![rpc::model::bad_request::FieldViolation::default()
563                        .set_field("field").set_description("desc")],
564                    )),
565                    StatusDetails::DebugInfo(
566                        DebugInfo::default()
567                            .set_stack_entries(vec!["stack".to_string()])
568                            .set_detail("detail"),
569                    ),
570                    StatusDetails::ErrorInfo(
571                        ErrorInfo::default()
572                            .set_reason("reason")
573                            .set_domain("domain")
574                            .set_metadata([("", "")].into_iter().take(0)),
575                    ),
576                    StatusDetails::Help(Help::default().set_links(
577                        vec![rpc::model::help::Link::default()
578                        .set_description( "desc")
579                        .set_url( "url")
580                    ],
581                    )),
582                    StatusDetails::LocalizedMessage(
583                        LocalizedMessage::default()
584                            .set_locale("locale")
585                            .set_message("message"),
586                    ),
587                    StatusDetails::PreconditionFailure(
588                        PreconditionFailure::default().set_violations(vec![
589                            rpc::model::precondition_failure::Violation::default()
590                                .set_type("type")
591                                .set_subject("subject")
592                                .set_description("desc"),
593                        ]),
594                    ),
595                    StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
596                        vec![rpc::model::quota_failure::Violation::default()
597                        .set_subject( "subject")
598                        .set_description( "desc")
599                    ],
600                    )),
601                    StatusDetails::RequestInfo(
602                        RequestInfo::default()
603                            .set_request_id("id")
604                            .set_serving_data("data"),
605                    ),
606                    StatusDetails::ResourceInfo(
607                        ResourceInfo::default()
608                            .set_resource_type("type")
609                            .set_resource_name("name")
610                            .set_owner("owner")
611                            .set_description("desc"),
612                    ),
613                    StatusDetails::RetryInfo(
614                        RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
615                    ),
616                ],
617            };
618        let got = serde_json::to_value(&status).unwrap();
619        let want = json!({
620            "code": Code::Unimplemented,
621            "message": "test",
622            "details": [
623                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
624                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
625                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
626                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
627                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
628                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
629                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
630                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
631                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
632                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
633            ]
634        });
635        assert_eq!(got, want);
636    }
637
638    #[test]
639    fn deserialization_all_variants() {
640        let json = json!({
641            "code": Code::Unknown as i32,
642            "message": "test",
643            "details": [
644                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
645                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
646                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
647                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
648                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
649                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
650                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
651                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
652                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
653                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
654            ]
655        });
656        let got: Status = serde_json::from_value(json).unwrap();
657        let want = Status {
658            code: Code::Unknown,
659            message: "test".to_string(),
660            details: vec![
661                StatusDetails::BadRequest(BadRequest::default().set_field_violations(
662                    vec![rpc::model::bad_request::FieldViolation::default()
663                        .set_field( "field" )
664                        .set_description( "desc" )
665                    ],
666                )),
667                StatusDetails::DebugInfo(
668                    DebugInfo::default()
669                        .set_stack_entries(vec!["stack".to_string()])
670                        .set_detail("detail"),
671                ),
672                StatusDetails::ErrorInfo(
673                    ErrorInfo::default()
674                        .set_reason("reason")
675                        .set_domain("domain"),
676                ),
677                StatusDetails::Help(Help::default().set_links(vec![
678                    rpc::model::help::Link::default().set_description("desc").set_url("url"),
679                ])),
680                StatusDetails::LocalizedMessage(
681                    LocalizedMessage::default()
682                        .set_locale("locale")
683                        .set_message("message"),
684                ),
685                StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
686                    vec![rpc::model::precondition_failure::Violation::default()
687                        .set_type( "type" )
688                        .set_subject( "subject" )
689                        .set_description( "desc" )
690                    ],
691                )),
692                StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
693                    vec![rpc::model::quota_failure::Violation::default()
694                        .set_subject( "subject")
695                        .set_description( "desc")
696                    ],
697                )),
698                StatusDetails::RequestInfo(
699                    RequestInfo::default()
700                        .set_request_id("id")
701                        .set_serving_data("data"),
702                ),
703                StatusDetails::ResourceInfo(
704                    ResourceInfo::default()
705                        .set_resource_type("type")
706                        .set_resource_name("name")
707                        .set_owner("owner")
708                        .set_description("desc"),
709                ),
710                StatusDetails::RetryInfo(
711                    RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
712                ),
713            ],
714        };
715        assert_eq!(got, want);
716    }
717
718    #[test]
719    fn serialization_other() -> Result<()> {
720        const TIME: &str = "2025-05-27T10:00:00Z";
721        let timestamp = wkt::Timestamp::try_from(TIME)?;
722        let any = wkt::Any::from_msg(&timestamp)?;
723        let input = Status {
724            code: Code::Unknown,
725            message: "test".to_string(),
726            details: vec![StatusDetails::Other(any)],
727        };
728        let got = serde_json::to_value(&input)?;
729        let want = json!({
730            "code": Code::Unknown as i32,
731            "message": "test",
732            "details": [
733                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
734            ]
735        });
736        assert_eq!(got, want);
737        Ok(())
738    }
739
740    #[test]
741    fn deserialization_other() -> Result<()> {
742        const TIME: &str = "2025-05-27T10:00:00Z";
743        let json = json!({
744            "code": Code::Unknown as i32,
745            "message": "test",
746            "details": [
747                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
748            ]
749        });
750        let timestamp = wkt::Timestamp::try_from(TIME)?;
751        let any = wkt::Any::from_msg(&timestamp)?;
752        let got: Status = serde_json::from_value(json)?;
753        let want = Status {
754            code: Code::Unknown,
755            message: "test".to_string(),
756            details: vec![StatusDetails::Other(any)],
757        };
758        assert_eq!(got, want);
759        Ok(())
760    }
761
762    #[test]
763    fn status_from_rpc_no_details() {
764        let input = rpc::model::Status::default()
765            .set_code(Code::Unavailable as i32)
766            .set_message("try-again");
767        let got = Status::from(&input);
768        assert_eq!(got.code, Code::Unavailable);
769        assert_eq!(got.message, "try-again");
770    }
771
772    #[test_case(
773        BadRequest::default(),
774        StatusDetails::BadRequest(BadRequest::default())
775    )]
776    #[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
777    #[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
778    #[test_case(Help::default(), StatusDetails::Help(Help::default()))]
779    #[test_case(
780        LocalizedMessage::default(),
781        StatusDetails::LocalizedMessage(LocalizedMessage::default())
782    )]
783    #[test_case(
784        PreconditionFailure::default(),
785        StatusDetails::PreconditionFailure(PreconditionFailure::default())
786    )]
787    #[test_case(
788        QuotaFailure::default(),
789        StatusDetails::QuotaFailure(QuotaFailure::default())
790    )]
791    #[test_case(
792        RequestInfo::default(),
793        StatusDetails::RequestInfo(RequestInfo::default())
794    )]
795    #[test_case(
796        ResourceInfo::default(),
797        StatusDetails::ResourceInfo(ResourceInfo::default())
798    )]
799    #[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
800    fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
801    where
802        T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
803    {
804        let input = rpc::model::Status::default()
805            .set_code(Code::Unavailable as i32)
806            .set_message("try-again")
807            .set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
808
809        let from_ref = Status::from(&input);
810        let status = Status::from(input);
811        assert_eq!(from_ref, status);
812        assert_eq!(status.code, Code::Unavailable);
813        assert_eq!(status.message, "try-again");
814
815        let got = status.details.first();
816        assert_eq!(got, Some(&want));
817    }
818
819    #[test]
820    fn status_from_rpc_unknown_details() {
821        let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
822        let input = rpc::model::Status::default()
823            .set_code(Code::Unavailable as i32)
824            .set_message("try-again")
825            .set_details(vec![any.clone()]);
826        let from_ref = Status::from(&input);
827        let got = Status::from(input);
828        assert_eq!(from_ref, got);
829        assert_eq!(got.code, Code::Unavailable);
830        assert_eq!(got.message, "try-again");
831
832        let got = got.details.first();
833        let want = StatusDetails::Other(any);
834        assert_eq!(got, Some(&want));
835    }
836
837    // This is a sample string received from production. It is useful to
838    // validate the serialization helpers.
839    const SAMPLE_PAYLOAD: &[u8] = b"{\n  \"error\": {\n    \"code\": 400,\n    \"message\": \"The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]\",\n    \"status\": \"INVALID_ARGUMENT\"\n  }\n}\n";
840    const INVALID_CODE_PAYLOAD: &[u8] = b"{\n  \"error\": {\n    \"code\": 400,\n    \"message\": \"The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]\",\n    \"status\": \"NOT-A-VALID-CODE\"\n  }\n}\n";
841
842    // The corresponding status message.
843    fn sample_status() -> Status {
844        Status {
845            code: Code::InvalidArgument,
846            message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
847                .into(),
848            details: [].into(),
849        }
850    }
851
852    #[test]
853    fn deserialize_status() {
854        let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
855        let want = ErrorWrapper {
856            error: WrapperStatus {
857                code: 400,
858                status: Some("INVALID_ARGUMENT".to_string()),
859                message:
860                    "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
861                        .into(),
862                details: [].into(),
863            },
864        };
865        assert_eq!(got.error, want.error);
866    }
867
868    #[test]
869    fn try_from_bytes() -> Result<()> {
870        let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
871        let want = sample_status();
872        assert_eq!(got, want);
873
874        let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
875        let err = got.unwrap_err();
876        assert!(err.is_deserialization(), "{err:?}");
877
878        let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
879        let err = got.unwrap_err();
880        assert!(err.is_deserialization(), "{err:?}");
881
882        let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
883        assert_eq!(got.code, Code::Unknown);
884        Ok(())
885    }
886
887    #[test]
888    fn code_to_string() {
889        let got = String::from(Code::AlreadyExists);
890        let want = "ALREADY_EXISTS";
891        assert_eq!(got, want);
892    }
893
894    #[test_case("OK")]
895    #[test_case("CANCELLED")]
896    #[test_case("UNKNOWN")]
897    #[test_case("INVALID_ARGUMENT")]
898    #[test_case("DEADLINE_EXCEEDED")]
899    #[test_case("NOT_FOUND")]
900    #[test_case("ALREADY_EXISTS")]
901    #[test_case("PERMISSION_DENIED")]
902    #[test_case("RESOURCE_EXHAUSTED")]
903    #[test_case("FAILED_PRECONDITION")]
904    #[test_case("ABORTED")]
905    #[test_case("OUT_OF_RANGE")]
906    #[test_case("UNIMPLEMENTED")]
907    #[test_case("INTERNAL")]
908    #[test_case("UNAVAILABLE")]
909    #[test_case("DATA_LOSS")]
910    #[test_case("UNAUTHENTICATED")]
911    fn code_roundtrip(input: &str) -> Result<()> {
912        let code = Code::try_from(input).unwrap();
913        let output = String::from(code);
914        assert_eq!(output.as_str(), input.to_string());
915        assert_eq!(&format!("{code}"), input);
916        assert_eq!(code.name(), input);
917        Ok(())
918    }
919
920    #[test_case("OK")]
921    #[test_case("CANCELLED")]
922    #[test_case("UNKNOWN")]
923    #[test_case("INVALID_ARGUMENT")]
924    #[test_case("DEADLINE_EXCEEDED")]
925    #[test_case("NOT_FOUND")]
926    #[test_case("ALREADY_EXISTS")]
927    #[test_case("PERMISSION_DENIED")]
928    #[test_case("RESOURCE_EXHAUSTED")]
929    #[test_case("FAILED_PRECONDITION")]
930    #[test_case("ABORTED")]
931    #[test_case("OUT_OF_RANGE")]
932    #[test_case("UNIMPLEMENTED")]
933    #[test_case("INTERNAL")]
934    #[test_case("UNAVAILABLE")]
935    #[test_case("DATA_LOSS")]
936    #[test_case("UNAUTHENTICATED")]
937    fn code_serialize_roundtrip(input: &str) -> Result<()> {
938        let want = Code::try_from(input).unwrap();
939        let serialized = serde_json::to_value(want)?;
940        let got = serde_json::from_value::<Code>(serialized)?;
941        assert_eq!(got, want);
942        Ok(())
943    }
944
945    #[test]
946    fn code_try_from_string_error() {
947        let err = Code::try_from("INVALID-NOT-A-CODE");
948        assert!(
949            matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
950            "expected error in try_from, got {err:?}"
951        );
952    }
953
954    #[test]
955    fn code_deserialize_invalid_type() {
956        let input = json!({"k": "v"});
957        let err = serde_json::from_value::<Code>(input);
958        assert!(err.is_err(), "expected an error, got {err:?}");
959    }
960
961    #[test]
962    fn code_deserialize_unknown() -> Result<()> {
963        let input = json!(-17);
964        let code = serde_json::from_value::<Code>(input)?;
965        assert_eq!(code, Code::Unknown);
966        Ok(())
967    }
968}