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