use crate::message::MessageSerializer;
#[derive(Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Any(serde_json::Map<String, serde_json::Value>);
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum AnyError {
#[error("cannot serialize object into an Any, source={0}")]
Serialization(#[source] BoxedError),
#[error("cannot deserialize from an Any, source={0}")]
Deserialization(#[source] BoxedError),
#[error(
"mismatched typenames extracting from Any, the any has {has}, the target type is {want}"
)]
TypeMismatch {
has: String,
want: String,
},
}
impl AnyError {
pub(crate) fn ser<T: Into<BoxedError>>(v: T) -> Self {
Self::Serialization(v.into())
}
pub(crate) fn deser<T: Into<BoxedError>>(v: T) -> Self {
Self::Deserialization(v.into())
}
pub(crate) fn mismatch(has: &str, want: &str) -> Self {
Self::TypeMismatch {
has: has.into(),
want: want.into(),
}
}
}
type BoxedError = Box<dyn std::error::Error + Send + Sync>;
type Error = AnyError;
impl Any {
pub fn type_url(&self) -> Option<&str> {
self.0.get("@type").and_then(serde_json::Value::as_str)
}
pub fn from_msg<T>(message: &T) -> Result<Self, Error>
where
T: crate::message::Message,
{
let serializer = T::serializer();
let value = serializer.serialize_to_map(message)?;
Ok(Any(value))
}
pub fn to_msg<T>(&self) -> Result<T, Error>
where
T: 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())?;
let serializer = T::serializer();
serializer.deserialize_from_map(map)
}
fn check_typename(has: &str, want: &str) -> Result<(), Error> {
if has == want {
return Ok(());
}
Err(Error::mismatch(has, want))
}
}
impl crate::message::Message for Any {
fn typename() -> &'static str {
"type.googleapis.com/google.protobuf.Any"
}
#[allow(private_interfaces)]
fn serializer() -> impl crate::message::MessageSerializer<Self> {
crate::message::ValueSerializer::<Self>::new()
}
}
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
impl serde::ser::Serialize for Any {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
self.0.serialize(serializer)
}
}
use serde::de::Unexpected;
type ValueMap = serde_json::Map<String, serde_json::Value>;
const EXPECTED: &str = "a valid type URL string in the @type field";
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
impl<'de> serde::de::Deserialize<'de> for Any {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error as _;
use serde_json::Value;
let value = ValueMap::deserialize(deserializer)?;
match value.get("@type") {
None => Ok(Any(value)),
Some(Value::String(s)) if validate_type_url(s) => Ok(Any(value)),
Some(Value::String(s)) => Err(D::Error::invalid_value(Unexpected::Str(s), &EXPECTED)),
Some(Value::Null) => Err(type_field_invalid_type("JSON null")),
Some(Value::Object(_)) => Err(type_field_invalid_type("JSON object")),
Some(Value::Array(_)) => Err(type_field_invalid_type("JSON array")),
Some(Value::Number(_)) => Err(type_field_invalid_type("JSON number")),
Some(Value::Bool(_)) => Err(type_field_invalid_type("JSON boolean")),
}
}
}
fn type_field_invalid_type<E>(reason: &str) -> E
where
E: serde::de::Error,
{
E::invalid_type(Unexpected::Other(reason), &EXPECTED)
}
fn validate_type_url(type_url: &str) -> bool {
match type_url.split_once("/") {
None => false,
Some((host, path)) => is_host(host) && is_protobuf_id(path),
}
}
fn is_host(host: &str) -> bool {
if host == "type.googleapis.com" {
return true;
}
if host.contains("_") {
return false;
}
url::Url::parse(format!("https://{host}").as_str()).is_ok()
}
fn is_protobuf_id(path: &str) -> bool {
path.split(".").all(is_identifier)
}
fn is_identifier(id: &str) -> bool {
!id.is_empty()
&& id.chars().all(|c: char| c.is_alphanumeric() || c == '_')
&& !id.starts_with(|c: char| c.is_ascii_digit())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::duration::*;
use crate::empty::Empty;
use crate::field_mask::*;
use crate::timestamp::*;
use serde_json::{Value, json};
use test_case::test_case;
type Result = anyhow::Result<()>;
#[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_any() -> Result {
let d = Duration::clamp(60, 0);
let any = Any::from_msg(&d)?;
let any = Any::from_msg(&any)?;
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Any")
);
let got = serde_json::to_value(any)?;
let want = json!({
"@type": "type.googleapis.com/google.protobuf.Any",
"value": {
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "60s"
}
});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialize_any() -> Result {
let input = json!({
"@type": "type.googleapis.com/google.protobuf.Any",
"value": {
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "60s"
}
});
let any = Any(input.as_object().unwrap().clone());
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Any")
);
let any = any.to_msg::<Any>()?;
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Duration")
);
let d = any.to_msg::<Duration>()?;
assert_eq!(d, Duration::clamp(60, 0));
Ok(())
}
#[test_case(json!({"value": "7"}))]
#[test_case(json!({"@type": "type.googleapis.com/foo"}))]
#[test_case(json!({"@type": "type.googleapis.com/foo_bar"}))]
#[test_case(json!({"@type": "type.googleapis.com/foo_bar.baz"}))]
#[test_case(json!({"@type": "type.googleapis.com/foo_bar.baz.Message"}))]
#[test_case(json!({"@type": "type.googleapis.com/foo_bar.baz.Message3"}))]
#[test_case(json!({"@type": "type.googleapis.com/foo2_bar.baz.Message3"}))]
fn deserialize_any_success(input: Value) {
let any = serde_json::from_value::<Any>(input.clone());
assert!(any.is_ok(), "{any:?} from {input:?}");
}
#[test_case(json!({"@type": "", "value": "7"}))]
#[test_case(json!({"@type": "type.googleapis.com/", "value": "7"}))]
#[test_case(json!({"@type": "/google.protobuf.Duration", "value": "7"}))]
#[test_case(json!({"@type": "type.googleapis.com/google.protobuf.7abc", "value": "7"}))]
#[test_case(json!({"@type": "type.googlea_pis.com/google.protobuf.Duration", "value": "7"}))]
#[test_case(json!({"@type": "abc_123/google.protobuf.Foo", "value": "7"}))]
#[test_case(json!({"@type": [], "value": "7"}); "type is array")]
#[test_case(json!({"@type": 7, "value": "7"}))]
#[test_case(json!({"@type": true, "value": "7"}))]
#[test_case(json!({"@type": null, "value": "7"}))]
#[test_case(json!({"@type": {}, "value": "7"}); "type is object")]
fn deserialize_bad_types(input: Value) {
let err = serde_json::from_value::<Any>(input).expect_err("should fail");
assert!(err.is_data(), "{err:?}");
}
#[test]
fn serialize_duration() -> Result {
let d = Duration::clamp(60, 0);
let any = Any::from_msg(&d)?;
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Duration")
);
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());
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Duration")
);
let d = any.to_msg::<Duration>()?;
assert_eq!(d, Duration::clamp(60, 0));
Ok(())
}
#[test]
fn serialize_empty() -> Result {
let empty = Empty::default();
let any = Any::from_msg(&empty)?;
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.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());
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Empty")
);
let empty = any.to_msg::<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::from_msg(&d)?;
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.FieldMask")
);
let got = serde_json::to_value(any)?;
let want =
json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "value": "a,b"});
assert_eq!(got, want);
Ok(())
}
#[test]
fn deserialize_field_mask() -> Result {
let input =
json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "value": "a,b"});
let any = Any(input.as_object().unwrap().clone());
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.FieldMask")
);
let d = any.to_msg::<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::from_msg(&d)?;
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Timestamp")
);
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());
assert_eq!(
any.type_url(),
Some("type.googleapis.com/google.protobuf.Timestamp")
);
let d = any.to_msg::<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::from_msg(&d)?;
assert_eq!(any.type_url(), Some("type.googleapis.com/wkt.test.Stored"));
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());
assert_eq!(any.type_url(), Some("type.googleapis.com/wkt.test.Stored"));
let d = any.to_msg::<Stored>()?;
assert_eq!(
d,
Stored {
parent: "parent".to_string(),
id: "id".to_string()
}
);
Ok(())
}
#[derive(Default, serde::Serialize, serde::Deserialize)]
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::from_msg(&input);
assert!(got.is_err(), "{got:?}");
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.to_msg::<Duration>();
assert!(got.is_err(), "{got:?}");
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.to_msg::<Duration>();
assert!(got.is_err(), "{got:?}");
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.to_msg::<Timestamp>();
assert!(got.is_err(), "{got:?}");
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(())
}
}