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<rpc::model::Status> for Status {
374 fn from(value: 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<&rpc::model::Status> for Status {
384 fn from(value: &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(rpc::model::BadRequest),
404 #[serde(rename = "type.googleapis.com/google.rpc.DebugInfo")]
405 DebugInfo(rpc::model::DebugInfo),
406 #[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
407 ErrorInfo(rpc::model::ErrorInfo),
408 #[serde(rename = "type.googleapis.com/google.rpc.Help")]
409 Help(rpc::model::Help),
410 #[serde(rename = "type.googleapis.com/google.rpc.LocalizedMessage")]
411 LocalizedMessage(rpc::model::LocalizedMessage),
412 #[serde(rename = "type.googleapis.com/google.rpc.PreconditionFailure")]
413 PreconditionFailure(rpc::model::PreconditionFailure),
414 #[serde(rename = "type.googleapis.com/google.rpc.QuotaFailure")]
415 QuotaFailure(rpc::model::QuotaFailure),
416 #[serde(rename = "type.googleapis.com/google.rpc.RequestInfo")]
417 RequestInfo(rpc::model::RequestInfo),
418 #[serde(rename = "type.googleapis.com/google.rpc.ResourceInfo")]
419 ResourceInfo(rpc::model::ResourceInfo),
420 #[serde(rename = "type.googleapis.com/google.rpc.RetryInfo")]
421 RetryInfo(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::<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::<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 rpc::model::BadRequest;
489 use rpc::model::DebugInfo;
490 use rpc::model::ErrorInfo;
491 use rpc::model::Help;
492 use rpc::model::LocalizedMessage;
493 use rpc::model::PreconditionFailure;
494 use rpc::model::QuotaFailure;
495 use rpc::model::RequestInfo;
496 use rpc::model::ResourceInfo;
497 use rpc::model::RetryInfo;
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(rpc::model::ErrorInfo::new().set_reason("test-reason"));
527 let d1 = StatusDetails::Help(
528 rpc::model::Help::new().set_links([rpc::model::help::Link::new().set_url("test-url")]),
529 );
530 let want = Status {
531 details: vec![d0.clone(), d1.clone()],
532 ..Default::default()
533 };
534
535 let got = Status::default().set_details([d0, d1]);
536 assert_eq!(got, want);
537
538 let a0 = wkt::Any::from_msg(&rpc::model::ErrorInfo::new().set_reason("test-reason"))?;
539 let a1 = wkt::Any::from_msg(
540 &rpc::model::Help::new().set_links([rpc::model::help::Link::new().set_url("test-url")]),
541 )?;
542 let got = Status::default().set_details(&[a0, a1]);
543 assert_eq!(got, want);
544
545 Ok(())
546 }
547
548 #[test]
549 fn serialization_all_variants() {
550 let status =
551 Status {
552 code: Code::Unimplemented,
553 message: "test".to_string(),
554
555 details: vec![
556 StatusDetails::BadRequest(BadRequest::default().set_field_violations(
557 vec![rpc::model::bad_request::FieldViolation::default()
558 .set_field("field").set_description("desc")],
559 )),
560 StatusDetails::DebugInfo(
561 DebugInfo::default()
562 .set_stack_entries(vec!["stack".to_string()])
563 .set_detail("detail"),
564 ),
565 StatusDetails::ErrorInfo(
566 ErrorInfo::default()
567 .set_reason("reason")
568 .set_domain("domain")
569 .set_metadata([("", "")].into_iter().take(0)),
570 ),
571 StatusDetails::Help(Help::default().set_links(
572 vec![rpc::model::help::Link::default()
573 .set_description( "desc")
574 .set_url( "url")
575 ],
576 )),
577 StatusDetails::LocalizedMessage(
578 LocalizedMessage::default()
579 .set_locale("locale")
580 .set_message("message"),
581 ),
582 StatusDetails::PreconditionFailure(
583 PreconditionFailure::default().set_violations(vec![
584 rpc::model::precondition_failure::Violation::default()
585 .set_type("type")
586 .set_subject("subject")
587 .set_description("desc"),
588 ]),
589 ),
590 StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
591 vec![rpc::model::quota_failure::Violation::default()
592 .set_subject( "subject")
593 .set_description( "desc")
594 ],
595 )),
596 StatusDetails::RequestInfo(
597 RequestInfo::default()
598 .set_request_id("id")
599 .set_serving_data("data"),
600 ),
601 StatusDetails::ResourceInfo(
602 ResourceInfo::default()
603 .set_resource_type("type")
604 .set_resource_name("name")
605 .set_owner("owner")
606 .set_description("desc"),
607 ),
608 StatusDetails::RetryInfo(
609 RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
610 ),
611 ],
612 };
613 let got = serde_json::to_value(&status).unwrap();
614 let want = json!({
615 "code": Code::Unimplemented,
616 "message": "test",
617 "details": [
618 {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
619 {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
620 {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
621 {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
622 {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
623 {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
624 {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
625 {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
626 {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
627 {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
628 ]
629 });
630 assert_eq!(got, want);
631 }
632
633 #[test]
634 fn deserialization_all_variants() {
635 let json = json!({
636 "code": Code::Unknown as i32,
637 "message": "test",
638 "details": [
639 {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
640 {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
641 {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
642 {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
643 {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
644 {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
645 {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
646 {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
647 {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
648 {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
649 ]
650 });
651 let got: Status = serde_json::from_value(json).unwrap();
652 let want = Status {
653 code: Code::Unknown,
654 message: "test".to_string(),
655 details: vec![
656 StatusDetails::BadRequest(BadRequest::default().set_field_violations(
657 vec![rpc::model::bad_request::FieldViolation::default()
658 .set_field( "field" )
659 .set_description( "desc" )
660 ],
661 )),
662 StatusDetails::DebugInfo(
663 DebugInfo::default()
664 .set_stack_entries(vec!["stack".to_string()])
665 .set_detail("detail"),
666 ),
667 StatusDetails::ErrorInfo(
668 ErrorInfo::default()
669 .set_reason("reason")
670 .set_domain("domain"),
671 ),
672 StatusDetails::Help(Help::default().set_links(vec![
673 rpc::model::help::Link::default().set_description("desc").set_url("url"),
674 ])),
675 StatusDetails::LocalizedMessage(
676 LocalizedMessage::default()
677 .set_locale("locale")
678 .set_message("message"),
679 ),
680 StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
681 vec![rpc::model::precondition_failure::Violation::default()
682 .set_type( "type" )
683 .set_subject( "subject" )
684 .set_description( "desc" )
685 ],
686 )),
687 StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
688 vec![rpc::model::quota_failure::Violation::default()
689 .set_subject( "subject")
690 .set_description( "desc")
691 ],
692 )),
693 StatusDetails::RequestInfo(
694 RequestInfo::default()
695 .set_request_id("id")
696 .set_serving_data("data"),
697 ),
698 StatusDetails::ResourceInfo(
699 ResourceInfo::default()
700 .set_resource_type("type")
701 .set_resource_name("name")
702 .set_owner("owner")
703 .set_description("desc"),
704 ),
705 StatusDetails::RetryInfo(
706 RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
707 ),
708 ],
709 };
710 assert_eq!(got, want);
711 }
712
713 #[test]
714 fn serialization_other() -> Result<()> {
715 const TIME: &str = "2025-05-27T10:00:00Z";
716 let timestamp = wkt::Timestamp::try_from(TIME)?;
717 let any = wkt::Any::from_msg(×tamp)?;
718 let input = Status {
719 code: Code::Unknown,
720 message: "test".to_string(),
721 details: vec![StatusDetails::Other(any)],
722 };
723 let got = serde_json::to_value(&input)?;
724 let want = json!({
725 "code": Code::Unknown as i32,
726 "message": "test",
727 "details": [
728 {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
729 ]
730 });
731 assert_eq!(got, want);
732 Ok(())
733 }
734
735 #[test]
736 fn deserialization_other() -> Result<()> {
737 const TIME: &str = "2025-05-27T10:00:00Z";
738 let json = json!({
739 "code": Code::Unknown as i32,
740 "message": "test",
741 "details": [
742 {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
743 ]
744 });
745 let timestamp = wkt::Timestamp::try_from(TIME)?;
746 let any = wkt::Any::from_msg(×tamp)?;
747 let got: Status = serde_json::from_value(json)?;
748 let want = Status {
749 code: Code::Unknown,
750 message: "test".to_string(),
751 details: vec![StatusDetails::Other(any)],
752 };
753 assert_eq!(got, want);
754 Ok(())
755 }
756
757 #[test]
758 fn status_from_rpc_no_details() {
759 let input = rpc::model::Status::default()
760 .set_code(Code::Unavailable as i32)
761 .set_message("try-again");
762 let got = Status::from(&input);
763 assert_eq!(got.code, Code::Unavailable);
764 assert_eq!(got.message, "try-again");
765 }
766
767 #[test_case(
768 BadRequest::default(),
769 StatusDetails::BadRequest(BadRequest::default())
770 )]
771 #[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
772 #[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
773 #[test_case(Help::default(), StatusDetails::Help(Help::default()))]
774 #[test_case(
775 LocalizedMessage::default(),
776 StatusDetails::LocalizedMessage(LocalizedMessage::default())
777 )]
778 #[test_case(
779 PreconditionFailure::default(),
780 StatusDetails::PreconditionFailure(PreconditionFailure::default())
781 )]
782 #[test_case(
783 QuotaFailure::default(),
784 StatusDetails::QuotaFailure(QuotaFailure::default())
785 )]
786 #[test_case(
787 RequestInfo::default(),
788 StatusDetails::RequestInfo(RequestInfo::default())
789 )]
790 #[test_case(
791 ResourceInfo::default(),
792 StatusDetails::ResourceInfo(ResourceInfo::default())
793 )]
794 #[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
795 fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
796 where
797 T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
798 {
799 let input = rpc::model::Status::default()
800 .set_code(Code::Unavailable as i32)
801 .set_message("try-again")
802 .set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
803
804 let from_ref = Status::from(&input);
805 let status = Status::from(input);
806 assert_eq!(from_ref, status);
807 assert_eq!(status.code, Code::Unavailable);
808 assert_eq!(status.message, "try-again");
809
810 let got = status.details.first();
811 assert_eq!(got, Some(&want));
812 }
813
814 #[test]
815 fn status_from_rpc_unknown_details() {
816 let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
817 let input = rpc::model::Status::default()
818 .set_code(Code::Unavailable as i32)
819 .set_message("try-again")
820 .set_details(vec![any.clone()]);
821 let from_ref = Status::from(&input);
822 let got = Status::from(input);
823 assert_eq!(from_ref, got);
824 assert_eq!(got.code, Code::Unavailable);
825 assert_eq!(got.message, "try-again");
826
827 let got = got.details.first();
828 let want = StatusDetails::Other(any);
829 assert_eq!(got, Some(&want));
830 }
831
832 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";
835 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";
836
837 fn sample_status() -> Status {
839 Status {
840 code: Code::InvalidArgument,
841 message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
842 .into(),
843 details: [].into(),
844 }
845 }
846
847 #[test]
848 fn deserialize_status() {
849 let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
850 let want = ErrorWrapper {
851 error: WrapperStatus {
852 code: 400,
853 status: Some("INVALID_ARGUMENT".to_string()),
854 message:
855 "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
856 .into(),
857 details: [].into(),
858 },
859 };
860 assert_eq!(got.error, want.error);
861 }
862
863 #[test]
864 fn try_from_bytes() -> Result<()> {
865 let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
866 let want = sample_status();
867 assert_eq!(got, want);
868
869 let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
870 let err = got.unwrap_err();
871 assert!(err.is_deserialization(), "{err:?}");
872
873 let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
874 let err = got.unwrap_err();
875 assert!(err.is_deserialization(), "{err:?}");
876
877 let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
878 assert_eq!(got.code, Code::Unknown);
879 Ok(())
880 }
881
882 #[test]
883 fn code_to_string() {
884 let got = String::from(Code::AlreadyExists);
885 let want = "ALREADY_EXISTS";
886 assert_eq!(got, want);
887 }
888
889 #[test_case("OK")]
890 #[test_case("CANCELLED")]
891 #[test_case("UNKNOWN")]
892 #[test_case("INVALID_ARGUMENT")]
893 #[test_case("DEADLINE_EXCEEDED")]
894 #[test_case("NOT_FOUND")]
895 #[test_case("ALREADY_EXISTS")]
896 #[test_case("PERMISSION_DENIED")]
897 #[test_case("RESOURCE_EXHAUSTED")]
898 #[test_case("FAILED_PRECONDITION")]
899 #[test_case("ABORTED")]
900 #[test_case("OUT_OF_RANGE")]
901 #[test_case("UNIMPLEMENTED")]
902 #[test_case("INTERNAL")]
903 #[test_case("UNAVAILABLE")]
904 #[test_case("DATA_LOSS")]
905 #[test_case("UNAUTHENTICATED")]
906 fn code_roundtrip(input: &str) -> Result<()> {
907 let code = Code::try_from(input).unwrap();
908 let output = String::from(code);
909 assert_eq!(output.as_str(), input.to_string());
910 assert_eq!(&format!("{code}"), input);
911 assert_eq!(code.name(), input);
912 Ok(())
913 }
914
915 #[test_case("OK")]
916 #[test_case("CANCELLED")]
917 #[test_case("UNKNOWN")]
918 #[test_case("INVALID_ARGUMENT")]
919 #[test_case("DEADLINE_EXCEEDED")]
920 #[test_case("NOT_FOUND")]
921 #[test_case("ALREADY_EXISTS")]
922 #[test_case("PERMISSION_DENIED")]
923 #[test_case("RESOURCE_EXHAUSTED")]
924 #[test_case("FAILED_PRECONDITION")]
925 #[test_case("ABORTED")]
926 #[test_case("OUT_OF_RANGE")]
927 #[test_case("UNIMPLEMENTED")]
928 #[test_case("INTERNAL")]
929 #[test_case("UNAVAILABLE")]
930 #[test_case("DATA_LOSS")]
931 #[test_case("UNAUTHENTICATED")]
932 fn code_serialize_roundtrip(input: &str) -> Result<()> {
933 let want = Code::try_from(input).unwrap();
934 let serialized = serde_json::to_value(want)?;
935 let got = serde_json::from_value::<Code>(serialized)?;
936 assert_eq!(got, want);
937 Ok(())
938 }
939
940 #[test]
941 fn code_try_from_string_error() {
942 let err = Code::try_from("INVALID-NOT-A-CODE");
943 assert!(
944 matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
945 "expected error in try_from, got {err:?}"
946 );
947 }
948
949 #[test]
950 fn code_deserialize_invalid_type() {
951 let input = json!({"k": "v"});
952 let err = serde_json::from_value::<Code>(input);
953 assert!(err.is_err(), "expected an error, got {err:?}");
954 }
955
956 #[test]
957 fn code_deserialize_unknown() -> Result<()> {
958 let input = json!(-17);
959 let code = serde_json::from_value::<Code>(input)?;
960 assert_eq!(code, Code::Unknown);
961 Ok(())
962 }
963}