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();
621 let want = json!({
622 "code": Code::Unimplemented,
623 "message": "test",
624 "details": [
625 {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
626 {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
627 {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
628 {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
629 {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
630 {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
631 {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
632 {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
633 {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
634 {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
635 ]
636 });
637 assert_eq!(got, want);
638 }
639
640 #[test]
641 fn deserialization_all_variants() {
642 let json = json!({
643 "code": Code::Unknown as i32,
644 "message": "test",
645 "details": [
646 {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
647 {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
648 {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
649 {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
650 {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
651 {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
652 {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
653 {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
654 {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
655 {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
656 ]
657 });
658 let got: Status = serde_json::from_value(json).unwrap();
659 let want = Status {
660 code: Code::Unknown,
661 message: "test".to_string(),
662 details: vec![
663 StatusDetails::BadRequest(BadRequest::default().set_field_violations(
664 vec![rpc::model::bad_request::FieldViolation::default()
665 .set_field( "field" )
666 .set_description( "desc" )
667 ],
668 )),
669 StatusDetails::DebugInfo(
670 DebugInfo::default()
671 .set_stack_entries(vec!["stack".to_string()])
672 .set_detail("detail"),
673 ),
674 StatusDetails::ErrorInfo(
675 ErrorInfo::default()
676 .set_reason("reason")
677 .set_domain("domain"),
678 ),
679 StatusDetails::Help(Help::default().set_links(vec![
680 rpc::model::help::Link::default().set_description("desc").set_url("url"),
681 ])),
682 StatusDetails::LocalizedMessage(
683 LocalizedMessage::default()
684 .set_locale("locale")
685 .set_message("message"),
686 ),
687 StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
688 vec![rpc::model::precondition_failure::Violation::default()
689 .set_type( "type" )
690 .set_subject( "subject" )
691 .set_description( "desc" )
692 ],
693 )),
694 StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
695 vec![rpc::model::quota_failure::Violation::default()
696 .set_subject( "subject")
697 .set_description( "desc")
698 ],
699 )),
700 StatusDetails::RequestInfo(
701 RequestInfo::default()
702 .set_request_id("id")
703 .set_serving_data("data"),
704 ),
705 StatusDetails::ResourceInfo(
706 ResourceInfo::default()
707 .set_resource_type("type")
708 .set_resource_name("name")
709 .set_owner("owner")
710 .set_description("desc"),
711 ),
712 StatusDetails::RetryInfo(
713 RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
714 ),
715 ],
716 };
717 assert_eq!(got, want);
718 }
719
720 #[test]
721 fn serialization_other() -> Result<()> {
722 const TIME: &str = "2025-05-27T10:00:00Z";
723 let timestamp = wkt::Timestamp::try_from(TIME)?;
724 let any = wkt::Any::from_msg(×tamp)?;
725 let input = Status {
726 code: Code::Unknown,
727 message: "test".to_string(),
728 details: vec![StatusDetails::Other(any)],
729 };
730 let got = serde_json::to_value(&input)?;
731 let want = json!({
732 "code": Code::Unknown as i32,
733 "message": "test",
734 "details": [
735 {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
736 ]
737 });
738 assert_eq!(got, want);
739 Ok(())
740 }
741
742 #[test]
743 fn deserialization_other() -> Result<()> {
744 const TIME: &str = "2025-05-27T10:00:00Z";
745 let json = json!({
746 "code": Code::Unknown as i32,
747 "message": "test",
748 "details": [
749 {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
750 ]
751 });
752 let timestamp = wkt::Timestamp::try_from(TIME)?;
753 let any = wkt::Any::from_msg(×tamp)?;
754 let got: Status = serde_json::from_value(json)?;
755 let want = Status {
756 code: Code::Unknown,
757 message: "test".to_string(),
758 details: vec![StatusDetails::Other(any)],
759 };
760 assert_eq!(got, want);
761 Ok(())
762 }
763
764 #[test]
765 fn status_from_rpc_no_details() {
766 let input = rpc::model::Status::default()
767 .set_code(Code::Unavailable as i32)
768 .set_message("try-again");
769 let got = Status::from(&input);
770 assert_eq!(got.code, Code::Unavailable);
771 assert_eq!(got.message, "try-again");
772 }
773
774 #[test_case(
775 BadRequest::default(),
776 StatusDetails::BadRequest(BadRequest::default())
777 )]
778 #[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
779 #[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
780 #[test_case(Help::default(), StatusDetails::Help(Help::default()))]
781 #[test_case(
782 LocalizedMessage::default(),
783 StatusDetails::LocalizedMessage(LocalizedMessage::default())
784 )]
785 #[test_case(
786 PreconditionFailure::default(),
787 StatusDetails::PreconditionFailure(PreconditionFailure::default())
788 )]
789 #[test_case(
790 QuotaFailure::default(),
791 StatusDetails::QuotaFailure(QuotaFailure::default())
792 )]
793 #[test_case(
794 RequestInfo::default(),
795 StatusDetails::RequestInfo(RequestInfo::default())
796 )]
797 #[test_case(
798 ResourceInfo::default(),
799 StatusDetails::ResourceInfo(ResourceInfo::default())
800 )]
801 #[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
802 fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
803 where
804 T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
805 {
806 let input = rpc::model::Status::default()
807 .set_code(Code::Unavailable as i32)
808 .set_message("try-again")
809 .set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
810
811 let from_ref = Status::from(&input);
812 let status = Status::from(input);
813 assert_eq!(from_ref, status);
814 assert_eq!(status.code, Code::Unavailable);
815 assert_eq!(status.message, "try-again");
816
817 let got = status.details.first();
818 assert_eq!(got, Some(&want));
819 }
820
821 #[test]
822 fn status_from_rpc_unknown_details() {
823 let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
824 let input = rpc::model::Status::default()
825 .set_code(Code::Unavailable as i32)
826 .set_message("try-again")
827 .set_details(vec![any.clone()]);
828 let from_ref = Status::from(&input);
829 let got = Status::from(input);
830 assert_eq!(from_ref, got);
831 assert_eq!(got.code, Code::Unavailable);
832 assert_eq!(got.message, "try-again");
833
834 let got = got.details.first();
835 let want = StatusDetails::Other(any);
836 assert_eq!(got, Some(&want));
837 }
838
839 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";
842 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";
843
844 fn sample_status() -> Status {
846 Status {
847 code: Code::InvalidArgument,
848 message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
849 .into(),
850 details: [].into(),
851 }
852 }
853
854 #[test]
855 fn deserialize_status() {
856 let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
857 let want = ErrorWrapper {
858 error: WrapperStatus {
859 code: 400,
860 status: Some("INVALID_ARGUMENT".to_string()),
861 message:
862 "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
863 .into(),
864 details: [].into(),
865 },
866 };
867 assert_eq!(got.error, want.error);
868 }
869
870 #[test]
871 fn try_from_bytes() -> Result<()> {
872 let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
873 let want = sample_status();
874 assert_eq!(got, want);
875
876 let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
877 let err = got.unwrap_err();
878 assert!(err.is_deserialization(), "{err:?}");
879
880 let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
881 let err = got.unwrap_err();
882 assert!(err.is_deserialization(), "{err:?}");
883
884 let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
885 assert_eq!(got.code, Code::Unknown);
886 Ok(())
887 }
888
889 #[test]
890 fn code_to_string() {
891 let got = String::from(Code::AlreadyExists);
892 let want = "ALREADY_EXISTS";
893 assert_eq!(got, want);
894 }
895
896 #[test_case("OK")]
897 #[test_case("CANCELLED")]
898 #[test_case("UNKNOWN")]
899 #[test_case("INVALID_ARGUMENT")]
900 #[test_case("DEADLINE_EXCEEDED")]
901 #[test_case("NOT_FOUND")]
902 #[test_case("ALREADY_EXISTS")]
903 #[test_case("PERMISSION_DENIED")]
904 #[test_case("RESOURCE_EXHAUSTED")]
905 #[test_case("FAILED_PRECONDITION")]
906 #[test_case("ABORTED")]
907 #[test_case("OUT_OF_RANGE")]
908 #[test_case("UNIMPLEMENTED")]
909 #[test_case("INTERNAL")]
910 #[test_case("UNAVAILABLE")]
911 #[test_case("DATA_LOSS")]
912 #[test_case("UNAUTHENTICATED")]
913 fn code_roundtrip(input: &str) -> Result<()> {
914 let code = Code::try_from(input).unwrap();
915 let output = String::from(code);
916 assert_eq!(output.as_str(), input.to_string());
917 assert_eq!(&format!("{code}"), input);
918 assert_eq!(code.name(), input);
919 Ok(())
920 }
921
922 #[test_case("OK")]
923 #[test_case("CANCELLED")]
924 #[test_case("UNKNOWN")]
925 #[test_case("INVALID_ARGUMENT")]
926 #[test_case("DEADLINE_EXCEEDED")]
927 #[test_case("NOT_FOUND")]
928 #[test_case("ALREADY_EXISTS")]
929 #[test_case("PERMISSION_DENIED")]
930 #[test_case("RESOURCE_EXHAUSTED")]
931 #[test_case("FAILED_PRECONDITION")]
932 #[test_case("ABORTED")]
933 #[test_case("OUT_OF_RANGE")]
934 #[test_case("UNIMPLEMENTED")]
935 #[test_case("INTERNAL")]
936 #[test_case("UNAVAILABLE")]
937 #[test_case("DATA_LOSS")]
938 #[test_case("UNAUTHENTICATED")]
939 fn code_serialize_roundtrip(input: &str) -> Result<()> {
940 let want = Code::try_from(input).unwrap();
941 let serialized = serde_json::to_value(want)?;
942 let got = serde_json::from_value::<Code>(serialized)?;
943 assert_eq!(got, want);
944 Ok(())
945 }
946
947 #[test]
948 fn code_try_from_string_error() {
949 let err = Code::try_from("INVALID-NOT-A-CODE");
950 assert!(
951 matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
952 "expected error in try_from, got {err:?}"
953 );
954 }
955
956 #[test]
957 fn code_deserialize_invalid_type() {
958 let input = json!({"k": "v"});
959 let err = serde_json::from_value::<Code>(input);
960 assert!(err.is_err(), "expected an error, got {err:?}");
961 }
962
963 #[test]
964 fn code_deserialize_unknown() -> Result<()> {
965 let input = json!(-17);
966 let code = serde_json::from_value::<Code>(input)?;
967 assert_eq!(code, Code::Unknown);
968 Ok(())
969 }
970}