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    /// Returns the string representation of the error code.
235    pub fn name(&self) -> &'static str {
236        match self {
237            Code::Ok => "OK",
238            Code::Cancelled => "CANCELLED",
239            Code::Unknown => "UNKNOWN",
240            Code::InvalidArgument => "INVALID_ARGUMENT",
241            Code::DeadlineExceeded => "DEADLINE_EXCEEDED",
242            Code::NotFound => "NOT_FOUND",
243            Code::AlreadyExists => "ALREADY_EXISTS",
244            Code::PermissionDenied => "PERMISSION_DENIED",
245            Code::ResourceExhausted => "RESOURCE_EXHAUSTED",
246            Code::FailedPrecondition => "FAILED_PRECONDITION",
247            Code::Aborted => "ABORTED",
248            Code::OutOfRange => "OUT_OF_RANGE",
249            Code::Unimplemented => "UNIMPLEMENTED",
250            Code::Internal => "INTERNAL",
251            Code::Unavailable => "UNAVAILABLE",
252            Code::DataLoss => "DATA_LOSS",
253            Code::Unauthenticated => "UNAUTHENTICATED",
254        }
255    }
256}
257
258impl std::convert::From<i32> for Code {
259    fn from(value: i32) -> Self {
260        match value {
261            0 => Code::Ok,
262            1 => Code::Cancelled,
263            2 => Code::Unknown,
264            3 => Code::InvalidArgument,
265            4 => Code::DeadlineExceeded,
266            5 => Code::NotFound,
267            6 => Code::AlreadyExists,
268            7 => Code::PermissionDenied,
269            8 => Code::ResourceExhausted,
270            9 => Code::FailedPrecondition,
271            10 => Code::Aborted,
272            11 => Code::OutOfRange,
273            12 => Code::Unimplemented,
274            13 => Code::Internal,
275            14 => Code::Unavailable,
276            15 => Code::DataLoss,
277            16 => Code::Unauthenticated,
278            _ => Code::default(),
279        }
280    }
281}
282
283impl std::convert::From<Code> for String {
284    fn from(value: Code) -> String {
285        value.name().to_string()
286    }
287}
288
289impl std::fmt::Display for Code {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        f.write_str(self.name())
292    }
293}
294
295impl std::convert::TryFrom<&str> for Code {
296    type Error = String;
297    fn try_from(value: &str) -> std::result::Result<Code, Self::Error> {
298        match value {
299            "OK" => Ok(Code::Ok),
300            "CANCELLED" => Ok(Code::Cancelled),
301            "UNKNOWN" => Ok(Code::Unknown),
302            "INVALID_ARGUMENT" => Ok(Code::InvalidArgument),
303            "DEADLINE_EXCEEDED" => Ok(Code::DeadlineExceeded),
304            "NOT_FOUND" => Ok(Code::NotFound),
305            "ALREADY_EXISTS" => Ok(Code::AlreadyExists),
306            "PERMISSION_DENIED" => Ok(Code::PermissionDenied),
307            "RESOURCE_EXHAUSTED" => Ok(Code::ResourceExhausted),
308            "FAILED_PRECONDITION" => Ok(Code::FailedPrecondition),
309            "ABORTED" => Ok(Code::Aborted),
310            "OUT_OF_RANGE" => Ok(Code::OutOfRange),
311            "UNIMPLEMENTED" => Ok(Code::Unimplemented),
312            "INTERNAL" => Ok(Code::Internal),
313            "UNAVAILABLE" => Ok(Code::Unavailable),
314            "DATA_LOSS" => Ok(Code::DataLoss),
315            "UNAUTHENTICATED" => Ok(Code::Unauthenticated),
316            _ => Err(format!("unknown status code value {value}")),
317        }
318    }
319}
320
321impl Serialize for Code {
322    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
323    where
324        S: serde::Serializer,
325    {
326        serializer.serialize_i32(*self as i32)
327    }
328}
329
330impl<'de> Deserialize<'de> for Code {
331    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
332    where
333        D: serde::Deserializer<'de>,
334    {
335        i32::deserialize(deserializer).map(Code::from)
336    }
337}
338
339/// A helper class to deserialized wrapped Status messages.
340#[derive(Clone, Debug, Deserialize)]
341struct ErrorWrapper {
342    error: WrapperStatus,
343}
344
345#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
346#[serde(default)]
347#[non_exhaustive]
348struct WrapperStatus {
349    pub code: i32,
350    pub message: String,
351    pub status: Option<String>,
352    pub details: Vec<StatusDetails>,
353}
354
355impl TryFrom<&bytes::Bytes> for Status {
356    type Error = Error;
357
358    fn try_from(value: &bytes::Bytes) -> Result<Self, Self::Error> {
359        let wrapper = serde_json::from_slice::<ErrorWrapper>(value)
360            .map(|w| w.error)
361            .map_err(Error::deser)?;
362        let code = match wrapper.status.as_deref().map(Code::try_from) {
363            Some(Ok(code)) => code,
364            Some(Err(_)) | None => Code::Unknown,
365        };
366        Ok(Status {
367            code,
368            message: wrapper.message,
369            details: wrapper.details,
370        })
371    }
372}
373
374impl From<google_cloud_rpc::model::Status> for Status {
375    fn from(value: google_cloud_rpc::model::Status) -> Self {
376        Self {
377            code: value.code.into(),
378            message: value.message,
379            details: value.details.into_iter().map(StatusDetails::from).collect(),
380        }
381    }
382}
383
384impl From<&google_cloud_rpc::model::Status> for Status {
385    fn from(value: &google_cloud_rpc::model::Status) -> Self {
386        Self {
387            code: value.code.into(),
388            message: value.message.clone(),
389            details: value.details.iter().map(StatusDetails::from).collect(),
390        }
391    }
392}
393
394/// The type of details associated with [Status].
395///
396/// Google cloud RPCs often return a detailed error description. This details
397/// can be used to better understand the root cause of the problem.
398#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
399#[serde(rename_all = "camelCase")]
400#[non_exhaustive]
401#[serde(tag = "@type")]
402/// Detailed information about the error.
403pub enum StatusDetails {
404    /// Describes violations in a client request.
405    ///
406    /// See [BadRequest][google_cloud_rpc::model::BadRequest] for more information.
407    #[serde(rename = "type.googleapis.com/google.rpc.BadRequest")]
408    BadRequest(google_cloud_rpc::model::BadRequest),
409
410    /// Describes additional debugging info.
411    ///
412    /// See [DebugInfo][google_cloud_rpc::model::DebugInfo] for more information.
413    #[serde(rename = "type.googleapis.com/google.rpc.DebugInfo")]
414    DebugInfo(google_cloud_rpc::model::DebugInfo),
415
416    /// Describes the cause of the error with structured details.
417    ///
418    /// See [ErrorInfo][google_cloud_rpc::model::ErrorInfo] for more information.
419    #[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
420    ErrorInfo(google_cloud_rpc::model::ErrorInfo),
421
422    /// Provides links to documentation or for performing an out of band action.
423    ///
424    /// See [Help][google_cloud_rpc::model::Help] for more information.
425    #[serde(rename = "type.googleapis.com/google.rpc.Help")]
426    Help(google_cloud_rpc::model::Help),
427
428    /// Provides a localized error message that is safe to return to the user.
429    ///
430    /// See [LocalizedMessage][google_cloud_rpc::model::LocalizedMessage] for more information.
431    #[serde(rename = "type.googleapis.com/google.rpc.LocalizedMessage")]
432    LocalizedMessage(google_cloud_rpc::model::LocalizedMessage),
433
434    /// Describes what preconditions have failed.
435    ///
436    /// See [PreconditionFailure][google_cloud_rpc::model::PreconditionFailure] for more information.
437    #[serde(rename = "type.googleapis.com/google.rpc.PreconditionFailure")]
438    PreconditionFailure(google_cloud_rpc::model::PreconditionFailure),
439
440    /// Describes a single quota violation.
441    ///
442    /// See [QuotaFailure][google_cloud_rpc::model::QuotaFailure] for more information.
443    #[serde(rename = "type.googleapis.com/google.rpc.QuotaFailure")]
444    QuotaFailure(google_cloud_rpc::model::QuotaFailure),
445
446    /// Contains metadata about the request that clients can attach when filing a bug.
447    ///
448    /// See [RequestInfo][google_cloud_rpc::model::RequestInfo] for more information.
449    #[serde(rename = "type.googleapis.com/google.rpc.RequestInfo")]
450    RequestInfo(google_cloud_rpc::model::RequestInfo),
451
452    /// Describes the resource that is being accessed.
453    ///
454    /// See [ResourceInfo][google_cloud_rpc::model::ResourceInfo] for more information.
455    #[serde(rename = "type.googleapis.com/google.rpc.ResourceInfo")]
456    ResourceInfo(google_cloud_rpc::model::ResourceInfo),
457
458    /// Describes when the clients can retry a failed request.
459    ///
460    /// See [RetryInfo][google_cloud_rpc::model::RetryInfo] for more information.
461    #[serde(rename = "type.googleapis.com/google.rpc.RetryInfo")]
462    RetryInfo(google_cloud_rpc::model::RetryInfo),
463
464    /// Other details (represented as Any).
465    #[serde(untagged)]
466    Other(wkt::Any),
467}
468
469impl From<wkt::Any> for StatusDetails {
470    fn from(value: wkt::Any) -> Self {
471        macro_rules! try_convert {
472            ($($variant:ident),*) => {
473                $(
474                    if let Ok(v) = value.to_msg::<google_cloud_rpc::model::$variant>() {
475                        return StatusDetails::$variant(v);
476                    }
477                )*
478            };
479        }
480
481        try_convert!(
482            BadRequest,
483            DebugInfo,
484            ErrorInfo,
485            Help,
486            LocalizedMessage,
487            PreconditionFailure,
488            QuotaFailure,
489            RequestInfo,
490            ResourceInfo,
491            RetryInfo
492        );
493
494        StatusDetails::Other(value)
495    }
496}
497
498impl From<&wkt::Any> for StatusDetails {
499    fn from(value: &wkt::Any) -> Self {
500        macro_rules! try_convert {
501            ($($variant:ident),*) => {
502                $(
503                    if let Ok(v) = value.to_msg::<google_cloud_rpc::model::$variant>() {
504                        return StatusDetails::$variant(v);
505                    }
506                )*
507            };
508        }
509
510        try_convert!(
511            BadRequest,
512            DebugInfo,
513            ErrorInfo,
514            Help,
515            LocalizedMessage,
516            PreconditionFailure,
517            QuotaFailure,
518            RequestInfo,
519            ResourceInfo,
520            RetryInfo
521        );
522
523        StatusDetails::Other(value.clone())
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530    use anyhow::Result;
531    use google_cloud_rpc::model::DebugInfo;
532    use google_cloud_rpc::model::ErrorInfo;
533    use google_cloud_rpc::model::LocalizedMessage;
534    use google_cloud_rpc::model::RequestInfo;
535    use google_cloud_rpc::model::ResourceInfo;
536    use google_cloud_rpc::model::RetryInfo;
537    use google_cloud_rpc::model::{BadRequest, bad_request};
538    use google_cloud_rpc::model::{Help, help};
539    use google_cloud_rpc::model::{PreconditionFailure, precondition_failure};
540    use google_cloud_rpc::model::{QuotaFailure, quota_failure};
541    use serde_json::json;
542    use test_case::test_case;
543
544    #[test]
545    fn status_basic_setters() {
546        let got = Status::default()
547            .set_code(Code::Unimplemented)
548            .set_message("test-message");
549        let want = Status {
550            code: Code::Unimplemented,
551            message: "test-message".into(),
552            ..Default::default()
553        };
554        assert_eq!(got, want);
555
556        let got = Status::default()
557            .set_code(Code::Unimplemented as i32)
558            .set_message("test-message");
559        let want = Status {
560            code: Code::Unimplemented,
561            message: "test-message".into(),
562            ..Default::default()
563        };
564        assert_eq!(got, want);
565    }
566
567    #[test]
568    fn status_detail_setter() -> Result<()> {
569        let d0 = StatusDetails::ErrorInfo(ErrorInfo::new().set_reason("test-reason"));
570        let d1 =
571            StatusDetails::Help(Help::new().set_links([help::Link::new().set_url("test-url")]));
572        let want = Status {
573            details: vec![d0.clone(), d1.clone()],
574            ..Default::default()
575        };
576
577        let got = Status::default().set_details([d0, d1]);
578        assert_eq!(got, want);
579
580        let a0 = wkt::Any::from_msg(&ErrorInfo::new().set_reason("test-reason"))?;
581        let a1 =
582            wkt::Any::from_msg(&Help::new().set_links([help::Link::new().set_url("test-url")]))?;
583        let got = Status::default().set_details(&[a0, a1]);
584        assert_eq!(got, want);
585
586        Ok(())
587    }
588
589    #[test]
590    fn serialization_all_variants() {
591        let status = Status {
592            code: Code::Unimplemented,
593            message: "test".to_string(),
594
595            details: vec![
596                StatusDetails::BadRequest(BadRequest::default().set_field_violations(vec![
597                        bad_request::FieldViolation::default()
598                            .set_field("field")
599                            .set_description("desc"),
600                    ])),
601                StatusDetails::DebugInfo(
602                    DebugInfo::default()
603                        .set_stack_entries(vec!["stack".to_string()])
604                        .set_detail("detail"),
605                ),
606                StatusDetails::ErrorInfo(
607                    ErrorInfo::default()
608                        .set_reason("reason")
609                        .set_domain("domain")
610                        .set_metadata([("", "")].into_iter().take(0)),
611                ),
612                StatusDetails::Help(Help::default().set_links(vec![
613                    help::Link::default().set_description("desc").set_url("url"),
614                ])),
615                StatusDetails::LocalizedMessage(
616                    LocalizedMessage::default()
617                        .set_locale("locale")
618                        .set_message("message"),
619                ),
620                StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
621                    vec![
622                            precondition_failure::Violation::default()
623                                .set_type("type")
624                                .set_subject("subject")
625                                .set_description("desc"),
626                        ],
627                )),
628                StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
629                    vec![quota_failure::Violation::default()
630                        .set_subject( "subject")
631                        .set_description( "desc")
632                    ],
633                )),
634                StatusDetails::RequestInfo(
635                    RequestInfo::default()
636                        .set_request_id("id")
637                        .set_serving_data("data"),
638                ),
639                StatusDetails::ResourceInfo(
640                    ResourceInfo::default()
641                        .set_resource_type("type")
642                        .set_resource_name("name")
643                        .set_owner("owner")
644                        .set_description("desc"),
645                ),
646                StatusDetails::RetryInfo(
647                    RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
648                ),
649            ],
650        };
651        let got = serde_json::to_value(&status).unwrap();
652        let want = json!({
653            "code": Code::Unimplemented,
654            "message": "test",
655            "details": [
656                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
657                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
658                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
659                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
660                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
661                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
662                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
663                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
664                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
665                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
666            ]
667        });
668        assert_eq!(got, want);
669    }
670
671    #[test]
672    fn deserialization_all_variants() {
673        let json = json!({
674            "code": Code::Unknown as i32,
675            "message": "test",
676            "details": [
677                {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
678                {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
679                {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
680                {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
681                {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
682                {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
683                {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
684                {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
685                {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
686                {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
687            ]
688        });
689        let got: Status = serde_json::from_value(json).unwrap();
690        let want = Status {
691            code: Code::Unknown,
692            message: "test".to_string(),
693            details: vec![
694                StatusDetails::BadRequest(BadRequest::default().set_field_violations(
695                    vec![bad_request::FieldViolation::default()
696                        .set_field( "field" )
697                        .set_description( "desc" )
698                    ],
699                )),
700                StatusDetails::DebugInfo(
701                    DebugInfo::default()
702                        .set_stack_entries(vec!["stack".to_string()])
703                        .set_detail("detail"),
704                ),
705                StatusDetails::ErrorInfo(
706                    ErrorInfo::default()
707                        .set_reason("reason")
708                        .set_domain("domain"),
709                ),
710                StatusDetails::Help(Help::default().set_links(vec![
711                    help::Link::default().set_description("desc").set_url("url"),
712                ])),
713                StatusDetails::LocalizedMessage(
714                    LocalizedMessage::default()
715                        .set_locale("locale")
716                        .set_message("message"),
717                ),
718                StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
719                    vec![precondition_failure::Violation::default()
720                        .set_type( "type" )
721                        .set_subject( "subject" )
722                        .set_description( "desc" )
723                    ],
724                )),
725                StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
726                    vec![quota_failure::Violation::default()
727                        .set_subject( "subject")
728                        .set_description( "desc")
729                    ],
730                )),
731                StatusDetails::RequestInfo(
732                    RequestInfo::default()
733                        .set_request_id("id")
734                        .set_serving_data("data"),
735                ),
736                StatusDetails::ResourceInfo(
737                    ResourceInfo::default()
738                        .set_resource_type("type")
739                        .set_resource_name("name")
740                        .set_owner("owner")
741                        .set_description("desc"),
742                ),
743                StatusDetails::RetryInfo(
744                    RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
745                ),
746            ],
747        };
748        assert_eq!(got, want);
749    }
750
751    #[test]
752    fn serialization_other() -> Result<()> {
753        const TIME: &str = "2025-05-27T10:00:00Z";
754        let timestamp = wkt::Timestamp::try_from(TIME)?;
755        let any = wkt::Any::from_msg(&timestamp)?;
756        let input = Status {
757            code: Code::Unknown,
758            message: "test".to_string(),
759            details: vec![StatusDetails::Other(any)],
760        };
761        let got = serde_json::to_value(&input)?;
762        let want = json!({
763            "code": Code::Unknown as i32,
764            "message": "test",
765            "details": [
766                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
767            ]
768        });
769        assert_eq!(got, want);
770        Ok(())
771    }
772
773    #[test]
774    fn deserialization_other() -> Result<()> {
775        const TIME: &str = "2025-05-27T10:00:00Z";
776        let json = json!({
777            "code": Code::Unknown as i32,
778            "message": "test",
779            "details": [
780                {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
781            ]
782        });
783        let timestamp = wkt::Timestamp::try_from(TIME)?;
784        let any = wkt::Any::from_msg(&timestamp)?;
785        let got: Status = serde_json::from_value(json)?;
786        let want = Status {
787            code: Code::Unknown,
788            message: "test".to_string(),
789            details: vec![StatusDetails::Other(any)],
790        };
791        assert_eq!(got, want);
792        Ok(())
793    }
794
795    #[test]
796    fn status_from_rpc_no_details() {
797        let input = google_cloud_rpc::model::Status::default()
798            .set_code(Code::Unavailable as i32)
799            .set_message("try-again");
800        let got = Status::from(&input);
801        assert_eq!(got.code, Code::Unavailable);
802        assert_eq!(got.message, "try-again");
803    }
804
805    #[test_case(
806        BadRequest::default(),
807        StatusDetails::BadRequest(BadRequest::default())
808    )]
809    #[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
810    #[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
811    #[test_case(Help::default(), StatusDetails::Help(Help::default()))]
812    #[test_case(
813        LocalizedMessage::default(),
814        StatusDetails::LocalizedMessage(LocalizedMessage::default())
815    )]
816    #[test_case(
817        PreconditionFailure::default(),
818        StatusDetails::PreconditionFailure(PreconditionFailure::default())
819    )]
820    #[test_case(
821        QuotaFailure::default(),
822        StatusDetails::QuotaFailure(QuotaFailure::default())
823    )]
824    #[test_case(
825        RequestInfo::default(),
826        StatusDetails::RequestInfo(RequestInfo::default())
827    )]
828    #[test_case(
829        ResourceInfo::default(),
830        StatusDetails::ResourceInfo(ResourceInfo::default())
831    )]
832    #[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
833    fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
834    where
835        T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
836    {
837        let input = google_cloud_rpc::model::Status::default()
838            .set_code(Code::Unavailable as i32)
839            .set_message("try-again")
840            .set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
841
842        let from_ref = Status::from(&input);
843        let status = Status::from(input);
844        assert_eq!(from_ref, status);
845        assert_eq!(status.code, Code::Unavailable);
846        assert_eq!(status.message, "try-again");
847
848        let got = status.details.first();
849        assert_eq!(got, Some(&want));
850    }
851
852    #[test]
853    fn status_from_rpc_unknown_details() {
854        let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
855        let input = google_cloud_rpc::model::Status::default()
856            .set_code(Code::Unavailable as i32)
857            .set_message("try-again")
858            .set_details(vec![any.clone()]);
859        let from_ref = Status::from(&input);
860        let got = Status::from(input);
861        assert_eq!(from_ref, got);
862        assert_eq!(got.code, Code::Unavailable);
863        assert_eq!(got.message, "try-again");
864
865        let got = got.details.first();
866        let want = StatusDetails::Other(any);
867        assert_eq!(got, Some(&want));
868    }
869
870    // This is a sample string received from production. It is useful to
871    // validate the serialization helpers.
872    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";
873    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";
874
875    // The corresponding status message.
876    fn sample_status() -> Status {
877        Status {
878            code: Code::InvalidArgument,
879            message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
880                .into(),
881            details: [].into(),
882        }
883    }
884
885    #[test]
886    fn deserialize_status() {
887        let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
888        let want = ErrorWrapper {
889            error: WrapperStatus {
890                code: 400,
891                status: Some("INVALID_ARGUMENT".to_string()),
892                message:
893                    "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
894                        .into(),
895                details: [].into(),
896            },
897        };
898        assert_eq!(got.error, want.error);
899    }
900
901    #[test]
902    fn try_from_bytes() -> Result<()> {
903        let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
904        let want = sample_status();
905        assert_eq!(got, want);
906
907        let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
908        let err = got.unwrap_err();
909        assert!(err.is_deserialization(), "{err:?}");
910
911        let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
912        let err = got.unwrap_err();
913        assert!(err.is_deserialization(), "{err:?}");
914
915        let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
916        assert_eq!(got.code, Code::Unknown);
917        Ok(())
918    }
919
920    #[test]
921    fn code_to_string() {
922        let got = String::from(Code::AlreadyExists);
923        let want = "ALREADY_EXISTS";
924        assert_eq!(got, want);
925    }
926
927    #[test_case("OK")]
928    #[test_case("CANCELLED")]
929    #[test_case("UNKNOWN")]
930    #[test_case("INVALID_ARGUMENT")]
931    #[test_case("DEADLINE_EXCEEDED")]
932    #[test_case("NOT_FOUND")]
933    #[test_case("ALREADY_EXISTS")]
934    #[test_case("PERMISSION_DENIED")]
935    #[test_case("RESOURCE_EXHAUSTED")]
936    #[test_case("FAILED_PRECONDITION")]
937    #[test_case("ABORTED")]
938    #[test_case("OUT_OF_RANGE")]
939    #[test_case("UNIMPLEMENTED")]
940    #[test_case("INTERNAL")]
941    #[test_case("UNAVAILABLE")]
942    #[test_case("DATA_LOSS")]
943    #[test_case("UNAUTHENTICATED")]
944    fn code_roundtrip(input: &str) -> Result<()> {
945        let code = Code::try_from(input).unwrap();
946        let output = String::from(code);
947        assert_eq!(output.as_str(), input.to_string());
948        assert_eq!(&format!("{code}"), input);
949        assert_eq!(code.name(), input);
950        Ok(())
951    }
952
953    #[test_case("OK")]
954    #[test_case("CANCELLED")]
955    #[test_case("UNKNOWN")]
956    #[test_case("INVALID_ARGUMENT")]
957    #[test_case("DEADLINE_EXCEEDED")]
958    #[test_case("NOT_FOUND")]
959    #[test_case("ALREADY_EXISTS")]
960    #[test_case("PERMISSION_DENIED")]
961    #[test_case("RESOURCE_EXHAUSTED")]
962    #[test_case("FAILED_PRECONDITION")]
963    #[test_case("ABORTED")]
964    #[test_case("OUT_OF_RANGE")]
965    #[test_case("UNIMPLEMENTED")]
966    #[test_case("INTERNAL")]
967    #[test_case("UNAVAILABLE")]
968    #[test_case("DATA_LOSS")]
969    #[test_case("UNAUTHENTICATED")]
970    fn code_serialize_roundtrip(input: &str) -> Result<()> {
971        let want = Code::try_from(input).unwrap();
972        let serialized = serde_json::to_value(want)?;
973        let got = serde_json::from_value::<Code>(serialized)?;
974        assert_eq!(got, want);
975        Ok(())
976    }
977
978    #[test]
979    fn code_try_from_string_error() {
980        let err = Code::try_from("INVALID-NOT-A-CODE");
981        assert!(
982            matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
983            "expected error in try_from, got {err:?}"
984        );
985    }
986
987    #[test]
988    fn code_deserialize_invalid_type() {
989        let input = json!({"k": "v"});
990        let err = serde_json::from_value::<Code>(input);
991        assert!(err.is_err(), "expected an error, got {err:?}");
992    }
993
994    #[test]
995    fn code_deserialize_unknown() -> Result<()> {
996        let input = json!(-17);
997        let code = serde_json::from_value::<Code>(input)?;
998        assert_eq!(code, Code::Unknown);
999        Ok(())
1000    }
1001}