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