diff --git a/Cargo.toml b/Cargo.toml index 01cbe5a9..6ea8053a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,12 @@ name = "cloudevents" [dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" -serde-value = "^0.6" chrono = { version = "^0.4", features = ["serde"] } delegate-attr = "^0.2" base64 = "^0.12" url = { version = "^2.1", features = ["serde"] } snafu = "^0.6" +bitflags = "^1.2" [target."cfg(not(target_arch = \"wasm32\"))".dependencies] hostname = "^0.3" @@ -38,6 +38,7 @@ uuid = { version = "^0.8", features = ["v4", "wasm-bindgen"] } rstest = "0.6" claim = "0.3.1" version-sync = "^0.9" +serde_yaml = "0.8" [workspace] members = [ diff --git a/src/event/format.rs b/src/event/format.rs index 6c2d3a04..5eed6f82 100644 --- a/src/event/format.rs +++ b/src/event/format.rs @@ -3,50 +3,45 @@ use super::{ EventFormatSerializerV03, EventFormatSerializerV10, }; use crate::event::{AttributesReader, ExtensionValue}; -use serde::de::{Error, IntoDeserializer, Unexpected}; +use serde::de::{Error, IntoDeserializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde_value::Value; -use std::collections::{BTreeMap, HashMap}; +use serde_json::{Map, Value}; +use std::collections::HashMap; -macro_rules! parse_optional_field { - ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { +macro_rules! parse_field { + ($value:expr, $target_type:ty, $error:ty) => { + <$target_type>::deserialize($value.into_deserializer()).map_err(<$error>::custom) + }; + + ($value:expr, $target_type:ty, $error:ty, $mapper:expr) => { + <$target_type>::deserialize($value.into_deserializer()) + .map_err(<$error>::custom) + .and_then(|v| $mapper(v).map_err(<$error>::custom)) + }; +} + +macro_rules! extract_optional_field { + ($map:ident, $name:literal, $target_type:ty, $error:ty) => { $map.remove($name) - .map(|val| match val { - Value::$value_variant(v) => Ok(v), - other => Err(<$error>::invalid_type( - crate::event::format::value_to_unexpected(&other), - &stringify!($value_variant), - )), - }) + .map(|v| parse_field!(v, $target_type, $error)) .transpose() }; - ($map:ident, $name:literal, $value_variant:ident, $error:ty, $mapper:expr) => { + ($map:ident, $name:literal, $target_type:ty, $error:ty, $mapper:expr) => { $map.remove($name) - .map(|val| match val { - Value::$value_variant(v) => $mapper(&v).map_err(|e| { - <$error>::invalid_value( - crate::event::format::value_to_unexpected(&Value::$value_variant(v)), - &e.to_string().as_str(), - ) - }), - other => Err(<$error>::invalid_type( - crate::event::format::value_to_unexpected(&other), - &stringify!($value_variant), - )), - }) + .map(|v| parse_field!(v, $target_type, $error, $mapper)) .transpose() }; } -macro_rules! parse_field { - ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { - parse_optional_field!($map, $name, $value_variant, $error)? +macro_rules! extract_field { + ($map:ident, $name:literal, $target_type:ty, $error:ty) => { + extract_optional_field!($map, $name, $target_type, $error)? .ok_or_else(|| <$error>::missing_field($name)) }; - ($map:ident, $name:literal, $value_variant:ident, $error:ty, $mapper:expr) => { - parse_optional_field!($map, $name, $value_variant, $error, $mapper)? + ($map:ident, $name:literal, $target_type:ty, $error:ty, $mapper:expr) => { + extract_optional_field!($map, $name, $target_type, $error, $mapper)? .ok_or_else(|| <$error>::missing_field($name)) }; } @@ -59,13 +54,7 @@ macro_rules! parse_data_json { macro_rules! parse_data_string { ($in:ident, $error:ty) => { - match $in { - Value::String(s) => Ok(s), - other => Err(E::invalid_type( - crate::event::format::value_to_unexpected(&other), - &"a string", - )), - } + parse_field!($in, String, $error) }; } @@ -78,31 +67,25 @@ macro_rules! parse_json_data_base64 { macro_rules! parse_data_base64 { ($in:ident, $error:ty) => { - match $in { - Value::String(s) => base64::decode(&s).map_err(|e| { + parse_field!($in, String, $error).and_then(|s| { + base64::decode(&s).map_err(|e| { <$error>::invalid_value(serde::de::Unexpected::Str(&s), &e.to_string().as_str()) - }), - other => Err(E::invalid_type( - crate::event::format::value_to_unexpected(&other), - &"a string", - )), - } + }) + }) }; } pub(crate) trait EventFormatDeserializer { fn deserialize_attributes( - map: &mut BTreeMap, + map: &mut Map, ) -> Result; fn deserialize_data( content_type: &str, - map: &mut BTreeMap, + map: &mut Map, ) -> Result, E>; - fn deserialize_event( - mut map: BTreeMap, - ) -> Result { + fn deserialize_event(mut map: Map) -> Result { let attributes = Self::deserialize_attributes(&mut map)?; let data = Self::deserialize_data( attributes.datacontenttype().unwrap_or("application/json"), @@ -110,9 +93,13 @@ pub(crate) trait EventFormatDeserializer { )?; let extensions = map .into_iter() - .map(|(k, v)| Ok((k, ExtensionValue::deserialize(v.into_deserializer())?))) - .collect::, serde_value::DeserializerError>>() - .map_err(E::custom)?; + .map(|(k, v)| { + Ok(( + k, + ExtensionValue::deserialize(v.into_deserializer()).map_err(E::custom)?, + )) + }) + .collect::, E>>()?; Ok(Event { attributes, @@ -136,20 +123,12 @@ impl<'de> Deserialize<'de> for Event { where D: Deserializer<'de>, { - let map = match Value::deserialize(deserializer)? { - Value::Map(m) => Ok(m), - v => Err(Error::invalid_type(value_to_unexpected(&v), &"a map")), - }?; + let root_value = Value::deserialize(deserializer)?; + let mut map: Map = + Map::deserialize(root_value.into_deserializer()).map_err(D::Error::custom)?; - let mut map: BTreeMap = map - .into_iter() - .map(|(k, v)| match k { - Value::String(s) => Ok((s, v)), - k => Err(Error::invalid_type(value_to_unexpected(&k), &"a string")), - }) - .collect::, >::Error>>()?; - - match parse_field!(map, "specversion", String, >::Error)?.as_str() { + match extract_field!(map, "specversion", String, >::Error)?.as_str() + { "0.3" => EventFormatDeserializerV03::deserialize_event(map), "1.0" => EventFormatDeserializerV10::deserialize_event(map), s => Err(D::Error::unknown_variant( @@ -175,28 +154,3 @@ impl Serialize for Event { } } } - -// This should be provided by the Value package itself -pub(crate) fn value_to_unexpected(v: &Value) -> Unexpected { - match v { - Value::Bool(b) => serde::de::Unexpected::Bool(*b), - Value::U8(n) => serde::de::Unexpected::Unsigned(*n as u64), - Value::U16(n) => serde::de::Unexpected::Unsigned(*n as u64), - Value::U32(n) => serde::de::Unexpected::Unsigned(*n as u64), - Value::U64(n) => serde::de::Unexpected::Unsigned(*n), - Value::I8(n) => serde::de::Unexpected::Signed(*n as i64), - Value::I16(n) => serde::de::Unexpected::Signed(*n as i64), - Value::I32(n) => serde::de::Unexpected::Signed(*n as i64), - Value::I64(n) => serde::de::Unexpected::Signed(*n), - Value::F32(n) => serde::de::Unexpected::Float(*n as f64), - Value::F64(n) => serde::de::Unexpected::Float(*n), - Value::Char(c) => serde::de::Unexpected::Char(*c), - Value::String(s) => serde::de::Unexpected::Str(s), - Value::Unit => serde::de::Unexpected::Unit, - Value::Option(_) => serde::de::Unexpected::Option, - Value::Newtype(_) => serde::de::Unexpected::NewtypeStruct, - Value::Seq(_) => serde::de::Unexpected::Seq, - Value::Map(_) => serde::de::Unexpected::Map, - Value::Bytes(b) => serde::de::Unexpected::Bytes(b), - } -} diff --git a/src/event/v03/format.rs b/src/event/v03/format.rs index 3009dcbf..e735e10c 100644 --- a/src/event/v03/format.rs +++ b/src/event/v03/format.rs @@ -5,33 +5,34 @@ use chrono::{DateTime, Utc}; use serde::de::IntoDeserializer; use serde::ser::SerializeMap; use serde::{Deserialize, Serializer}; -use serde_value::Value; -use std::collections::{BTreeMap, HashMap}; +use serde_json::{Map, Value}; +use std::collections::HashMap; use url::Url; pub(crate) struct EventFormatDeserializer {} impl crate::event::format::EventFormatDeserializer for EventFormatDeserializer { fn deserialize_attributes( - map: &mut BTreeMap, + map: &mut Map, ) -> Result { Ok(crate::event::Attributes::V03(Attributes { - id: parse_field!(map, "id", String, E)?, - ty: parse_field!(map, "type", String, E)?, - source: parse_field!(map, "source", String, E, Url::parse)?, - datacontenttype: parse_optional_field!(map, "datacontenttype", String, E)?, - schemaurl: parse_optional_field!(map, "schemaurl", String, E, Url::parse)?, - subject: parse_optional_field!(map, "subject", String, E)?, - time: parse_optional_field!(map, "time", String, E, |s| DateTime::parse_from_rfc3339( - s - ) - .map(DateTime::::from))?, + id: extract_field!(map, "id", String, E)?, + ty: extract_field!(map, "type", String, E)?, + source: extract_field!(map, "source", String, E, |s: String| Url::parse(&s))?, + datacontenttype: extract_optional_field!(map, "datacontenttype", String, E)?, + schemaurl: extract_optional_field!(map, "schemaurl", String, E, |s: String| { + Url::parse(&s) + })?, + subject: extract_optional_field!(map, "subject", String, E)?, + time: extract_optional_field!(map, "time", String, E, |s: String| { + DateTime::parse_from_rfc3339(&s).map(DateTime::::from) + })?, })) } fn deserialize_data( content_type: &str, - map: &mut BTreeMap, + map: &mut Map, ) -> Result, E> { let data = map.remove("data"); let is_base64 = map diff --git a/src/event/v10/format.rs b/src/event/v10/format.rs index ff54b322..df624639 100644 --- a/src/event/v10/format.rs +++ b/src/event/v10/format.rs @@ -5,33 +5,34 @@ use chrono::{DateTime, Utc}; use serde::de::IntoDeserializer; use serde::ser::SerializeMap; use serde::{Deserialize, Serializer}; -use serde_value::Value; -use std::collections::{BTreeMap, HashMap}; +use serde_json::{Map, Value}; +use std::collections::HashMap; use url::Url; pub(crate) struct EventFormatDeserializer {} impl crate::event::format::EventFormatDeserializer for EventFormatDeserializer { fn deserialize_attributes( - map: &mut BTreeMap, + map: &mut Map, ) -> Result { Ok(crate::event::Attributes::V10(Attributes { - id: parse_field!(map, "id", String, E)?, - ty: parse_field!(map, "type", String, E)?, - source: parse_field!(map, "source", String, E, Url::parse)?, - datacontenttype: parse_optional_field!(map, "datacontenttype", String, E)?, - dataschema: parse_optional_field!(map, "dataschema", String, E, Url::parse)?, - subject: parse_optional_field!(map, "subject", String, E)?, - time: parse_optional_field!(map, "time", String, E, |s| DateTime::parse_from_rfc3339( - s - ) - .map(DateTime::::from))?, + id: extract_field!(map, "id", String, E)?, + ty: extract_field!(map, "type", String, E)?, + source: extract_field!(map, "source", String, E, |s: String| Url::parse(&s))?, + datacontenttype: extract_optional_field!(map, "datacontenttype", String, E)?, + dataschema: extract_optional_field!(map, "dataschema", String, E, |s: String| { + Url::parse(&s) + })?, + subject: extract_optional_field!(map, "subject", String, E)?, + time: extract_optional_field!(map, "time", String, E, |s: String| { + DateTime::parse_from_rfc3339(&s).map(DateTime::::from) + })?, })) } fn deserialize_data( content_type: &str, - map: &mut BTreeMap, + map: &mut Map, ) -> Result, E> { let data = map.remove("data"); let data_base64 = map.remove("data_base64"); diff --git a/tests/serde_yaml.rs b/tests/serde_yaml.rs new file mode 100644 index 00000000..ea917142 --- /dev/null +++ b/tests/serde_yaml.rs @@ -0,0 +1,32 @@ +use claim::*; +use cloudevents::{Event, EventBuilder, EventBuilderV10}; +use serde_yaml; + +mod test_data; + +/// This test checks if the usage of serde_json::Value makes the Deserialize implementation incompatible with +/// other Deserializers +#[test] +fn deserialize_should_succeed() { + let input = r#" + id: aaa + type: bbb + source: http://localhost + datacontenttype: application/json + data: true + specversion: "1.0" + "#; + + let expected = EventBuilderV10::new() + .id("aaa") + .ty("bbb") + .source("http://localhost") + .data("application/json", serde_json::Value::Bool(true)) + .build() + .unwrap(); + + let deserialize_result: Result = serde_yaml::from_str(input); + assert_ok!(&deserialize_result); + let deserialized = deserialize_result.unwrap(); + assert_eq!(deserialized, expected) +}