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