#[derive(Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Any(serde_json::Map<String, serde_json::Value>);
#[derive(thiserror::Error, Debug)]
pub enum AnyError {
#[error("cannot serialize object into an Any, source={0:?}")]
SerializationError(#[source] BoxedError),
#[error("cannot deserialize from an Any, source={0:?}")]
DeserializationError(#[source] BoxedError),
#[error("expected type mismatch in Any deserialization type={0}")]
TypeMismatchError(String),
}
impl AnyError {
pub(crate) fn ser<T: Into<BoxedError>>(v: T) -> Self {
Self::SerializationError(v.into())
}
pub(crate) fn deser<T: Into<BoxedError>>(v: T) -> Self {
Self::DeserializationError(v.into())
}
}
type BoxedError = Box<dyn std::error::Error + Send + Sync>;
type Error = AnyError;
impl Any {
pub fn try_from<T>(message: &T) -> Result<Self, Error>
where
T: serde::ser::Serialize + crate::message::Message,
{
use serde_json::{Map, Value};
let value = serde_json::to_value(message).map_err(Error::ser)?;
let value = match value {
Value::Object(mut map) => {
map.insert(
"@type".to_string(),
Value::String(T::typename().to_string()),
);
map
}
Value::String(s) => {
let map: Map<String, serde_json::Value> =
[("@type", T::typename().to_string()), ("value", s)]
.into_iter()
.map(|(k, v)| (k.to_string(), Value::String(v)))
.collect();
map
}
_ => {
return Err(Self::unexpected_json_type());
}
};
Ok(Any(value))
}
pub fn try_into_message<T>(&self) -> Result<T, Error>
where
T: serde::de::DeserializeOwned + crate::message::Message,
{
let map = &self.0;
let r#type = map
.get("@type")
.and_then(|v| v.as_str())
.ok_or_else(|| "@type field is missing or is not a string".to_string())
.map_err(Error::deser)?;
Self::check_typename(r#type, T::typename())?;
if r#type.starts_with("type.googleapis.com/google.protobuf.")
&& r#type != "type.googleapis.com/google.protobuf.Empty"
&& r#type != "type.googleapis.com/google.protobuf.FieldMask"
{
return map
.get("value")
.map(|v| serde_json::from_value::<T>(v.clone()))
.ok_or_else(Self::missing_value_field)?
.map_err(Error::deser);
}
serde_json::from_value::<T>(serde_json::Value::Object(map.clone())).map_err(Error::deser)
}
fn missing_value_field() -> Error {
Error::deser("value field is missing")
}
fn unexpected_json_type() -> Error {
Error::ser("unexpected JSON type, only Object and String are supported")
}
fn check_typename(got: &str, want: &str) -> Result<(), Error> {
if got == want {
return Ok(());
}
Err(Error::deser(format!("mismatched typenames extracting from Any, the any has {got}, the target type is {want}")))
}
}
impl serde::ser::Serialize for Any {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> serde::de::Deserialize<'de> for Any {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Map::<String, serde_json::Value>::deserialize(deserializer)?;
Ok(Any(value))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::duration::*;
use crate::empty::Empty;
use crate::field_mask::*;
use crate::timestamp::*;
use serde_json::json;
type Result = std::result::Result<(), Box<dyn std::error::Error>>;
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Stored {
#[serde(skip_serializing_if = "String::is_empty")]
pub parent: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub id: String,
}
impl crate::message::Message for Stored {
fn typename() -> &'static str {
"type.googleapis.com/wkt.test.Stored"
}
}
#[test]
fn serialize_duration() -> Result {
let d = Duration::clamp(60, 0);
let any = Any::try_from(&d)?;
let got = serde_json::to_value(any)?;
let want = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialize_duration() -> Result {
let input =
json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
let any = Any(input.as_object().unwrap().clone());
let d = any.try_into_message::<Duration>()?;
assert_eq!(d, Duration::clamp(60, 0));
Ok(())
}
#[test]
fn serialize_empty() -> Result {
let empty = Empty::default();
let any = Any::try_from(&empty)?;
let got = serde_json::to_value(any)?;
let want = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialize_empty() -> Result {
let input = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
let any = Any(input.as_object().unwrap().clone());
let empty = any.try_into_message::<Empty>()?;
assert_eq!(empty, Empty::default());
Ok(())
}
#[test]
fn serialize_field_mask() -> Result {
let d = FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec());
let any = Any::try_from(&d)?;
let got = serde_json::to_value(any)?;
let want =
json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "paths": "a,b"});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialize_field_mask() -> Result {
let input =
json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "paths": "a,b"});
let any = Any(input.as_object().unwrap().clone());
let d = any.try_into_message::<FieldMask>()?;
assert_eq!(
d,
FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec())
);
Ok(())
}
#[test]
fn serialize_timestamp() -> Result {
let d = Timestamp::clamp(123, 0);
let any = Any::try_from(&d)?;
let got = serde_json::to_value(any)?;
let want = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialize_timestamp() -> Result {
let input = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
let any = Any(input.as_object().unwrap().clone());
let d = any.try_into_message::<Timestamp>()?;
assert_eq!(d, Timestamp::clamp(123, 0));
Ok(())
}
#[test]
fn serialize_generic() -> Result {
let d = Stored {
parent: "parent".to_string(),
id: "id".to_string(),
};
let any = Any::try_from(&d)?;
let got = serde_json::to_value(any)?;
let want =
json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialize_generic() -> Result {
let input =
json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
let any = Any(input.as_object().unwrap().clone());
let d = any.try_into_message::<Stored>()?;
assert_eq!(
d,
Stored {
parent: "parent".to_string(),
id: "id".to_string()
}
);
Ok(())
}
#[derive(Default, serde::Serialize)]
struct DetectBadMessages(serde_json::Value);
impl crate::message::Message for DetectBadMessages {
fn typename() -> &'static str {
"not used"
}
}
#[test]
fn try_from_error() -> Result {
let input = DetectBadMessages(json!([2, 3]));
let got = Any::try_from(&input);
assert!(got.is_err(), "{got:?}");
Ok(())
}
#[test]
fn deserialize_missing_type_field() -> Result {
let input = json!({"@type-is-missing": ""});
let any = serde_json::from_value::<Any>(input)?;
let got = any.try_into_message::<Stored>();
assert!(got.is_err());
Ok(())
}
#[test]
fn deserialize_invalid_type_field() -> Result {
let input = json!({"@type": [1, 2, 3]});
let any = serde_json::from_value::<Any>(input)?;
let got = any.try_into_message::<Stored>();
assert!(got.is_err());
Ok(())
}
#[test]
fn deserialize_missing_value_field() -> Result {
let input = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value-is-missing": "1.2s"});
let any = serde_json::from_value::<Any>(input)?;
let got = any.try_into_message::<Duration>();
assert!(got.is_err());
Ok(())
}
#[test]
fn deserialize_invalid_value_field() -> Result {
let input =
json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": ["1.2s"]});
let any = serde_json::from_value::<Any>(input)?;
let got = any.try_into_message::<Duration>();
assert!(got.is_err());
Ok(())
}
#[test]
fn deserialize_type_mismatch() -> Result {
let input =
json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "1.2s"});
let any = serde_json::from_value::<Any>(input)?;
let got = any.try_into_message::<Timestamp>();
assert!(got.is_err());
let error = got.err().unwrap();
assert!(
format!("{error}").contains("type.googleapis.com/google.protobuf.Duration"),
"{error}"
);
assert!(
format!("{error}").contains("type.googleapis.com/google.protobuf.Timestamp"),
"{error}"
);
Ok(())
}
}