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        // TODO(#505) - change this test so it does not require updates as new
619        //     fields appear.
620        let got = serde_json::to_value(&status).unwrap();
621        let want = json!({
622            "code": Code::Unimplemented,
623            "message": "test",
624            "details": [
625                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
626                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
627                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
628                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
629                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
630                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
631                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
632                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
633                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
634                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
635            ]
636        });
637        assert_eq!(got, want);
638    }
639
640    #[test]
641    fn deserialization_all_variants() {
642        let json = json!({
643            "code": Code::Unknown as i32,
644            "message": "test",
645            "details": [
646                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
647                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
648                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
649                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
650                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
651                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
652                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
653                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
654                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
655                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
656            ]
657        });
658        let got: Status = serde_json::from_value(json).unwrap();
659        let want = Status {
660            code: Code::Unknown,
661            message: "test".to_string(),
662            details: vec![
663                StatusDetails::BadRequest(BadRequest::default().set_field_violations(
664                    vec![rpc::model::bad_request::FieldViolation::default()
665                        .set_field( "field" )
666                        .set_description( "desc" )
667                    ],
668                )),
669                StatusDetails::DebugInfo(
670                    DebugInfo::default()
671                        .set_stack_entries(vec!["stack".to_string()])
672                        .set_detail("detail"),
673                ),
674                StatusDetails::ErrorInfo(
675                    ErrorInfo::default()
676                        .set_reason("reason")
677                        .set_domain("domain"),
678                ),
679                StatusDetails::Help(Help::default().set_links(vec![
680                    rpc::model::help::Link::default().set_description("desc").set_url("url"),
681                ])),
682                StatusDetails::LocalizedMessage(
683                    LocalizedMessage::default()
684                        .set_locale("locale")
685                        .set_message("message"),
686                ),
687                StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
688                    vec![rpc::model::precondition_failure::Violation::default()
689                        .set_type( "type" )
690                        .set_subject( "subject" )
691                        .set_description( "desc" )
692                    ],
693                )),
694                StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
695                    vec![rpc::model::quota_failure::Violation::default()
696                        .set_subject( "subject")
697                        .set_description( "desc")
698                    ],
699                )),
700                StatusDetails::RequestInfo(
701                    RequestInfo::default()
702                        .set_request_id("id")
703                        .set_serving_data("data"),
704                ),
705                StatusDetails::ResourceInfo(
706                    ResourceInfo::default()
707                        .set_resource_type("type")
708                        .set_resource_name("name")
709                        .set_owner("owner")
710                        .set_description("desc"),
711                ),
712                StatusDetails::RetryInfo(
713                    RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
714                ),
715            ],
716        };
717        assert_eq!(got, want);
718    }
719
720    #[test]
721    fn serialization_other() -> Result<()> {
722        const TIME: &str = "2025-05-27T10:00:00Z";
723        let timestamp = wkt::Timestamp::try_from(TIME)?;
724        let any = wkt::Any::from_msg(&timestamp)?;
725        let input = Status {
726            code: Code::Unknown,
727            message: "test".to_string(),
728            details: vec![StatusDetails::Other(any)],
729        };
730        let got = serde_json::to_value(&input)?;
731        let want = json!({
732            "code": Code::Unknown as i32,
733            "message": "test",
734            "details": [
735                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
736            ]
737        });
738        assert_eq!(got, want);
739        Ok(())
740    }
741
742    #[test]
743    fn deserialization_other() -> Result<()> {
744        const TIME: &str = "2025-05-27T10:00:00Z";
745        let json = json!({
746            "code": Code::Unknown as i32,
747            "message": "test",
748            "details": [
749                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
750            ]
751        });
752        let timestamp = wkt::Timestamp::try_from(TIME)?;
753        let any = wkt::Any::from_msg(&timestamp)?;
754        let got: Status = serde_json::from_value(json)?;
755        let want = Status {
756            code: Code::Unknown,
757            message: "test".to_string(),
758            details: vec![StatusDetails::Other(any)],
759        };
760        assert_eq!(got, want);
761        Ok(())
762    }
763
764    #[test]
765    fn status_from_rpc_no_details() {
766        let input = rpc::model::Status::default()
767            .set_code(Code::Unavailable as i32)
768            .set_message("try-again");
769        let got = Status::from(&input);
770        assert_eq!(got.code, Code::Unavailable);
771        assert_eq!(got.message, "try-again");
772    }
773
774    #[test_case(
775        BadRequest::default(),
776        StatusDetails::BadRequest(BadRequest::default())
777    )]
778    #[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
779    #[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
780    #[test_case(Help::default(), StatusDetails::Help(Help::default()))]
781    #[test_case(
782        LocalizedMessage::default(),
783        StatusDetails::LocalizedMessage(LocalizedMessage::default())
784    )]
785    #[test_case(
786        PreconditionFailure::default(),
787        StatusDetails::PreconditionFailure(PreconditionFailure::default())
788    )]
789    #[test_case(
790        QuotaFailure::default(),
791        StatusDetails::QuotaFailure(QuotaFailure::default())
792    )]
793    #[test_case(
794        RequestInfo::default(),
795        StatusDetails::RequestInfo(RequestInfo::default())
796    )]
797    #[test_case(
798        ResourceInfo::default(),
799        StatusDetails::ResourceInfo(ResourceInfo::default())
800    )]
801    #[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
802    fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
803    where
804        T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
805    {
806        let input = rpc::model::Status::default()
807            .set_code(Code::Unavailable as i32)
808            .set_message("try-again")
809            .set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
810
811        let from_ref = Status::from(&input);
812        let status = Status::from(input);
813        assert_eq!(from_ref, status);
814        assert_eq!(status.code, Code::Unavailable);
815        assert_eq!(status.message, "try-again");
816
817        let got = status.details.first();
818        assert_eq!(got, Some(&want));
819    }
820
821    #[test]
822    fn status_from_rpc_unknown_details() {
823        let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
824        let input = rpc::model::Status::default()
825            .set_code(Code::Unavailable as i32)
826            .set_message("try-again")
827            .set_details(vec![any.clone()]);
828        let from_ref = Status::from(&input);
829        let got = Status::from(input);
830        assert_eq!(from_ref, got);
831        assert_eq!(got.code, Code::Unavailable);
832        assert_eq!(got.message, "try-again");
833
834        let got = got.details.first();
835        let want = StatusDetails::Other(any);
836        assert_eq!(got, Some(&want));
837    }
838
839    // This is a sample string received from production. It is useful to
840    // validate the serialization helpers.
841    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";
842    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";
843
844    // The corresponding status message.
845    fn sample_status() -> Status {
846        Status {
847            code: Code::InvalidArgument,
848            message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
849                .into(),
850            details: [].into(),
851        }
852    }
853
854    #[test]
855    fn deserialize_status() {
856        let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
857        let want = ErrorWrapper {
858            error: WrapperStatus {
859                code: 400,
860                status: Some("INVALID_ARGUMENT".to_string()),
861                message:
862                    "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
863                        .into(),
864                details: [].into(),
865            },
866        };
867        assert_eq!(got.error, want.error);
868    }
869
870    #[test]
871    fn try_from_bytes() -> Result<()> {
872        let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
873        let want = sample_status();
874        assert_eq!(got, want);
875
876        let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
877        let err = got.unwrap_err();
878        assert!(err.is_deserialization(), "{err:?}");
879
880        let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
881        let err = got.unwrap_err();
882        assert!(err.is_deserialization(), "{err:?}");
883
884        let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
885        assert_eq!(got.code, Code::Unknown);
886        Ok(())
887    }
888
889    #[test]
890    fn code_to_string() {
891        let got = String::from(Code::AlreadyExists);
892        let want = "ALREADY_EXISTS";
893        assert_eq!(got, want);
894    }
895
896    #[test_case("OK")]
897    #[test_case("CANCELLED")]
898    #[test_case("UNKNOWN")]
899    #[test_case("INVALID_ARGUMENT")]
900    #[test_case("DEADLINE_EXCEEDED")]
901    #[test_case("NOT_FOUND")]
902    #[test_case("ALREADY_EXISTS")]
903    #[test_case("PERMISSION_DENIED")]
904    #[test_case("RESOURCE_EXHAUSTED")]
905    #[test_case("FAILED_PRECONDITION")]
906    #[test_case("ABORTED")]
907    #[test_case("OUT_OF_RANGE")]
908    #[test_case("UNIMPLEMENTED")]
909    #[test_case("INTERNAL")]
910    #[test_case("UNAVAILABLE")]
911    #[test_case("DATA_LOSS")]
912    #[test_case("UNAUTHENTICATED")]
913    fn code_roundtrip(input: &str) -> Result<()> {
914        let code = Code::try_from(input).unwrap();
915        let output = String::from(code);
916        assert_eq!(output.as_str(), input.to_string());
917        assert_eq!(&format!("{code}"), input);
918        assert_eq!(code.name(), input);
919        Ok(())
920    }
921
922    #[test_case("OK")]
923    #[test_case("CANCELLED")]
924    #[test_case("UNKNOWN")]
925    #[test_case("INVALID_ARGUMENT")]
926    #[test_case("DEADLINE_EXCEEDED")]
927    #[test_case("NOT_FOUND")]
928    #[test_case("ALREADY_EXISTS")]
929    #[test_case("PERMISSION_DENIED")]
930    #[test_case("RESOURCE_EXHAUSTED")]
931    #[test_case("FAILED_PRECONDITION")]
932    #[test_case("ABORTED")]
933    #[test_case("OUT_OF_RANGE")]
934    #[test_case("UNIMPLEMENTED")]
935    #[test_case("INTERNAL")]
936    #[test_case("UNAVAILABLE")]
937    #[test_case("DATA_LOSS")]
938    #[test_case("UNAUTHENTICATED")]
939    fn code_serialize_roundtrip(input: &str) -> Result<()> {
940        let want = Code::try_from(input).unwrap();
941        let serialized = serde_json::to_value(want)?;
942        let got = serde_json::from_value::<Code>(serialized)?;
943        assert_eq!(got, want);
944        Ok(())
945    }
946
947    #[test]
948    fn code_try_from_string_error() {
949        let err = Code::try_from("INVALID-NOT-A-CODE");
950        assert!(
951            matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
952            "expected error in try_from, got {err:?}"
953        );
954    }
955
956    #[test]
957    fn code_deserialize_invalid_type() {
958        let input = json!({"k": "v"});
959        let err = serde_json::from_value::<Code>(input);
960        assert!(err.is_err(), "expected an error, got {err:?}");
961    }
962
963    #[test]
964    fn code_deserialize_unknown() -> Result<()> {
965        let input = json!(-17);
966        let code = serde_json::from_value::<Code>(input)?;
967        assert_eq!(code, Code::Unknown);
968        Ok(())
969    }
970}