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, Default, PartialEq)]
73#[non_exhaustive]
74pub enum Code {
75 Ok = 0,
79
80 Cancelled = 1,
84
85 #[default]
93 Unknown = 2,
94
95 InvalidArgument = 3,
102
103 DeadlineExceeded = 4,
111
112 NotFound = 5,
122
123 AlreadyExists = 6,
128
129 PermissionDenied = 7,
140
141 ResourceExhausted = 8,
146
147 FailedPrecondition = 9,
166
167 Aborted = 10,
177
178 OutOfRange = 11,
196
197 Unimplemented = 12,
202
203 Internal = 13,
209
210 Unavailable = 14,
220
221 DataLoss = 15,
225
226 Unauthenticated = 16,
231}
232
233impl Code {
234 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#[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#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
399#[serde(rename_all = "camelCase")]
400#[non_exhaustive]
401#[serde(tag = "@type")]
402pub enum StatusDetails {
404 #[serde(rename = "type.googleapis.com/google.rpc.BadRequest")]
408 BadRequest(google_cloud_rpc::model::BadRequest),
409
410 #[serde(rename = "type.googleapis.com/google.rpc.DebugInfo")]
414 DebugInfo(google_cloud_rpc::model::DebugInfo),
415
416 #[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
420 ErrorInfo(google_cloud_rpc::model::ErrorInfo),
421
422 #[serde(rename = "type.googleapis.com/google.rpc.Help")]
426 Help(google_cloud_rpc::model::Help),
427
428 #[serde(rename = "type.googleapis.com/google.rpc.LocalizedMessage")]
432 LocalizedMessage(google_cloud_rpc::model::LocalizedMessage),
433
434 #[serde(rename = "type.googleapis.com/google.rpc.PreconditionFailure")]
438 PreconditionFailure(google_cloud_rpc::model::PreconditionFailure),
439
440 #[serde(rename = "type.googleapis.com/google.rpc.QuotaFailure")]
444 QuotaFailure(google_cloud_rpc::model::QuotaFailure),
445
446 #[serde(rename = "type.googleapis.com/google.rpc.RequestInfo")]
450 RequestInfo(google_cloud_rpc::model::RequestInfo),
451
452 #[serde(rename = "type.googleapis.com/google.rpc.ResourceInfo")]
456 ResourceInfo(google_cloud_rpc::model::ResourceInfo),
457
458 #[serde(rename = "type.googleapis.com/google.rpc.RetryInfo")]
462 RetryInfo(google_cloud_rpc::model::RetryInfo),
463
464 #[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(×tamp)?;
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(×tamp)?;
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 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 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}