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
388#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
393#[serde(rename_all = "camelCase")]
394#[non_exhaustive]
395#[serde(tag = "@type")]
396pub enum StatusDetails {
397 #[serde(rename = "type.googleapis.com/google.rpc.BadRequest")]
398 BadRequest(rpc::model::BadRequest),
399 #[serde(rename = "type.googleapis.com/google.rpc.DebugInfo")]
400 DebugInfo(rpc::model::DebugInfo),
401 #[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
402 ErrorInfo(rpc::model::ErrorInfo),
403 #[serde(rename = "type.googleapis.com/google.rpc.Help")]
404 Help(rpc::model::Help),
405 #[serde(rename = "type.googleapis.com/google.rpc.LocalizedMessage")]
406 LocalizedMessage(rpc::model::LocalizedMessage),
407 #[serde(rename = "type.googleapis.com/google.rpc.PreconditionFailure")]
408 PreconditionFailure(rpc::model::PreconditionFailure),
409 #[serde(rename = "type.googleapis.com/google.rpc.QuotaFailure")]
410 QuotaFailure(rpc::model::QuotaFailure),
411 #[serde(rename = "type.googleapis.com/google.rpc.RequestInfo")]
412 RequestInfo(rpc::model::RequestInfo),
413 #[serde(rename = "type.googleapis.com/google.rpc.ResourceInfo")]
414 ResourceInfo(rpc::model::ResourceInfo),
415 #[serde(rename = "type.googleapis.com/google.rpc.RetryInfo")]
416 RetryInfo(rpc::model::RetryInfo),
417 #[serde(untagged)]
418 Other(wkt::Any),
419}
420
421impl From<wkt::Any> for StatusDetails {
422 fn from(value: wkt::Any) -> Self {
423 macro_rules! try_convert {
424 ($($variant:ident),*) => {
425 $(
426 if let Ok(v) = value.to_msg::<rpc::model::$variant>() {
427 return StatusDetails::$variant(v);
428 }
429 )*
430 };
431 }
432
433 try_convert!(
434 BadRequest,
435 DebugInfo,
436 ErrorInfo,
437 Help,
438 LocalizedMessage,
439 PreconditionFailure,
440 QuotaFailure,
441 RequestInfo,
442 ResourceInfo,
443 RetryInfo
444 );
445
446 StatusDetails::Other(value)
447 }
448}
449
450#[cfg(test)]
451mod test {
452 use super::*;
453 use anyhow::Result;
454 use rpc::model::BadRequest;
455 use rpc::model::DebugInfo;
456 use rpc::model::ErrorInfo;
457 use rpc::model::Help;
458 use rpc::model::LocalizedMessage;
459 use rpc::model::PreconditionFailure;
460 use rpc::model::QuotaFailure;
461 use rpc::model::RequestInfo;
462 use rpc::model::ResourceInfo;
463 use rpc::model::RetryInfo;
464 use serde_json::json;
465 use test_case::test_case;
466
467 #[test]
468 fn status_basic_setters() {
469 let got = Status::default()
470 .set_code(Code::Unimplemented)
471 .set_message("test-message");
472 let want = Status {
473 code: Code::Unimplemented,
474 message: "test-message".into(),
475 ..Default::default()
476 };
477 assert_eq!(got, want);
478
479 let got = Status::default()
480 .set_code(Code::Unimplemented as i32)
481 .set_message("test-message");
482 let want = Status {
483 code: Code::Unimplemented,
484 message: "test-message".into(),
485 ..Default::default()
486 };
487 assert_eq!(got, want);
488 }
489
490 #[test]
491 fn status_detail_setter() -> Result<()> {
492 let d0 = StatusDetails::ErrorInfo(rpc::model::ErrorInfo::new().set_reason("test-reason"));
493 let d1 = StatusDetails::Help(
494 rpc::model::Help::new().set_links([rpc::model::help::Link::new().set_url("test-url")]),
495 );
496 let want = Status {
497 details: vec![d0.clone(), d1.clone()],
498 ..Default::default()
499 };
500
501 let got = Status::default().set_details([d0, d1]);
502 assert_eq!(got, want);
503
504 let a0 = wkt::Any::from_msg(&rpc::model::ErrorInfo::new().set_reason("test-reason"))?;
505 let a1 = wkt::Any::from_msg(
506 &rpc::model::Help::new().set_links([rpc::model::help::Link::new().set_url("test-url")]),
507 )?;
508 let got = Status::default().set_details([a0, a1]);
509 assert_eq!(got, want);
510
511 Ok(())
512 }
513
514 #[test]
515 fn serialization_all_variants() {
516 let status =
517 Status {
518 code: Code::Unimplemented,
519 message: "test".to_string(),
520
521 details: vec![
522 StatusDetails::BadRequest(BadRequest::default().set_field_violations(
523 vec![rpc::model::bad_request::FieldViolation::default()
524 .set_field("field").set_description("desc")],
525 )),
526 StatusDetails::DebugInfo(
527 DebugInfo::default()
528 .set_stack_entries(vec!["stack".to_string()])
529 .set_detail("detail"),
530 ),
531 StatusDetails::ErrorInfo(
532 ErrorInfo::default()
533 .set_reason("reason")
534 .set_domain("domain")
535 .set_metadata([("", "")].into_iter().take(0)),
536 ),
537 StatusDetails::Help(Help::default().set_links(
538 vec![rpc::model::help::Link::default()
539 .set_description( "desc")
540 .set_url( "url")
541 ],
542 )),
543 StatusDetails::LocalizedMessage(
544 LocalizedMessage::default()
545 .set_locale("locale")
546 .set_message("message"),
547 ),
548 StatusDetails::PreconditionFailure(
549 PreconditionFailure::default().set_violations(vec![
550 rpc::model::precondition_failure::Violation::default()
551 .set_type("type")
552 .set_subject("subject")
553 .set_description("desc"),
554 ]),
555 ),
556 StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
557 vec![rpc::model::quota_failure::Violation::default()
558 .set_subject( "subject")
559 .set_description( "desc")
560 ],
561 )),
562 StatusDetails::RequestInfo(
563 RequestInfo::default()
564 .set_request_id("id")
565 .set_serving_data("data"),
566 ),
567 StatusDetails::ResourceInfo(
568 ResourceInfo::default()
569 .set_resource_type("type")
570 .set_resource_name("name")
571 .set_owner("owner")
572 .set_description("desc"),
573 ),
574 StatusDetails::RetryInfo(
575 RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
576 ),
577 ],
578 };
579 let got = serde_json::to_value(&status).unwrap();
582 let want = json!({
583 "code": Code::Unimplemented,
584 "message": "test",
585 "details": [
586 {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
587 {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
588 {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
589 {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
590 {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
591 {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
592 {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
593 {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
594 {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
595 {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
596 ]
597 });
598 assert_eq!(got, want);
599 }
600
601 #[test]
602 fn deserialization_all_variants() {
603 let json = json!({
604 "code": Code::Unknown as i32,
605 "message": "test",
606 "details": [
607 {"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
608 {"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
609 {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
610 {"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
611 {"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
612 {"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
613 {"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
614 {"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
615 {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
616 {"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
617 ]
618 });
619 let got: Status = serde_json::from_value(json).unwrap();
620 let want = Status {
621 code: Code::Unknown,
622 message: "test".to_string(),
623 details: vec![
624 StatusDetails::BadRequest(BadRequest::default().set_field_violations(
625 vec![rpc::model::bad_request::FieldViolation::default()
626 .set_field( "field" )
627 .set_description( "desc" )
628 ],
629 )),
630 StatusDetails::DebugInfo(
631 DebugInfo::default()
632 .set_stack_entries(vec!["stack".to_string()])
633 .set_detail("detail"),
634 ),
635 StatusDetails::ErrorInfo(
636 ErrorInfo::default()
637 .set_reason("reason")
638 .set_domain("domain"),
639 ),
640 StatusDetails::Help(Help::default().set_links(vec![
641 rpc::model::help::Link::default().set_description("desc").set_url("url"),
642 ])),
643 StatusDetails::LocalizedMessage(
644 LocalizedMessage::default()
645 .set_locale("locale")
646 .set_message("message"),
647 ),
648 StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
649 vec![rpc::model::precondition_failure::Violation::default()
650 .set_type( "type" )
651 .set_subject( "subject" )
652 .set_description( "desc" )
653 ],
654 )),
655 StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
656 vec![rpc::model::quota_failure::Violation::default()
657 .set_subject( "subject")
658 .set_description( "desc")
659 ],
660 )),
661 StatusDetails::RequestInfo(
662 RequestInfo::default()
663 .set_request_id("id")
664 .set_serving_data("data"),
665 ),
666 StatusDetails::ResourceInfo(
667 ResourceInfo::default()
668 .set_resource_type("type")
669 .set_resource_name("name")
670 .set_owner("owner")
671 .set_description("desc"),
672 ),
673 StatusDetails::RetryInfo(
674 RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
675 ),
676 ],
677 };
678 assert_eq!(got, want);
679 }
680
681 #[test]
682 fn serialization_other() -> Result<()> {
683 const TIME: &str = "2025-05-27T10:00:00Z";
684 let timestamp = wkt::Timestamp::try_from(TIME)?;
685 let any = wkt::Any::from_msg(×tamp)?;
686 let input = Status {
687 code: Code::Unknown,
688 message: "test".to_string(),
689 details: vec![StatusDetails::Other(any)],
690 };
691 let got = serde_json::to_value(&input)?;
692 let want = json!({
693 "code": Code::Unknown as i32,
694 "message": "test",
695 "details": [
696 {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
697 ]
698 });
699 assert_eq!(got, want);
700 Ok(())
701 }
702
703 #[test]
704 fn deserialization_other() -> Result<()> {
705 const TIME: &str = "2025-05-27T10:00:00Z";
706 let json = json!({
707 "code": Code::Unknown as i32,
708 "message": "test",
709 "details": [
710 {"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
711 ]
712 });
713 let timestamp = wkt::Timestamp::try_from(TIME)?;
714 let any = wkt::Any::from_msg(×tamp)?;
715 let got: Status = serde_json::from_value(json)?;
716 let want = Status {
717 code: Code::Unknown,
718 message: "test".to_string(),
719 details: vec![StatusDetails::Other(any)],
720 };
721 assert_eq!(got, want);
722 Ok(())
723 }
724
725 #[test]
726 fn status_from_rpc_no_details() {
727 let input = rpc::model::Status::default()
728 .set_code(Code::Unavailable as i32)
729 .set_message("try-again");
730 let got = Status::from(input);
731 assert_eq!(got.code, Code::Unavailable);
732 assert_eq!(got.message, "try-again");
733 }
734
735 #[test_case(
736 BadRequest::default(),
737 StatusDetails::BadRequest(BadRequest::default())
738 )]
739 #[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
740 #[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
741 #[test_case(Help::default(), StatusDetails::Help(Help::default()))]
742 #[test_case(
743 LocalizedMessage::default(),
744 StatusDetails::LocalizedMessage(LocalizedMessage::default())
745 )]
746 #[test_case(
747 PreconditionFailure::default(),
748 StatusDetails::PreconditionFailure(PreconditionFailure::default())
749 )]
750 #[test_case(
751 QuotaFailure::default(),
752 StatusDetails::QuotaFailure(QuotaFailure::default())
753 )]
754 #[test_case(
755 RequestInfo::default(),
756 StatusDetails::RequestInfo(RequestInfo::default())
757 )]
758 #[test_case(
759 ResourceInfo::default(),
760 StatusDetails::ResourceInfo(ResourceInfo::default())
761 )]
762 #[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
763 fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
764 where
765 T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
766 {
767 let input = rpc::model::Status::default()
768 .set_code(Code::Unavailable as i32)
769 .set_message("try-again")
770 .set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
771
772 let status = Status::from(input);
773 assert_eq!(status.code, Code::Unavailable);
774 assert_eq!(status.message, "try-again");
775
776 let got = status.details.first();
777 assert_eq!(got, Some(&want));
778 }
779
780 #[test]
781 fn status_from_rpc_unknown_details() {
782 let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
783 let input = rpc::model::Status::default()
784 .set_code(Code::Unavailable as i32)
785 .set_message("try-again")
786 .set_details(vec![any.clone()]);
787 let got = Status::from(input);
788 assert_eq!(got.code, Code::Unavailable);
789 assert_eq!(got.message, "try-again");
790
791 let got = got.details.first();
792 let want = StatusDetails::Other(any);
793 assert_eq!(got, Some(&want));
794 }
795
796 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";
799 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";
800
801 fn sample_status() -> Status {
803 Status {
804 code: Code::InvalidArgument,
805 message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
806 .into(),
807 details: [].into(),
808 }
809 }
810
811 #[test]
812 fn deserialize_status() {
813 let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
814 let want = ErrorWrapper {
815 error: WrapperStatus {
816 code: 400,
817 status: Some("INVALID_ARGUMENT".to_string()),
818 message:
819 "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
820 .into(),
821 details: [].into(),
822 },
823 };
824 assert_eq!(got.error, want.error);
825 }
826
827 #[test]
828 fn try_from_bytes() -> Result<()> {
829 let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
830 let want = sample_status();
831 assert_eq!(got, want);
832
833 let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
834 let err = got.unwrap_err();
835 assert!(err.is_deserialization(), "{err:?}");
836
837 let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
838 let err = got.unwrap_err();
839 assert!(err.is_deserialization(), "{err:?}");
840
841 let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
842 assert_eq!(got.code, Code::Unknown);
843 Ok(())
844 }
845
846 #[test]
847 fn code_to_string() {
848 let got = String::from(Code::AlreadyExists);
849 let want = "ALREADY_EXISTS";
850 assert_eq!(got, want);
851 }
852
853 #[test_case("OK")]
854 #[test_case("CANCELLED")]
855 #[test_case("UNKNOWN")]
856 #[test_case("INVALID_ARGUMENT")]
857 #[test_case("DEADLINE_EXCEEDED")]
858 #[test_case("NOT_FOUND")]
859 #[test_case("ALREADY_EXISTS")]
860 #[test_case("PERMISSION_DENIED")]
861 #[test_case("RESOURCE_EXHAUSTED")]
862 #[test_case("FAILED_PRECONDITION")]
863 #[test_case("ABORTED")]
864 #[test_case("OUT_OF_RANGE")]
865 #[test_case("UNIMPLEMENTED")]
866 #[test_case("INTERNAL")]
867 #[test_case("UNAVAILABLE")]
868 #[test_case("DATA_LOSS")]
869 #[test_case("UNAUTHENTICATED")]
870 fn code_roundtrip(input: &str) -> Result<()> {
871 let code = Code::try_from(input).unwrap();
872 let output = String::from(code);
873 assert_eq!(output.as_str(), input.to_string());
874 assert_eq!(&format!("{code}"), input);
875 assert_eq!(code.name(), input);
876 Ok(())
877 }
878
879 #[test_case("OK")]
880 #[test_case("CANCELLED")]
881 #[test_case("UNKNOWN")]
882 #[test_case("INVALID_ARGUMENT")]
883 #[test_case("DEADLINE_EXCEEDED")]
884 #[test_case("NOT_FOUND")]
885 #[test_case("ALREADY_EXISTS")]
886 #[test_case("PERMISSION_DENIED")]
887 #[test_case("RESOURCE_EXHAUSTED")]
888 #[test_case("FAILED_PRECONDITION")]
889 #[test_case("ABORTED")]
890 #[test_case("OUT_OF_RANGE")]
891 #[test_case("UNIMPLEMENTED")]
892 #[test_case("INTERNAL")]
893 #[test_case("UNAVAILABLE")]
894 #[test_case("DATA_LOSS")]
895 #[test_case("UNAUTHENTICATED")]
896 fn code_serialize_roundtrip(input: &str) -> Result<()> {
897 let want = Code::try_from(input).unwrap();
898 let serialized = serde_json::to_value(want)?;
899 let got = serde_json::from_value::<Code>(serialized)?;
900 assert_eq!(got, want);
901 Ok(())
902 }
903
904 #[test]
905 fn code_try_from_string_error() {
906 let err = Code::try_from("INVALID-NOT-A-CODE");
907 assert!(
908 matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
909 "expected error in try_from, got {err:?}"
910 );
911 }
912
913 #[test]
914 fn code_deserialize_invalid_type() {
915 let input = json!({"k": "v"});
916 let err = serde_json::from_value::<Code>(input);
917 assert!(err.is_err(), "expected an error, got {err:?}");
918 }
919
920 #[test]
921 fn code_deserialize_unknown() -> Result<()> {
922 let input = json!(-17);
923 let code = serde_json::from_value::<Code>(input)?;
924 assert_eq!(code, Code::Unknown);
925 Ok(())
926 }
927}