use crate::error::Error;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
#[non_exhaustive]
pub struct Status {
pub code: Code,
pub message: String,
pub details: Vec<StatusDetails>,
}
impl Status {
pub fn set_code<T: Into<Code>>(mut self, v: T) -> Self {
self.code = v.into();
self
}
pub fn set_message<T: Into<String>>(mut self, v: T) -> Self {
self.message = v.into();
self
}
pub fn set_details<T, I>(mut self, v: T) -> Self
where
T: IntoIterator<Item = I>,
I: Into<StatusDetails>,
{
self.details = v.into_iter().map(|v| v.into()).collect();
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[non_exhaustive]
pub enum Code {
Ok = 0,
Cancelled = 1,
#[default]
Unknown = 2,
InvalidArgument = 3,
DeadlineExceeded = 4,
NotFound = 5,
AlreadyExists = 6,
PermissionDenied = 7,
ResourceExhausted = 8,
FailedPrecondition = 9,
Aborted = 10,
OutOfRange = 11,
Unimplemented = 12,
Internal = 13,
Unavailable = 14,
DataLoss = 15,
Unauthenticated = 16,
}
impl Code {
pub fn name(&self) -> &'static str {
match self {
Code::Ok => "OK",
Code::Cancelled => "CANCELLED",
Code::Unknown => "UNKNOWN",
Code::InvalidArgument => "INVALID_ARGUMENT",
Code::DeadlineExceeded => "DEADLINE_EXCEEDED",
Code::NotFound => "NOT_FOUND",
Code::AlreadyExists => "ALREADY_EXISTS",
Code::PermissionDenied => "PERMISSION_DENIED",
Code::ResourceExhausted => "RESOURCE_EXHAUSTED",
Code::FailedPrecondition => "FAILED_PRECONDITION",
Code::Aborted => "ABORTED",
Code::OutOfRange => "OUT_OF_RANGE",
Code::Unimplemented => "UNIMPLEMENTED",
Code::Internal => "INTERNAL",
Code::Unavailable => "UNAVAILABLE",
Code::DataLoss => "DATA_LOSS",
Code::Unauthenticated => "UNAUTHENTICATED",
}
}
}
impl std::convert::From<i32> for Code {
fn from(value: i32) -> Self {
match value {
0 => Code::Ok,
1 => Code::Cancelled,
2 => Code::Unknown,
3 => Code::InvalidArgument,
4 => Code::DeadlineExceeded,
5 => Code::NotFound,
6 => Code::AlreadyExists,
7 => Code::PermissionDenied,
8 => Code::ResourceExhausted,
9 => Code::FailedPrecondition,
10 => Code::Aborted,
11 => Code::OutOfRange,
12 => Code::Unimplemented,
13 => Code::Internal,
14 => Code::Unavailable,
15 => Code::DataLoss,
16 => Code::Unauthenticated,
_ => Code::default(),
}
}
}
impl std::convert::From<Code> for String {
fn from(value: Code) -> String {
value.name().to_string()
}
}
impl std::fmt::Display for Code {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
impl std::convert::TryFrom<&str> for Code {
type Error = String;
fn try_from(value: &str) -> std::result::Result<Code, Self::Error> {
match value {
"OK" => Ok(Code::Ok),
"CANCELLED" => Ok(Code::Cancelled),
"UNKNOWN" => Ok(Code::Unknown),
"INVALID_ARGUMENT" => Ok(Code::InvalidArgument),
"DEADLINE_EXCEEDED" => Ok(Code::DeadlineExceeded),
"NOT_FOUND" => Ok(Code::NotFound),
"ALREADY_EXISTS" => Ok(Code::AlreadyExists),
"PERMISSION_DENIED" => Ok(Code::PermissionDenied),
"RESOURCE_EXHAUSTED" => Ok(Code::ResourceExhausted),
"FAILED_PRECONDITION" => Ok(Code::FailedPrecondition),
"ABORTED" => Ok(Code::Aborted),
"OUT_OF_RANGE" => Ok(Code::OutOfRange),
"UNIMPLEMENTED" => Ok(Code::Unimplemented),
"INTERNAL" => Ok(Code::Internal),
"UNAVAILABLE" => Ok(Code::Unavailable),
"DATA_LOSS" => Ok(Code::DataLoss),
"UNAUTHENTICATED" => Ok(Code::Unauthenticated),
_ => Err(format!("unknown status code value {value}")),
}
}
}
impl Serialize for Code {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i32(*self as i32)
}
}
impl<'de> Deserialize<'de> for Code {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
i32::deserialize(deserializer).map(Code::from)
}
}
#[derive(Clone, Debug, Deserialize)]
struct ErrorWrapper {
error: WrapperStatus,
}
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(default)]
#[non_exhaustive]
struct WrapperStatus {
pub code: i32,
pub message: String,
pub status: Option<String>,
pub details: Vec<StatusDetails>,
}
impl TryFrom<&bytes::Bytes> for Status {
type Error = Error;
fn try_from(value: &bytes::Bytes) -> Result<Self, Self::Error> {
let wrapper = serde_json::from_slice::<ErrorWrapper>(value)
.map(|w| w.error)
.map_err(Error::deser)?;
let code = match wrapper.status.as_deref().map(Code::try_from) {
Some(Ok(code)) => code,
Some(Err(_)) | None => Code::Unknown,
};
Ok(Status {
code,
message: wrapper.message,
details: wrapper.details,
})
}
}
impl From<google_cloud_rpc::model::Status> for Status {
fn from(value: google_cloud_rpc::model::Status) -> Self {
Self {
code: value.code.into(),
message: value.message,
details: value.details.into_iter().map(StatusDetails::from).collect(),
}
}
}
impl From<&google_cloud_rpc::model::Status> for Status {
fn from(value: &google_cloud_rpc::model::Status) -> Self {
Self {
code: value.code.into(),
message: value.message.clone(),
details: value.details.iter().map(StatusDetails::from).collect(),
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
#[serde(tag = "@type")]
pub enum StatusDetails {
#[serde(rename = "type.googleapis.com/google.rpc.BadRequest")]
BadRequest(google_cloud_rpc::model::BadRequest),
#[serde(rename = "type.googleapis.com/google.rpc.DebugInfo")]
DebugInfo(google_cloud_rpc::model::DebugInfo),
#[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
ErrorInfo(google_cloud_rpc::model::ErrorInfo),
#[serde(rename = "type.googleapis.com/google.rpc.Help")]
Help(google_cloud_rpc::model::Help),
#[serde(rename = "type.googleapis.com/google.rpc.LocalizedMessage")]
LocalizedMessage(google_cloud_rpc::model::LocalizedMessage),
#[serde(rename = "type.googleapis.com/google.rpc.PreconditionFailure")]
PreconditionFailure(google_cloud_rpc::model::PreconditionFailure),
#[serde(rename = "type.googleapis.com/google.rpc.QuotaFailure")]
QuotaFailure(google_cloud_rpc::model::QuotaFailure),
#[serde(rename = "type.googleapis.com/google.rpc.RequestInfo")]
RequestInfo(google_cloud_rpc::model::RequestInfo),
#[serde(rename = "type.googleapis.com/google.rpc.ResourceInfo")]
ResourceInfo(google_cloud_rpc::model::ResourceInfo),
#[serde(rename = "type.googleapis.com/google.rpc.RetryInfo")]
RetryInfo(google_cloud_rpc::model::RetryInfo),
#[serde(untagged)]
Other(wkt::Any),
}
impl From<wkt::Any> for StatusDetails {
fn from(value: wkt::Any) -> Self {
macro_rules! try_convert {
($($variant:ident),*) => {
$(
if let Ok(v) = value.to_msg::<google_cloud_rpc::model::$variant>() {
return StatusDetails::$variant(v);
}
)*
};
}
try_convert!(
BadRequest,
DebugInfo,
ErrorInfo,
Help,
LocalizedMessage,
PreconditionFailure,
QuotaFailure,
RequestInfo,
ResourceInfo,
RetryInfo
);
StatusDetails::Other(value)
}
}
impl From<&wkt::Any> for StatusDetails {
fn from(value: &wkt::Any) -> Self {
macro_rules! try_convert {
($($variant:ident),*) => {
$(
if let Ok(v) = value.to_msg::<google_cloud_rpc::model::$variant>() {
return StatusDetails::$variant(v);
}
)*
};
}
try_convert!(
BadRequest,
DebugInfo,
ErrorInfo,
Help,
LocalizedMessage,
PreconditionFailure,
QuotaFailure,
RequestInfo,
ResourceInfo,
RetryInfo
);
StatusDetails::Other(value.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use google_cloud_rpc::model::DebugInfo;
use google_cloud_rpc::model::ErrorInfo;
use google_cloud_rpc::model::LocalizedMessage;
use google_cloud_rpc::model::RequestInfo;
use google_cloud_rpc::model::ResourceInfo;
use google_cloud_rpc::model::RetryInfo;
use google_cloud_rpc::model::{BadRequest, bad_request};
use google_cloud_rpc::model::{Help, help};
use google_cloud_rpc::model::{PreconditionFailure, precondition_failure};
use google_cloud_rpc::model::{QuotaFailure, quota_failure};
use serde_json::json;
use test_case::test_case;
#[test]
fn status_basic_setters() {
let got = Status::default()
.set_code(Code::Unimplemented)
.set_message("test-message");
let want = Status {
code: Code::Unimplemented,
message: "test-message".into(),
..Default::default()
};
assert_eq!(got, want);
let got = Status::default()
.set_code(Code::Unimplemented as i32)
.set_message("test-message");
let want = Status {
code: Code::Unimplemented,
message: "test-message".into(),
..Default::default()
};
assert_eq!(got, want);
}
#[test]
fn status_detail_setter() -> Result<()> {
let d0 = StatusDetails::ErrorInfo(ErrorInfo::new().set_reason("test-reason"));
let d1 =
StatusDetails::Help(Help::new().set_links([help::Link::new().set_url("test-url")]));
let want = Status {
details: vec![d0.clone(), d1.clone()],
..Default::default()
};
let got = Status::default().set_details([d0, d1]);
assert_eq!(got, want);
let a0 = wkt::Any::from_msg(&ErrorInfo::new().set_reason("test-reason"))?;
let a1 =
wkt::Any::from_msg(&Help::new().set_links([help::Link::new().set_url("test-url")]))?;
let got = Status::default().set_details(&[a0, a1]);
assert_eq!(got, want);
Ok(())
}
#[test]
fn serialization_all_variants() {
let status = Status {
code: Code::Unimplemented,
message: "test".to_string(),
details: vec![
StatusDetails::BadRequest(BadRequest::default().set_field_violations(vec![
bad_request::FieldViolation::default()
.set_field("field")
.set_description("desc"),
])),
StatusDetails::DebugInfo(
DebugInfo::default()
.set_stack_entries(vec!["stack".to_string()])
.set_detail("detail"),
),
StatusDetails::ErrorInfo(
ErrorInfo::default()
.set_reason("reason")
.set_domain("domain")
.set_metadata([("", "")].into_iter().take(0)),
),
StatusDetails::Help(Help::default().set_links(vec![
help::Link::default().set_description("desc").set_url("url"),
])),
StatusDetails::LocalizedMessage(
LocalizedMessage::default()
.set_locale("locale")
.set_message("message"),
),
StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
vec![
precondition_failure::Violation::default()
.set_type("type")
.set_subject("subject")
.set_description("desc"),
],
)),
StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
vec![quota_failure::Violation::default()
.set_subject( "subject")
.set_description( "desc")
],
)),
StatusDetails::RequestInfo(
RequestInfo::default()
.set_request_id("id")
.set_serving_data("data"),
),
StatusDetails::ResourceInfo(
ResourceInfo::default()
.set_resource_type("type")
.set_resource_name("name")
.set_owner("owner")
.set_description("desc"),
),
StatusDetails::RetryInfo(
RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
),
],
};
let got = serde_json::to_value(&status).unwrap();
let want = json!({
"code": Code::Unimplemented,
"message": "test",
"details": [
{"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
{"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
{"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain"},
{"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
{"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
{"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
{"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
{"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
{"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
{"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
]
});
assert_eq!(got, want);
}
#[test]
fn deserialization_all_variants() {
let json = json!({
"code": Code::Unknown as i32,
"message": "test",
"details": [
{"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "field", "description": "desc"}]},
{"@type": "type.googleapis.com/google.rpc.DebugInfo", "stackEntries": ["stack"], "detail": "detail"},
{"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "reason", "domain": "domain", "metadata": {}},
{"@type": "type.googleapis.com/google.rpc.Help", "links": [{"description": "desc", "url": "url"}]},
{"@type": "type.googleapis.com/google.rpc.LocalizedMessage", "locale": "locale", "message": "message"},
{"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"type": "type", "subject": "subject", "description": "desc"}]},
{"@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [{"subject": "subject", "description": "desc"}]},
{"@type": "type.googleapis.com/google.rpc.RequestInfo", "requestId": "id", "servingData": "data"},
{"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resourceType": "type", "resourceName": "name", "owner": "owner", "description": "desc"},
{"@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "1s"},
]
});
let got: Status = serde_json::from_value(json).unwrap();
let want = Status {
code: Code::Unknown,
message: "test".to_string(),
details: vec![
StatusDetails::BadRequest(BadRequest::default().set_field_violations(
vec![bad_request::FieldViolation::default()
.set_field( "field" )
.set_description( "desc" )
],
)),
StatusDetails::DebugInfo(
DebugInfo::default()
.set_stack_entries(vec!["stack".to_string()])
.set_detail("detail"),
),
StatusDetails::ErrorInfo(
ErrorInfo::default()
.set_reason("reason")
.set_domain("domain"),
),
StatusDetails::Help(Help::default().set_links(vec![
help::Link::default().set_description("desc").set_url("url"),
])),
StatusDetails::LocalizedMessage(
LocalizedMessage::default()
.set_locale("locale")
.set_message("message"),
),
StatusDetails::PreconditionFailure(PreconditionFailure::default().set_violations(
vec![precondition_failure::Violation::default()
.set_type( "type" )
.set_subject( "subject" )
.set_description( "desc" )
],
)),
StatusDetails::QuotaFailure(QuotaFailure::default().set_violations(
vec![quota_failure::Violation::default()
.set_subject( "subject")
.set_description( "desc")
],
)),
StatusDetails::RequestInfo(
RequestInfo::default()
.set_request_id("id")
.set_serving_data("data"),
),
StatusDetails::ResourceInfo(
ResourceInfo::default()
.set_resource_type("type")
.set_resource_name("name")
.set_owner("owner")
.set_description("desc"),
),
StatusDetails::RetryInfo(
RetryInfo::default().set_retry_delay(wkt::Duration::clamp(1, 0)),
),
],
};
assert_eq!(got, want);
}
#[test]
fn serialization_other() -> Result<()> {
const TIME: &str = "2025-05-27T10:00:00Z";
let timestamp = wkt::Timestamp::try_from(TIME)?;
let any = wkt::Any::from_msg(×tamp)?;
let input = Status {
code: Code::Unknown,
message: "test".to_string(),
details: vec![StatusDetails::Other(any)],
};
let got = serde_json::to_value(&input)?;
let want = json!({
"code": Code::Unknown as i32,
"message": "test",
"details": [
{"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
]
});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialization_other() -> Result<()> {
const TIME: &str = "2025-05-27T10:00:00Z";
let json = json!({
"code": Code::Unknown as i32,
"message": "test",
"details": [
{"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": TIME},
]
});
let timestamp = wkt::Timestamp::try_from(TIME)?;
let any = wkt::Any::from_msg(×tamp)?;
let got: Status = serde_json::from_value(json)?;
let want = Status {
code: Code::Unknown,
message: "test".to_string(),
details: vec![StatusDetails::Other(any)],
};
assert_eq!(got, want);
Ok(())
}
#[test]
fn status_from_rpc_no_details() {
let input = google_cloud_rpc::model::Status::default()
.set_code(Code::Unavailable as i32)
.set_message("try-again");
let got = Status::from(&input);
assert_eq!(got.code, Code::Unavailable);
assert_eq!(got.message, "try-again");
}
#[test_case(
BadRequest::default(),
StatusDetails::BadRequest(BadRequest::default())
)]
#[test_case(DebugInfo::default(), StatusDetails::DebugInfo(DebugInfo::default()))]
#[test_case(ErrorInfo::default(), StatusDetails::ErrorInfo(ErrorInfo::default()))]
#[test_case(Help::default(), StatusDetails::Help(Help::default()))]
#[test_case(
LocalizedMessage::default(),
StatusDetails::LocalizedMessage(LocalizedMessage::default())
)]
#[test_case(
PreconditionFailure::default(),
StatusDetails::PreconditionFailure(PreconditionFailure::default())
)]
#[test_case(
QuotaFailure::default(),
StatusDetails::QuotaFailure(QuotaFailure::default())
)]
#[test_case(
RequestInfo::default(),
StatusDetails::RequestInfo(RequestInfo::default())
)]
#[test_case(
ResourceInfo::default(),
StatusDetails::ResourceInfo(ResourceInfo::default())
)]
#[test_case(RetryInfo::default(), StatusDetails::RetryInfo(RetryInfo::default()))]
fn status_from_rpc_status_known_detail_type<T>(detail: T, want: StatusDetails)
where
T: wkt::message::Message + serde::ser::Serialize + serde::de::DeserializeOwned,
{
let input = google_cloud_rpc::model::Status::default()
.set_code(Code::Unavailable as i32)
.set_message("try-again")
.set_details(vec![wkt::Any::from_msg(&detail).unwrap()]);
let from_ref = Status::from(&input);
let status = Status::from(input);
assert_eq!(from_ref, status);
assert_eq!(status.code, Code::Unavailable);
assert_eq!(status.message, "try-again");
let got = status.details.first();
assert_eq!(got, Some(&want));
}
#[test]
fn status_from_rpc_unknown_details() {
let any = wkt::Any::from_msg(&wkt::Duration::clamp(123, 0)).unwrap();
let input = google_cloud_rpc::model::Status::default()
.set_code(Code::Unavailable as i32)
.set_message("try-again")
.set_details(vec![any.clone()]);
let from_ref = Status::from(&input);
let got = Status::from(input);
assert_eq!(from_ref, got);
assert_eq!(got.code, Code::Unavailable);
assert_eq!(got.message, "try-again");
let got = got.details.first();
let want = StatusDetails::Other(any);
assert_eq!(got, Some(&want));
}
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";
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";
fn sample_status() -> Status {
Status {
code: Code::InvalidArgument,
message: "The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
.into(),
details: [].into(),
}
}
#[test]
fn deserialize_status() {
let got = serde_json::from_slice::<ErrorWrapper>(SAMPLE_PAYLOAD).unwrap();
let want = ErrorWrapper {
error: WrapperStatus {
code: 400,
status: Some("INVALID_ARGUMENT".to_string()),
message:
"The provided Secret ID [] does not match the expected format [[a-zA-Z_0-9]+]"
.into(),
details: [].into(),
},
};
assert_eq!(got.error, want.error);
}
#[test]
fn try_from_bytes() -> Result<()> {
let got = Status::try_from(&bytes::Bytes::from_static(SAMPLE_PAYLOAD))?;
let want = sample_status();
assert_eq!(got, want);
let got = Status::try_from(&bytes::Bytes::from_static(b"\"error\": 1234"));
let err = got.unwrap_err();
assert!(err.is_deserialization(), "{err:?}");
let got = Status::try_from(&bytes::Bytes::from_static(b"\"missing-error\": 1234"));
let err = got.unwrap_err();
assert!(err.is_deserialization(), "{err:?}");
let got = Status::try_from(&bytes::Bytes::from_static(INVALID_CODE_PAYLOAD))?;
assert_eq!(got.code, Code::Unknown);
Ok(())
}
#[test]
fn code_to_string() {
let got = String::from(Code::AlreadyExists);
let want = "ALREADY_EXISTS";
assert_eq!(got, want);
}
#[test_case("OK")]
#[test_case("CANCELLED")]
#[test_case("UNKNOWN")]
#[test_case("INVALID_ARGUMENT")]
#[test_case("DEADLINE_EXCEEDED")]
#[test_case("NOT_FOUND")]
#[test_case("ALREADY_EXISTS")]
#[test_case("PERMISSION_DENIED")]
#[test_case("RESOURCE_EXHAUSTED")]
#[test_case("FAILED_PRECONDITION")]
#[test_case("ABORTED")]
#[test_case("OUT_OF_RANGE")]
#[test_case("UNIMPLEMENTED")]
#[test_case("INTERNAL")]
#[test_case("UNAVAILABLE")]
#[test_case("DATA_LOSS")]
#[test_case("UNAUTHENTICATED")]
fn code_roundtrip(input: &str) -> Result<()> {
let code = Code::try_from(input).unwrap();
let output = String::from(code);
assert_eq!(output.as_str(), input.to_string());
assert_eq!(&format!("{code}"), input);
assert_eq!(code.name(), input);
Ok(())
}
#[test_case("OK")]
#[test_case("CANCELLED")]
#[test_case("UNKNOWN")]
#[test_case("INVALID_ARGUMENT")]
#[test_case("DEADLINE_EXCEEDED")]
#[test_case("NOT_FOUND")]
#[test_case("ALREADY_EXISTS")]
#[test_case("PERMISSION_DENIED")]
#[test_case("RESOURCE_EXHAUSTED")]
#[test_case("FAILED_PRECONDITION")]
#[test_case("ABORTED")]
#[test_case("OUT_OF_RANGE")]
#[test_case("UNIMPLEMENTED")]
#[test_case("INTERNAL")]
#[test_case("UNAVAILABLE")]
#[test_case("DATA_LOSS")]
#[test_case("UNAUTHENTICATED")]
fn code_serialize_roundtrip(input: &str) -> Result<()> {
let want = Code::try_from(input).unwrap();
let serialized = serde_json::to_value(want)?;
let got = serde_json::from_value::<Code>(serialized)?;
assert_eq!(got, want);
Ok(())
}
#[test]
fn code_try_from_string_error() {
let err = Code::try_from("INVALID-NOT-A-CODE");
assert!(
matches!(&err, Err(s) if s.contains("INVALID-NOT-A-CODE")),
"expected error in try_from, got {err:?}"
);
}
#[test]
fn code_deserialize_invalid_type() {
let input = json!({"k": "v"});
let err = serde_json::from_value::<Code>(input);
assert!(err.is_err(), "expected an error, got {err:?}");
}
#[test]
fn code_deserialize_unknown() -> Result<()> {
let input = json!(-17);
let code = serde_json::from_value::<Code>(input)?;
assert_eq!(code, Code::Unknown);
Ok(())
}
}