Skip to main content

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<google_cloud_rpc::model::Status> for Status {
374    fn from(value: google_cloud_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<&google_cloud_rpc::model::Status> for Status {
384    fn from(value: &google_cloud_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(google_cloud_rpc::model::BadRequest),
404    #[serde(rename = "type.googleapis.com/google.rpc.DebugInfo")]
405    DebugInfo(google_cloud_rpc::model::DebugInfo),
406    #[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
407    ErrorInfo(google_cloud_rpc::model::ErrorInfo),
408    #[serde(rename = "type.googleapis.com/google.rpc.Help")]
409    Help(google_cloud_rpc::model::Help),
410    #[serde(rename = "type.googleapis.com/google.rpc.LocalizedMessage")]
411    LocalizedMessage(google_cloud_rpc::model::LocalizedMessage),
412    #[serde(rename = "type.googleapis.com/google.rpc.PreconditionFailure")]
413    PreconditionFailure(google_cloud_rpc::model::PreconditionFailure),
414    #[serde(rename = "type.googleapis.com/google.rpc.QuotaFailure")]
415    QuotaFailure(google_cloud_rpc::model::QuotaFailure),
416    #[serde(rename = "type.googleapis.com/google.rpc.RequestInfo")]
417    RequestInfo(google_cloud_rpc::model::RequestInfo),
418    #[serde(rename = "type.googleapis.com/google.rpc.ResourceInfo")]
419    ResourceInfo(google_cloud_rpc::model::ResourceInfo),
420    #[serde(rename = "type.googleapis.com/google.rpc.RetryInfo")]
421    RetryInfo(google_cloud_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::<google_cloud_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::<google_cloud_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 google_cloud_rpc::model::DebugInfo;
489    use google_cloud_rpc::model::ErrorInfo;
490    use google_cloud_rpc::model::LocalizedMessage;
491    use google_cloud_rpc::model::RequestInfo;
492    use google_cloud_rpc::model::ResourceInfo;
493    use google_cloud_rpc::model::RetryInfo;
494    use google_cloud_rpc::model::{BadRequest, bad_request};
495    use google_cloud_rpc::model::{Help, help};
496    use google_cloud_rpc::model::{PreconditionFailure, precondition_failure};
497    use google_cloud_rpc::model::{QuotaFailure, quota_failure};
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(ErrorInfo::new().set_reason("test-reason"));
527        let d1 =
528            StatusDetails::Help(Help::new().set_links([help::Link::new().set_url("test-url")]));
529        let want = Status {
530            details: vec![d0.clone(), d1.clone()],
531            ..Default::default()
532        };
533
534        let got = Status::default().set_details([d0, d1]);
535        assert_eq!(got, want);
536
537        let a0 = wkt::Any::from_msg(&ErrorInfo::new().set_reason("test-reason"))?;
538        let a1 =
539            wkt::Any::from_msg(&Help::new().set_links([help::Link::new().set_url("test-url")]))?;
540        let got = Status::default().set_details(&[a0, a1]);
541        assert_eq!(got, want);
542
543        Ok(())
544    }
545
546    #[test]
547    fn serialization_all_variants() {
548        let status = Status {
549            code: Code::Unimplemented,
550            message: "test".to_string(),
551
552            details: vec![
553                StatusDetails::BadRequest(BadRequest::default().set_field_violations(vec![
554                        bad_request::FieldViolation::default()
555                            .set_field("field")
556                            .set_description("desc"),
557                    ])),
558                StatusDetails::DebugInfo(
559                    DebugInfo::default()
560                        .set_stack_entries(vec!["stack".to_string()])
561                        .set_detail("detail"),
562                ),
563                StatusDetails::ErrorInfo(
564                    ErrorInfo::default()
565                        .set_reason("reason")
566                        .set_domain("domain")
567                        .set_metadata([("", "")].into_iter().take(0)),
568                ),
569                StatusDetails::Help(Help::default().set_links(vec![
570                    help::Link::default().set_description("desc").set_url("url"),
571                ])),
572                StatusDetails::LocalizedMessage(
573                    LocalizedMessage::default()
574                        .set_locale("locale")
575                        .set_message("message"),
576                ),
577                StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
578                    vec![
579                            precondition_failure::Violation::default()
580                                .set_type("type")
581                                .set_subject("subject")
582                                .set_description("desc"),
583                        ],
584                )),
585                StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
586                    vec![quota_failure::Violation::default()
587                        .set_subject( "subject")
588                        .set_description( "desc")
589                    ],
590                )),
591                StatusDetails::RequestInfo(
592                    RequestInfo::default()
593                        .set_request_id("id")
594                        .set_serving_data("data"),
595                ),
596                StatusDetails::ResourceInfo(
597                    ResourceInfo::default()
598                        .set_resource_type("type")
599                        .set_resource_name("name")
600                        .set_owner("owner")
601                        .set_description("desc"),
602                ),
603                StatusDetails::RetryInfo(
604                    RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
605                ),
606            ],
607        };
608        let got = serde_json::to_value(&status).unwrap();
609        let want = json!({
610            "code": Code::Unimplemented,
611            "message": "test",
612            "details": [
613                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
614                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
615                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
616                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
617                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
618                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
619                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
620                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
621                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
622                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
623            ]
624        });
625        assert_eq!(got, want);
626    }
627
628    #[test]
629    fn deserialization_all_variants() {
630        let json = json!({
631            "code": Code::Unknown as i32,
632            "message": "test",
633            "details": [
634                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
635                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
636                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
637                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
638                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
639                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
640                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
641                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
642                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
643                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
644            ]
645        });
646        let got: Status = serde_json::from_value(json).unwrap();
647        let want = Status {
648            code: Code::Unknown,
649            message: "test".to_string(),
650            details: vec![
651                StatusDetails::BadRequest(BadRequest::default().set_field_violations(
652                    vec![bad_request::FieldViolation::default()
653                        .set_field( "field" )
654                        .set_description( "desc" )
655                    ],
656                )),
657                StatusDetails::DebugInfo(
658                    DebugInfo::default()
659                        .set_stack_entries(vec!["stack".to_string()])
660                        .set_detail("detail"),
661                ),
662                StatusDetails::ErrorInfo(
663                    ErrorInfo::default()
664                        .set_reason("reason")
665                        .set_domain("domain"),
666                ),
667                StatusDetails::Help(Help::default().set_links(vec![
668                    help::Link::default().set_description("desc").set_url("url"),
669                ])),
670                StatusDetails::LocalizedMessage(
671                    LocalizedMessage::default()
672                        .set_locale("locale")
673                        .set_message("message"),
674                ),
675                StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
676                    vec![precondition_failure::Violation::default()
677                        .set_type( "type" )
678                        .set_subject( "subject" )
679                        .set_description( "desc" )
680                    ],
681                )),
682                StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
683                    vec![quota_failure::Violation::default()
684                        .set_subject( "subject")
685                        .set_description( "desc")
686                    ],
687                )),
688                StatusDetails::RequestInfo(
689                    RequestInfo::default()
690                        .set_request_id("id")
691                        .set_serving_data("data"),
692                ),
693                StatusDetails::ResourceInfo(
694                    ResourceInfo::default()
695                        .set_resource_type("type")
696                        .set_resource_name("name")
697                        .set_owner("owner")
698                        .set_description("desc"),
699                ),
700                StatusDetails::RetryInfo(
701                    RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
702                ),
703            ],
704        };
705        assert_eq!(got, want);
706    }
707
708    #[test]
709    fn serialization_other() -> Result<()> {
710        const TIME: &str = "2025-05-27T10:00:00Z";
711        let timestamp = wkt::Timestamp::try_from(TIME)?;
712        let any = wkt::Any::from_msg(&timestamp)?;
713        let input = Status {
714            code: Code::Unknown,
715            message: "test".to_string(),
716            details: vec![StatusDetails::Other(any)],
717        };
718        let got = serde_json::to_value(&input)?;
719        let want = json!({
720            "code": Code::Unknown as i32,
721            "message": "test",
722            "details": [
723                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
724            ]
725        });
726        assert_eq!(got, want);
727        Ok(())
728    }
729
730    #[test]
731    fn deserialization_other() -> Result<()> {
732        const TIME: &str = "2025-05-27T10:00:00Z";
733        let json = json!({
734            "code": Code::Unknown as i32,
735            "message": "test",
736            "details": [
737                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
738            ]
739        });
740        let timestamp = wkt::Timestamp::try_from(TIME)?;
741        let any = wkt::Any::from_msg(&timestamp)?;
742        let got: Status = serde_json::from_value(json)?;
743        let want = Status {
744            code: Code::Unknown,
745            message: "test".to_string(),
746            details: vec![StatusDetails::Other(any)],
747        };
748        assert_eq!(got, want);
749        Ok(())
750    }
751
752    #[test]
753    fn status_from_rpc_no_details() {
754        let input = google_cloud_rpc::model::Status::default()
755            .set_code(Code::Unavailable as i32)
756            .set_message("try-again");
757        let got = Status::from(&input);
758        assert_eq!(got.code, Code::Unavailable);
759        assert_eq!(got.message, "try-again");
760    }
761
762    #[test_case(
763        BadRequest::default(),
764        StatusDetails::BadRequest(BadRequest::default())
765    )]
766    #[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
767    #[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
768    #[test_case(Help::default(), StatusDetails::Help(Help::default()))]
769    #[test_case(
770        LocalizedMessage::default(),
771        StatusDetails::LocalizedMessage(LocalizedMessage::default())
772    )]
773    #[test_case(
774        PreconditionFailure::default(),
775        StatusDetails::PreconditionFailure(PreconditionFailure::default())
776    )]
777    #[test_case(
778        QuotaFailure::default(),
779        StatusDetails::QuotaFailure(QuotaFailure::default())
780    )]
781    #[test_case(
782        RequestInfo::default(),
783        StatusDetails::RequestInfo(RequestInfo::default())
784    )]
785    #[test_case(
786        ResourceInfo::default(),
787        StatusDetails::ResourceInfo(ResourceInfo::default())
788    )]
789    #[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
790    fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
791    where
792        T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
793    {
794        let input = google_cloud_rpc::model::Status::default()
795            .set_code(Code::Unavailable as i32)
796            .set_message("try-again")
797            .set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
798
799        let from_ref = Status::from(&input);
800        let status = Status::from(input);
801        assert_eq!(from_ref, status);
802        assert_eq!(status.code, Code::Unavailable);
803        assert_eq!(status.message, "try-again");
804
805        let got = status.details.first();
806        assert_eq!(got, Some(&want));
807    }
808
809    #[test]
810    fn status_from_rpc_unknown_details() {
811        let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
812        let input = google_cloud_rpc::model::Status::default()
813            .set_code(Code::Unavailable as i32)
814            .set_message("try-again")
815            .set_details(vec![any.clone()]);
816        let from_ref = Status::from(&input);
817        let got = Status::from(input);
818        assert_eq!(from_ref, got);
819        assert_eq!(got.code, Code::Unavailable);
820        assert_eq!(got.message, "try-again");
821
822        let got = got.details.first();
823        let want = StatusDetails::Other(any);
824        assert_eq!(got, Some(&want));
825    }
826
827    // This is a sample string received from production. It is useful to
828    // validate the serialization helpers.
829    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";
830    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";
831
832    // The corresponding status message.
833    fn sample_status() -> Status {
834        Status {
835            code: Code::InvalidArgument,
836            message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
837                .into(),
838            details: [].into(),
839        }
840    }
841
842    #[test]
843    fn deserialize_status() {
844        let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
845        let want = ErrorWrapper {
846            error: WrapperStatus {
847                code: 400,
848                status: Some("INVALID_ARGUMENT".to_string()),
849                message:
850                    "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
851                        .into(),
852                details: [].into(),
853            },
854        };
855        assert_eq!(got.error, want.error);
856    }
857
858    #[test]
859    fn try_from_bytes() -> Result<()> {
860        let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
861        let want = sample_status();
862        assert_eq!(got, want);
863
864        let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
865        let err = got.unwrap_err();
866        assert!(err.is_deserialization(), "{err:?}");
867
868        let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
869        let err = got.unwrap_err();
870        assert!(err.is_deserialization(), "{err:?}");
871
872        let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
873        assert_eq!(got.code, Code::Unknown);
874        Ok(())
875    }
876
877    #[test]
878    fn code_to_string() {
879        let got = String::from(Code::AlreadyExists);
880        let want = "ALREADY_EXISTS";
881        assert_eq!(got, want);
882    }
883
884    #[test_case("OK")]
885    #[test_case("CANCELLED")]
886    #[test_case("UNKNOWN")]
887    #[test_case("INVALID_ARGUMENT")]
888    #[test_case("DEADLINE_EXCEEDED")]
889    #[test_case("NOT_FOUND")]
890    #[test_case("ALREADY_EXISTS")]
891    #[test_case("PERMISSION_DENIED")]
892    #[test_case("RESOURCE_EXHAUSTED")]
893    #[test_case("FAILED_PRECONDITION")]
894    #[test_case("ABORTED")]
895    #[test_case("OUT_OF_RANGE")]
896    #[test_case("UNIMPLEMENTED")]
897    #[test_case("INTERNAL")]
898    #[test_case("UNAVAILABLE")]
899    #[test_case("DATA_LOSS")]
900    #[test_case("UNAUTHENTICATED")]
901    fn code_roundtrip(input: &str) -> Result<()> {
902        let code = Code::try_from(input).unwrap();
903        let output = String::from(code);
904        assert_eq!(output.as_str(), input.to_string());
905        assert_eq!(&format!("{code}"), input);
906        assert_eq!(code.name(), input);
907        Ok(())
908    }
909
910    #[test_case("OK")]
911    #[test_case("CANCELLED")]
912    #[test_case("UNKNOWN")]
913    #[test_case("INVALID_ARGUMENT")]
914    #[test_case("DEADLINE_EXCEEDED")]
915    #[test_case("NOT_FOUND")]
916    #[test_case("ALREADY_EXISTS")]
917    #[test_case("PERMISSION_DENIED")]
918    #[test_case("RESOURCE_EXHAUSTED")]
919    #[test_case("FAILED_PRECONDITION")]
920    #[test_case("ABORTED")]
921    #[test_case("OUT_OF_RANGE")]
922    #[test_case("UNIMPLEMENTED")]
923    #[test_case("INTERNAL")]
924    #[test_case("UNAVAILABLE")]
925    #[test_case("DATA_LOSS")]
926    #[test_case("UNAUTHENTICATED")]
927    fn code_serialize_roundtrip(input: &str) -> Result<()> {
928        let want = Code::try_from(input).unwrap();
929        let serialized = serde_json::to_value(want)?;
930        let got = serde_json::from_value::<Code>(serialized)?;
931        assert_eq!(got, want);
932        Ok(())
933    }
934
935    #[test]
936    fn code_try_from_string_error() {
937        let err = Code::try_from("INVALID-NOT-A-CODE");
938        assert!(
939            matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
940            "expected error in try_from, got {err:?}"
941        );
942    }
943
944    #[test]
945    fn code_deserialize_invalid_type() {
946        let input = json!({"k": "v"});
947        let err = serde_json::from_value::<Code>(input);
948        assert!(err.is_err(), "expected an error, got {err:?}");
949    }
950
951    #[test]
952    fn code_deserialize_unknown() -> Result<()> {
953        let input = json!(-17);
954        let code = serde_json::from_value::<Code>(input)?;
955        assert_eq!(code, Code::Unknown);
956        Ok(())
957    }
958}