diff --git a/src/event/attributes.rs b/src/event/attributes.rs index a02fe390..5cfa5be2 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,5 +1,4 @@ -use super::SpecVersion; -use crate::event::AttributesV10; +use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). @@ -30,6 +29,11 @@ pub trait AttributesWriter { fn set_time(&mut self, time: Option>>); } +pub(crate) trait AttributesConverter { + fn into_v03(self) -> AttributesV03; + fn into_v10(self) -> AttributesV10; +} + pub(crate) trait DataAttributesWriter { fn set_datacontenttype(&mut self, datacontenttype: Option>); fn set_dataschema(&mut self, dataschema: Option>); @@ -37,54 +41,63 @@ pub(crate) trait DataAttributesWriter { #[derive(PartialEq, Debug, Clone)] pub enum Attributes { + V03(AttributesV03), V10(AttributesV10), } impl AttributesReader for Attributes { fn get_id(&self) -> &str { match self { + Attributes::V03(a) => a.get_id(), Attributes::V10(a) => a.get_id(), } } fn get_source(&self) -> &str { match self { + Attributes::V03(a) => a.get_source(), Attributes::V10(a) => a.get_source(), } } fn get_specversion(&self) -> SpecVersion { match self { + Attributes::V03(a) => a.get_specversion(), Attributes::V10(a) => a.get_specversion(), } } fn get_type(&self) -> &str { match self { + Attributes::V03(a) => a.get_type(), Attributes::V10(a) => a.get_type(), } } fn get_datacontenttype(&self) -> Option<&str> { match self { + Attributes::V03(a) => a.get_datacontenttype(), Attributes::V10(a) => a.get_datacontenttype(), } } fn get_dataschema(&self) -> Option<&str> { match self { + Attributes::V03(a) => a.get_dataschema(), Attributes::V10(a) => a.get_dataschema(), } } fn get_subject(&self) -> Option<&str> { match self { + Attributes::V03(a) => a.get_subject(), Attributes::V10(a) => a.get_subject(), } } fn get_time(&self) -> Option<&DateTime> { match self { + Attributes::V03(a) => a.get_time(), Attributes::V10(a) => a.get_time(), } } @@ -93,30 +106,35 @@ impl AttributesReader for Attributes { impl AttributesWriter for Attributes { fn set_id(&mut self, id: impl Into) { match self { + Attributes::V03(a) => a.set_id(id), Attributes::V10(a) => a.set_id(id), } } fn set_source(&mut self, source: impl Into) { match self { + Attributes::V03(a) => a.set_source(source), Attributes::V10(a) => a.set_source(source), } } fn set_type(&mut self, ty: impl Into) { match self { + Attributes::V03(a) => a.set_type(ty), Attributes::V10(a) => a.set_type(ty), } } fn set_subject(&mut self, subject: Option>) { match self { + Attributes::V03(a) => a.set_subject(subject), Attributes::V10(a) => a.set_subject(subject), } } fn set_time(&mut self, time: Option>>) { match self { + Attributes::V03(a) => a.set_time(time), Attributes::V10(a) => a.set_time(time), } } @@ -125,13 +143,30 @@ impl AttributesWriter for Attributes { impl DataAttributesWriter for Attributes { fn set_datacontenttype(&mut self, datacontenttype: Option>) { match self { + Attributes::V03(a) => a.set_datacontenttype(datacontenttype), Attributes::V10(a) => a.set_datacontenttype(datacontenttype), } } fn set_dataschema(&mut self, dataschema: Option>) { match self { + Attributes::V03(a) => a.set_dataschema(dataschema), Attributes::V10(a) => a.set_dataschema(dataschema), } } } + +impl Attributes { + pub fn into_v10(self) -> Self { + match self { + Attributes::V03(v03) => Attributes::V10(v03.into_v10()), + _ => self, + } + } + pub fn into_v03(self) -> Self { + match self { + Attributes::V10(v10) => Attributes::V03(v10.into_v03()), + _ => self, + } + } +} diff --git a/src/event/builder.rs b/src/event/builder.rs index b72dbc87..5dbfef35 100644 --- a/src/event/builder.rs +++ b/src/event/builder.rs @@ -1,4 +1,4 @@ -use super::EventBuilderV10; +use super::{EventBuilderV03, EventBuilderV10}; /// Builder to create [`Event`]: /// ``` @@ -14,8 +14,18 @@ use super::EventBuilderV10; pub struct EventBuilder {} impl EventBuilder { + /// Creates a new builder for latest CloudEvents version + pub fn new() -> EventBuilderV10 { + return Self::v10(); + } + /// Creates a new builder for CloudEvents V1.0 pub fn v10() -> EventBuilderV10 { return EventBuilderV10::new(); } + + /// Creates a new builder for CloudEvents V0.3 + pub fn v03() -> EventBuilderV03 { + return EventBuilderV03::new(); + } } diff --git a/src/event/data.rs b/src/event/data.rs index fcea8393..e511e512 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -1,10 +1,13 @@ use std::convert::{Into, TryFrom}; /// Event [data attribute](https://github.com/cloudevents/spec/blob/master/spec.md#event-data) representation -/// #[derive(Debug, PartialEq, Clone)] pub enum Data { + /// Event has a binary payload Binary(Vec), + /// Event has a non-json string payload + String(String), + /// Event has a json payload Json(serde_json::Value), } @@ -30,6 +33,10 @@ impl Data { } } +pub(crate) fn is_json_content_type(ct: &str) -> bool { + ct == "application/json" || ct == "text/json" || ct.ends_with("+json") +} + impl Into for serde_json::Value { fn into(self) -> Data { Data::Json(self) @@ -44,7 +51,7 @@ impl Into for Vec { impl Into for String { fn into(self) -> Data { - Data::Json(self.into()) + Data::String(self) } } @@ -55,6 +62,7 @@ impl TryFrom for serde_json::Value { match value { Data::Binary(v) => Ok(serde_json::from_slice(&v)?), Data::Json(v) => Ok(v), + Data::String(s) => Ok(serde_json::from_str(&s)?), } } } @@ -66,6 +74,7 @@ impl TryFrom for Vec { match value { Data::Binary(v) => Ok(serde_json::from_slice(&v)?), Data::Json(v) => Ok(serde_json::to_vec(&v)?), + Data::String(s) => Ok(s.into_bytes()), } } } @@ -76,8 +85,8 @@ impl TryFrom for String { fn try_from(value: Data) -> Result { match value { Data::Binary(v) => Ok(String::from_utf8(v)?), - Data::Json(serde_json::Value::String(s)) => Ok(s), // Return the string without quotes Data::Json(v) => Ok(v.to_string()), + Data::String(s) => Ok(s), } } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 507e6de7..3af14943 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -15,6 +15,13 @@ pub use event::Event; pub use extensions::ExtensionValue; pub use spec_version::SpecVersion; +mod v03; + +pub use v03::Attributes as AttributesV03; +pub use v03::EventBuilder as EventBuilderV03; +pub(crate) use v03::EventDeserializer as EventDeserializerV03; +pub(crate) use v03::EventSerializer as EventSerializerV03; + mod v10; pub use v10::Attributes as AttributesV10; diff --git a/src/event/serde.rs b/src/event/serde.rs index cdaee561..c3910097 100644 --- a/src/event/serde.rs +++ b/src/event/serde.rs @@ -1,11 +1,14 @@ -use super::{Attributes, Data, Event, EventDeserializerV10, EventSerializerV10}; -use crate::event::ExtensionValue; +use super::{ + Attributes, Data, Event, EventDeserializerV03, EventDeserializerV10, EventSerializerV03, + EventSerializerV10, +}; +use crate::event::{AttributesReader, ExtensionValue}; use serde::de::{Error, IntoDeserializer, Unexpected}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_value::Value; use std::collections::{BTreeMap, HashMap}; -const SPEC_VERSIONS: [&'static str; 1] = ["1.0"]; +const SPEC_VERSIONS: [&'static str; 2] = ["0.3", "1.0"]; macro_rules! parse_optional_field { ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { @@ -28,16 +31,78 @@ macro_rules! parse_field { }; } +macro_rules! parse_data_json { + ($in:ident, $error:ty) => { + Ok(serde_json::Value::deserialize($in.into_deserializer()) + .map_err(|e| <$error>::custom(e))?) + }; +} + +macro_rules! parse_data_string { + ($in:ident, $error:ty) => { + match $in { + Value::String(s) => Ok(s), + other => Err(E::invalid_type( + crate::event::serde::value_to_unexpected(&other), + &"a string", + )), + } + }; +} + +macro_rules! parse_json_data_base64 { + ($in:ident, $error:ty) => {{ + let data = parse_data_base64!($in, $error)?; + serde_json::from_slice(&data).map_err(|e| <$error>::custom(e)) + }}; +} + +macro_rules! parse_data_base64 { + ($in:ident, $error:ty) => { + match $in { + Value::String(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::serde::value_to_unexpected(&other), + &"a string", + )), + } + }; +} + pub(crate) trait EventDeserializer { fn deserialize_attributes( - &self, map: &mut BTreeMap, ) -> Result; fn deserialize_data( - &self, + content_type: &str, map: &mut BTreeMap, ) -> Result, E>; + + fn deserialize_event( + mut map: BTreeMap, + ) -> Result { + let attributes = Self::deserialize_attributes(&mut map)?; + let data = Self::deserialize_data( + attributes + .get_datacontenttype() + .unwrap_or("application/json"), + &mut map, + )?; + let extensions = map + .into_iter() + .map(|(k, v)| Ok((k, ExtensionValue::deserialize(v.into_deserializer())?))) + .collect::, serde_value::DeserializerError>>() + .map_err(|e| E::custom(e))?; + + Ok(Event { + attributes, + data, + extensions, + }) + } } pub(crate) trait EventSerializer { @@ -67,30 +132,11 @@ impl<'de> Deserialize<'de> for Event { }) .collect::, >::Error>>()?; - let event_deserializer = - match parse_field!(map, "specversion", String, >::Error)? - .as_str() - { - "1.0" => Ok(EventDeserializerV10 {}), - s => Err(>::Error::unknown_variant( - s, - &SPEC_VERSIONS, - )), - }?; - - let attributes = event_deserializer.deserialize_attributes(&mut map)?; - let data = event_deserializer.deserialize_data(&mut map)?; - let extensions = map - .into_iter() - .map(|(k, v)| Ok((k, ExtensionValue::deserialize(v.into_deserializer())?))) - .collect::, serde_value::DeserializerError>>() - .map_err(|e| >::Error::custom(e))?; - - Ok(Event { - attributes, - data, - extensions, - }) + match parse_field!(map, "specversion", String, >::Error)?.as_str() { + "0.3" => EventDeserializerV03::deserialize_event(map), + "1.0" => EventDeserializerV10::deserialize_event(map), + s => Err(D::Error::unknown_variant(s, &SPEC_VERSIONS)), + } } } @@ -100,6 +146,9 @@ impl Serialize for Event { S: Serializer, { match &self.attributes { + Attributes::V03(a) => { + EventSerializerV03::serialize(a, &self.data, &self.extensions, serializer) + } Attributes::V10(a) => { EventSerializerV10::serialize(a, &self.data, &self.extensions, serializer) } diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs new file mode 100644 index 00000000..1e75629e --- /dev/null +++ b/src/event/v03/attributes.rs @@ -0,0 +1,124 @@ +use crate::event::attributes::{AttributesConverter, DataAttributesWriter}; +use crate::event::AttributesV10; +use crate::event::{AttributesReader, AttributesWriter, SpecVersion}; +use chrono::{DateTime, Utc}; +use hostname::get_hostname; +use uuid::Uuid; + +#[derive(PartialEq, Debug, Clone)] +pub struct Attributes { + pub(crate) id: String, + pub(crate) ty: String, + pub(crate) source: String, + pub(crate) datacontenttype: Option, + pub(crate) schemaurl: Option, + pub(crate) subject: Option, + pub(crate) time: Option>, +} + +impl AttributesReader for Attributes { + fn get_id(&self) -> &str { + &self.id + } + + fn get_source(&self) -> &str { + &self.source + } + + fn get_specversion(&self) -> SpecVersion { + SpecVersion::V03 + } + + fn get_type(&self) -> &str { + &self.ty + } + + fn get_datacontenttype(&self) -> Option<&str> { + match self.datacontenttype.as_ref() { + Some(s) => Some(&s), + None => None, + } + } + + fn get_dataschema(&self) -> Option<&str> { + match self.schemaurl.as_ref() { + Some(s) => Some(&s), + None => None, + } + } + + fn get_subject(&self) -> Option<&str> { + match self.subject.as_ref() { + Some(s) => Some(&s), + None => None, + } + } + + fn get_time(&self) -> Option<&DateTime> { + self.time.as_ref() + } +} + +impl AttributesWriter for Attributes { + fn set_id(&mut self, id: impl Into) { + self.id = id.into() + } + + fn set_source(&mut self, source: impl Into) { + self.source = source.into() + } + + fn set_type(&mut self, ty: impl Into) { + self.ty = ty.into() + } + + fn set_subject(&mut self, subject: Option>) { + self.subject = subject.map(Into::into) + } + + fn set_time(&mut self, time: Option>>) { + self.time = time.map(Into::into) + } +} + +impl DataAttributesWriter for Attributes { + fn set_datacontenttype(&mut self, datacontenttype: Option>) { + self.datacontenttype = datacontenttype.map(Into::into) + } + + fn set_dataschema(&mut self, dataschema: Option>) { + self.schemaurl = dataschema.map(Into::into) + } +} + +impl Default for Attributes { + fn default() -> Self { + Attributes { + id: Uuid::new_v4().to_string(), + ty: "type".to_string(), + source: get_hostname().unwrap_or("http://localhost/".to_string()), + datacontenttype: None, + schemaurl: None, + subject: None, + time: None, + } + } +} + +impl AttributesConverter for Attributes { + fn into_v03(self) -> Self { + self + } + + fn into_v10(self) -> AttributesV10 { + AttributesV10 { + id: self.id, + ty: self.ty, + source: self.source, + datacontenttype: self.datacontenttype, + dataschema: self.schemaurl, + subject: self.subject, + time: self.time, + } + } +} diff --git a/src/event/v03/builder.rs b/src/event/v03/builder.rs new file mode 100644 index 00000000..41aca7c4 --- /dev/null +++ b/src/event/v03/builder.rs @@ -0,0 +1,132 @@ +use super::Attributes as AttributesV03; +use crate::event::{Attributes, AttributesWriter, Data, Event, ExtensionValue}; +use chrono::{DateTime, Utc}; +use std::collections::HashMap; + +pub struct EventBuilder { + event: Event, +} + +impl EventBuilder { + pub fn from(event: Event) -> Self { + EventBuilder { + event: Event { + attributes: event.attributes.into_v03(), + data: event.data, + extensions: event.extensions, + }, + } + } + + pub fn new() -> Self { + EventBuilder { + event: Event { + attributes: Attributes::V03(AttributesV03::default()), + data: None, + extensions: HashMap::new(), + }, + } + } + + pub fn id(mut self, id: impl Into) -> Self { + self.event.set_id(id); + return self; + } + + pub fn source(mut self, source: impl Into) -> Self { + self.event.set_source(source); + return self; + } + + pub fn ty(mut self, ty: impl Into) -> Self { + self.event.set_type(ty); + return self; + } + + pub fn subject(mut self, subject: impl Into) -> Self { + self.event.set_subject(Some(subject)); + return self; + } + + pub fn time(mut self, time: impl Into>) -> Self { + self.event.set_time(Some(time)); + return self; + } + + pub fn extension( + mut self, + extension_name: &str, + extension_value: impl Into, + ) -> Self { + self.event.set_extension(extension_name, extension_value); + return self; + } + + pub fn data(mut self, datacontenttype: impl Into, data: impl Into) -> Self { + self.event.write_data(datacontenttype, data); + return self; + } + + pub fn data_with_schema( + mut self, + datacontenttype: impl Into, + schemaurl: impl Into, + data: impl Into, + ) -> Self { + self.event + .write_data_with_schema(datacontenttype, schemaurl, data); + return self; + } + + pub fn build(self) -> Event { + self.event + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::event::{AttributesReader, SpecVersion}; + + #[test] + fn build_event() { + let id = "aaa"; + let source = "http://localhost:8080"; + let ty = "bbb"; + let subject = "francesco"; + let time: DateTime = Utc::now(); + let extension_name = "ext"; + let extension_value = 10i64; + let content_type = "application/json"; + let schema = "http://localhost:8080/schema"; + let data = serde_json::json!({ + "hello": "world" + }); + + let event = EventBuilder::new() + .id(id) + .source(source) + .ty(ty) + .subject(subject) + .time(time) + .extension(extension_name, extension_value) + .data_with_schema(content_type, schema, data.clone()) + .build(); + + assert_eq!(SpecVersion::V03, event.get_specversion()); + assert_eq!(id, event.get_id()); + assert_eq!(source, event.get_source()); + assert_eq!(ty, event.get_type()); + assert_eq!(subject, event.get_subject().unwrap()); + assert_eq!(time, event.get_time().unwrap().clone()); + assert_eq!( + ExtensionValue::from(extension_value), + event.get_extension(extension_name).unwrap().clone() + ); + assert_eq!(content_type, event.get_datacontenttype().unwrap()); + assert_eq!(schema, event.get_dataschema().unwrap()); + + let event_data: serde_json::Value = event.try_get_data().unwrap().unwrap(); + assert_eq!(data, event_data); + } +} diff --git a/src/event/v03/mod.rs b/src/event/v03/mod.rs index e69de29b..4ab6a7a0 100644 --- a/src/event/v03/mod.rs +++ b/src/event/v03/mod.rs @@ -0,0 +1,8 @@ +mod attributes; +mod builder; +mod serde; + +pub(crate) use crate::event::v03::serde::EventDeserializer; +pub(crate) use crate::event::v03::serde::EventSerializer; +pub use attributes::Attributes; +pub use builder::EventBuilder; diff --git a/src/event/v03/serde.rs b/src/event/v03/serde.rs new file mode 100644 index 00000000..58f0f650 --- /dev/null +++ b/src/event/v03/serde.rs @@ -0,0 +1,110 @@ +use super::Attributes; +use crate::event::data::is_json_content_type; +use crate::event::{Data, ExtensionValue}; +use chrono::{DateTime, Utc}; +use serde::de::{IntoDeserializer, Unexpected}; +use serde::ser::SerializeMap; +use serde::{Deserialize, Serializer}; +use serde_value::Value; +use std::collections::{BTreeMap, HashMap}; + +pub(crate) struct EventDeserializer {} + +impl crate::event::serde::EventDeserializer for EventDeserializer { + fn deserialize_attributes( + map: &mut BTreeMap, + ) -> 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)?, + datacontenttype: parse_optional_field!(map, "datacontenttype", String, E)?, + schemaurl: parse_optional_field!(map, "schemaurl", String, E)?, + subject: parse_optional_field!(map, "subject", String, E)?, + time: parse_optional_field!(map, "time", String, E)? + .map(|s| match DateTime::parse_from_rfc3339(&s) { + Ok(d) => Ok(DateTime::::from(d)), + Err(e) => Err(E::invalid_value( + Unexpected::Str(&s), + &e.to_string().as_str(), + )), + }) + .transpose()?, + })) + } + + fn deserialize_data( + content_type: &str, + map: &mut BTreeMap, + ) -> Result, E> { + let data = map.remove("data"); + let is_base64 = map + .remove("datacontentencoding") + .map(String::deserialize) + .transpose() + .map_err(|e| E::custom(e))? + .map(|dce| dce.to_lowercase() == "base64") + .unwrap_or(false); + let is_json = is_json_content_type(content_type); + + Ok(match (data, is_base64, is_json) { + (Some(d), false, true) => Some(Data::Json(parse_data_json!(d, E)?)), + (Some(d), false, false) => Some(Data::String(parse_data_string!(d, E)?)), + (Some(d), true, true) => Some(Data::Json(parse_json_data_base64!(d, E)?)), + (Some(d), true, false) => Some(Data::Binary(parse_data_base64!(d, E)?)), + (None, _, _) => None, + }) + } +} + +pub(crate) struct EventSerializer {} + +impl crate::event::serde::EventSerializer for EventSerializer { + fn serialize( + attributes: &Attributes, + data: &Option, + extensions: &HashMap, + serializer: S, + ) -> Result<::Ok, ::Error> { + let num = + 3 + if attributes.datacontenttype.is_some() { + 1 + } else { + 0 + } + if attributes.schemaurl.is_some() { 1 } else { 0 } + + if attributes.subject.is_some() { 1 } else { 0 } + + if attributes.time.is_some() { 1 } else { 0 } + + if data.is_some() { 1 } else { 0 } + + extensions.len(); + let mut state = serializer.serialize_map(Some(num))?; + state.serialize_entry("specversion", "0.3")?; + state.serialize_entry("id", &attributes.id)?; + state.serialize_entry("type", &attributes.ty)?; + state.serialize_entry("source", &attributes.source)?; + if let Some(datacontenttype) = &attributes.datacontenttype { + state.serialize_entry("datacontenttype", datacontenttype)?; + } + if let Some(schemaurl) = &attributes.schemaurl { + state.serialize_entry("schemaurl", schemaurl)?; + } + if let Some(subject) = &attributes.subject { + state.serialize_entry("subject", subject)?; + } + if let Some(time) = &attributes.time { + state.serialize_entry("time", time)?; + } + match data { + Some(Data::Json(j)) => state.serialize_entry("data", j)?, + Some(Data::String(s)) => state.serialize_entry("data", s)?, + Some(Data::Binary(v)) => { + state.serialize_entry("data", &base64::encode(v))?; + state.serialize_entry("datacontentencoding", "base64")?; + } + _ => (), + }; + for (k, v) in extensions { + state.serialize_entry(k, v)?; + } + state.end() + } +} diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 183f1a95..cd3d92b7 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,5 +1,5 @@ -use crate::event::attributes::DataAttributesWriter; -use crate::event::{AttributesReader, AttributesWriter, SpecVersion}; +use crate::event::attributes::{AttributesConverter, DataAttributesWriter}; +use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; use uuid::Uuid; @@ -103,3 +103,21 @@ impl Default for Attributes { } } } + +impl AttributesConverter for Attributes { + fn into_v10(self) -> Self { + self + } + + fn into_v03(self) -> AttributesV03 { + AttributesV03 { + id: self.id, + ty: self.ty, + source: self.source, + datacontenttype: self.datacontenttype, + schemaurl: self.dataschema, + subject: self.subject, + time: self.time, + } + } +} diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 3b3ed949..9b7f8888 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -8,10 +8,15 @@ pub struct EventBuilder { } impl EventBuilder { - // This works as soon as we have an event version converter - // pub fn from(event: Event) -> Self { - // EventBuilder { event } - // } + pub fn from(event: Event) -> Self { + EventBuilder { + event: Event { + attributes: event.attributes.into_v10(), + data: event.data, + extensions: event.extensions, + }, + } + } pub fn new() -> Self { EventBuilder { diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs index e6a4a9aa..fa219549 100644 --- a/src/event/v10/serde.rs +++ b/src/event/v10/serde.rs @@ -1,4 +1,5 @@ use super::Attributes; +use crate::event::data::is_json_content_type; use crate::event::{Data, ExtensionValue}; use chrono::{DateTime, Utc}; use serde::de::{IntoDeserializer, Unexpected}; @@ -11,7 +12,6 @@ pub(crate) struct EventDeserializer {} impl crate::event::serde::EventDeserializer for EventDeserializer { fn deserialize_attributes( - &self, map: &mut BTreeMap, ) -> Result { Ok(crate::event::Attributes::V10(Attributes { @@ -34,28 +34,22 @@ impl crate::event::serde::EventDeserializer for EventDeserializer { } fn deserialize_data( - &self, + content_type: &str, map: &mut BTreeMap, ) -> Result, E> { let data = map.remove("data"); let data_base64 = map.remove("data_base64"); - match (data, data_base64) { - (Some(d), None) => Ok(Some(Data::Json( - serde_json::Value::deserialize(d.into_deserializer()).map_err(|e| E::custom(e))?, - ))), - (None, Some(d)) => match d { - Value::String(s) => Ok(Some(Data::from_base64(s.clone()).map_err(|e| { - E::invalid_value(Unexpected::Str(&s), &e.to_string().as_str()) - })?)), - other => Err(E::invalid_type( - crate::event::serde::value_to_unexpected(&other), - &"a string", - )), - }, - (Some(_), Some(_)) => Err(E::custom("Cannot have both data and data_base64 field")), - (None, None) => Ok(None), - } + let is_json = is_json_content_type(content_type); + + Ok(match (data, data_base64, is_json) { + (Some(d), None, true) => Some(Data::Json(parse_data_json!(d, E)?)), + (Some(d), None, false) => Some(Data::String(parse_data_string!(d, E)?)), + (None, Some(d), true) => Some(Data::Json(parse_json_data_base64!(d, E)?)), + (None, Some(d), false) => Some(Data::Binary(parse_data_base64!(d, E)?)), + (Some(_), Some(_), _) => Err(E::custom("Cannot have both data and data_base64 field"))?, + (None, None, _) => None, + }) } } @@ -100,6 +94,7 @@ impl crate::event::serde::EventSerializer f } match data { Some(Data::Json(j)) => state.serialize_entry("data", j)?, + Some(Data::String(s)) => state.serialize_entry("data", s)?, Some(Data::Binary(v)) => state.serialize_entry("data_base64", &base64::encode(v))?, _ => (), }; diff --git a/tests/event.rs b/tests/event.rs deleted file mode 100644 index 7ad4c3a3..00000000 --- a/tests/event.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[test] -fn use_event() {} diff --git a/tests/serde_json.rs b/tests/serde_json.rs index ac54cf02..14f01619 100644 --- a/tests/serde_json.rs +++ b/tests/serde_json.rs @@ -7,30 +7,83 @@ mod test_data; use test_data::*; /// This test is a parametrized test that uses data from tests/test_data -/// The test follows the flow Event -> serde_json::Value -> String -> Event #[rstest( - event, - expected_json, - case::minimal_v1(minimal_v1(), minimal_v1_json()), - case::full_v1_no_data(full_v1_no_data(), full_v1_no_data_json()), - case::full_v1_with_json_data(full_v1_json_data(), full_v1_json_data_json()), - case::full_v1_with_base64_data(full_v1_binary_data(), full_v1_base64_data_json()) + in_event, + out_json, + case::minimal_v03(v03::minimal(), v03::minimal_json()), + case::full_v03_no_data(v03::full_no_data(), v03::full_no_data_json()), + case::full_v03_with_json_data(v03::full_json_data(), v03::full_json_data_json()), + case::full_v03_with_xml_string_data( + v03::full_xml_string_data(), + v03::full_xml_string_data_json() + ), + case::full_v03_with_xml_base64_data( + v03::full_xml_binary_data(), + v03::full_xml_base64_data_json() + ), + case::minimal_v10(v10::minimal(), v10::minimal_json()), + case::full_v10_no_data(v10::full_no_data(), v10::full_no_data_json()), + case::full_v10_with_json_data(v10::full_json_data(), v10::full_json_data_json()), + case::full_v10_with_xml_string_data( + v10::full_xml_string_data(), + v10::full_xml_string_data_json() + ), + case::full_v10_with_xml_base64_data( + v10::full_xml_binary_data(), + v10::full_xml_base64_data_json() + ) )] -fn serialize_deserialize_should_succeed(event: Event, expected_json: Value) { +fn serialize_should_succeed(in_event: Event, out_json: Value) { // Event -> serde_json::Value - let serialize_result = serde_json::to_value(event.clone()); + let serialize_result = serde_json::to_value(in_event.clone()); assert_ok!(&serialize_result); let actual_json = serialize_result.unwrap(); - assert_eq!(&actual_json, &expected_json); + assert_eq!(&actual_json, &out_json); // serde_json::Value -> String let actual_json_serialized = actual_json.to_string(); - assert_eq!(actual_json_serialized, expected_json.to_string()); + assert_eq!(actual_json_serialized, out_json.to_string()); // String -> Event let deserialize_result: Result = serde_json::from_str(&actual_json_serialized); assert_ok!(&deserialize_result); let deserialize_json = deserialize_result.unwrap(); - assert_eq!(deserialize_json, event) + assert_eq!(deserialize_json, in_event) +} + +/// This test is a parametrized test that uses data from tests/test_data +#[rstest( + in_json, + out_event, + case::minimal_v03(v03::minimal_json(), v03::minimal()), + case::full_v03_no_data(v03::full_no_data_json(), v03::full_no_data()), + case::full_v03_with_json_data(v03::full_json_data_json(), v03::full_json_data()), + case::full_v03_with_json_base64_data(v03::full_json_base64_data_json(), v03::full_json_data()), + case::full_v03_with_xml_string_data( + v03::full_xml_string_data_json(), + v03::full_xml_string_data() + ), + case::full_v03_with_xml_base64_data( + v03::full_xml_base64_data_json(), + v03::full_xml_binary_data() + ), + case::minimal_v10(v10::minimal_json(), v10::minimal()), + case::full_v10_no_data(v10::full_no_data_json(), v10::full_no_data()), + case::full_v10_with_json_data(v10::full_json_data_json(), v10::full_json_data()), + case::full_v10_with_json_base64_data(v10::full_json_base64_data_json(), v10::full_json_data()), + case::full_v10_with_xml_string_data( + v10::full_xml_string_data_json(), + v10::full_xml_string_data() + ), + case::full_v10_with_xml_base64_data( + v10::full_xml_base64_data_json(), + v10::full_xml_binary_data() + ) +)] +fn deserialize_should_succeed(in_json: Value, out_event: Event) { + let deserialize_result: Result = serde_json::from_value(in_json); + assert_ok!(&deserialize_result); + let deserialize_json = deserialize_result.unwrap(); + assert_eq!(deserialize_json, out_event) } diff --git a/tests/test_data/data.rs b/tests/test_data/data.rs new file mode 100644 index 00000000..087b20d2 --- /dev/null +++ b/tests/test_data/data.rs @@ -0,0 +1,58 @@ +use chrono::{DateTime, TimeZone, Utc}; +use serde_json::{json, Value}; + +pub fn id() -> String { + "0001".to_string() +} + +pub fn ty() -> String { + "test_event.test_application".to_string() +} + +pub fn source() -> String { + "http://localhost".to_string() +} + +pub fn json_datacontenttype() -> String { + "application/json".to_string() +} + +pub fn xml_datacontenttype() -> String { + "application/xml".to_string() +} + +pub fn dataschema() -> String { + "http://localhost/schema".to_string() +} + +pub fn json_data() -> Value { + json!({"hello": "world"}) +} + +pub fn json_data_binary() -> Vec { + serde_json::to_vec(&json!({"hello": "world"})).unwrap() +} + +pub fn xml_data() -> String { + "world".to_string() +} + +pub fn subject() -> String { + "cloudevents-sdk".to_string() +} + +pub fn time() -> DateTime { + Utc.ymd(2020, 3, 16).and_hms(11, 50, 00) +} + +pub fn string_extension() -> (String, String) { + ("string_ex".to_string(), "val".to_string()) +} + +pub fn bool_extension() -> (String, bool) { + ("bool_ex".to_string(), true) +} + +pub fn int_extension() -> (String, i64) { + ("int_ex".to_string(), 10) +} diff --git a/tests/test_data/mod.rs b/tests/test_data/mod.rs index cd582d97..2e4f45c3 100644 --- a/tests/test_data/mod.rs +++ b/tests/test_data/mod.rs @@ -1,183 +1,5 @@ -use chrono::{DateTime, TimeZone, Utc}; -use cloudevents::{Event, EventBuilder}; -use serde_json::{json, Value}; +mod data; +pub use data::*; -pub fn id() -> String { - "0001".to_string() -} - -pub fn ty() -> String { - "test_event.test_application".to_string() -} - -pub fn source() -> String { - "http://localhost".to_string() -} - -pub fn datacontenttype() -> String { - "application/json".to_string() -} - -pub fn dataschema() -> String { - "http://localhost/schema".to_string() -} - -pub fn data() -> Value { - json!({"hello": "world"}) -} - -pub fn data_base_64() -> Vec { - serde_json::to_vec(&json!({"hello": "world"})).unwrap() -} - -pub fn subject() -> String { - "cloudevents-sdk".to_string() -} - -pub fn time() -> DateTime { - Utc.ymd(2020, 3, 16).and_hms(11, 50, 00) -} - -pub fn string_extension() -> (String, String) { - ("string_ex".to_string(), "val".to_string()) -} - -pub fn bool_extension() -> (String, bool) { - ("bool_ex".to_string(), true) -} - -pub fn int_extension() -> (String, i64) { - ("int_ex".to_string(), 10) -} - -pub fn minimal_v1() -> Event { - EventBuilder::v10() - .id(id()) - .source(source()) - .ty(ty()) - .build() -} - -pub fn minimal_v1_json() -> Value { - json!({ - "specversion": "1.0", - "id": id(), - "type": ty(), - "source": source(), - }) -} - -pub fn full_v1_no_data() -> Event { - let (string_ext_name, string_ext_value) = string_extension(); - let (bool_ext_name, bool_ext_value) = bool_extension(); - let (int_ext_name, int_ext_value) = int_extension(); - - EventBuilder::v10() - .id(id()) - .source(source()) - .ty(ty()) - .subject(subject()) - .time(time()) - .extension(&string_ext_name, string_ext_value) - .extension(&bool_ext_name, bool_ext_value) - .extension(&int_ext_name, int_ext_value) - .build() -} - -pub fn full_v1_no_data_json() -> Value { - let (string_ext_name, string_ext_value) = string_extension(); - let (bool_ext_name, bool_ext_value) = bool_extension(); - let (int_ext_name, int_ext_value) = int_extension(); - - json!({ - "specversion": "1.0", - "id": id(), - "type": ty(), - "source": source(), - "subject": subject(), - "time": time(), - string_ext_name: string_ext_value, - bool_ext_name: bool_ext_value, - int_ext_name: int_ext_value - }) -} - -pub fn full_v1_json_data() -> Event { - let (string_ext_name, string_ext_value) = string_extension(); - let (bool_ext_name, bool_ext_value) = bool_extension(); - let (int_ext_name, int_ext_value) = int_extension(); - - EventBuilder::v10() - .id(id()) - .source(source()) - .ty(ty()) - .subject(subject()) - .time(time()) - .extension(&string_ext_name, string_ext_value) - .extension(&bool_ext_name, bool_ext_value) - .extension(&int_ext_name, int_ext_value) - .data_with_schema(datacontenttype(), dataschema(), data()) - .build() -} - -pub fn full_v1_json_data_json() -> Value { - let (string_ext_name, string_ext_value) = string_extension(); - let (bool_ext_name, bool_ext_value) = bool_extension(); - let (int_ext_name, int_ext_value) = int_extension(); - - json!({ - "specversion": "1.0", - "id": id(), - "type": ty(), - "source": source(), - "subject": subject(), - "time": time(), - string_ext_name: string_ext_value, - bool_ext_name: bool_ext_value, - int_ext_name: int_ext_value, - "datacontenttype": datacontenttype(), - "dataschema": dataschema(), - "data": data() - }) -} - -pub fn full_v1_binary_data() -> Event { - let (string_ext_name, string_ext_value) = string_extension(); - let (bool_ext_name, bool_ext_value) = bool_extension(); - let (int_ext_name, int_ext_value) = int_extension(); - - EventBuilder::v10() - .id(id()) - .source(source()) - .ty(ty()) - .subject(subject()) - .time(time()) - .extension(&string_ext_name, string_ext_value) - .extension(&bool_ext_name, bool_ext_value) - .extension(&int_ext_name, int_ext_value) - .data_with_schema(datacontenttype(), dataschema(), data_base_64()) - .build() -} - -pub fn full_v1_base64_data_json() -> Value { - let (string_ext_name, string_ext_value) = string_extension(); - let (bool_ext_name, bool_ext_value) = bool_extension(); - let (int_ext_name, int_ext_value) = int_extension(); - - let d = base64::encode(&data_base_64()); - - json!({ - "specversion": "1.0", - "id": id(), - "type": ty(), - "source": source(), - "subject": subject(), - "time": time(), - string_ext_name: string_ext_value, - bool_ext_name: bool_ext_value, - int_ext_name: int_ext_value, - "datacontenttype": datacontenttype(), - "dataschema": dataschema(), - "data_base64": d - }) -} +pub mod v03; +pub mod v10; diff --git a/tests/test_data/v03.rs b/tests/test_data/v03.rs new file mode 100644 index 00000000..00e53478 --- /dev/null +++ b/tests/test_data/v03.rs @@ -0,0 +1,193 @@ +use super::*; +use cloudevents::{Event, EventBuilder}; +use serde_json::{json, Value}; + +pub fn minimal() -> Event { + EventBuilder::v03() + .id(id()) + .source(source()) + .ty(ty()) + .build() +} + +pub fn minimal_json() -> Value { + json!({ + "specversion": "0.3", + "id": id(), + "type": ty(), + "source": source(), + }) +} + +pub fn full_no_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v03() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .build() +} + +pub fn full_no_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "0.3", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value + }) +} + +pub fn full_json_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v03() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .data_with_schema(json_datacontenttype(), dataschema(), json_data()) + .build() +} + +pub fn full_json_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "0.3", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": json_datacontenttype(), + "schemaurl": dataschema(), + "data": json_data() + }) +} + +pub fn full_json_base64_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "0.3", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": json_datacontenttype(), + "schemaurl": dataschema(), + "datacontentencoding": "base64", + "data": base64::encode(&json_data_binary()) + }) +} + +pub fn full_xml_string_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v03() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .data(xml_datacontenttype(), xml_data()) + .build() +} + +pub fn full_xml_binary_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v03() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .data(xml_datacontenttype(), Vec::from(xml_data())) + .build() +} + +pub fn full_xml_string_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "0.3", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": xml_datacontenttype(), + "data": xml_data() + }) +} + +pub fn full_xml_base64_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "0.3", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": xml_datacontenttype(), + "datacontentencoding": "base64", + "data": base64::encode(Vec::from(xml_data())) + }) +} diff --git a/tests/test_data/v10.rs b/tests/test_data/v10.rs new file mode 100644 index 00000000..f564c4d6 --- /dev/null +++ b/tests/test_data/v10.rs @@ -0,0 +1,191 @@ +use super::*; +use cloudevents::{Event, EventBuilder}; +use serde_json::{json, Value}; + +pub fn minimal() -> Event { + EventBuilder::v10() + .id(id()) + .source(source()) + .ty(ty()) + .build() +} + +pub fn minimal_json() -> Value { + json!({ + "specversion": "1.0", + "id": id(), + "type": ty(), + "source": source(), + }) +} + +pub fn full_no_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v10() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .build() +} + +pub fn full_no_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "1.0", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value + }) +} + +pub fn full_json_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v10() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .data_with_schema(json_datacontenttype(), dataschema(), json_data()) + .build() +} + +pub fn full_json_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "1.0", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": json_datacontenttype(), + "dataschema": dataschema(), + "data": json_data() + }) +} + +pub fn full_json_base64_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "1.0", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": json_datacontenttype(), + "dataschema": dataschema(), + "data_base64": base64::encode(&json_data_binary()) + }) +} + +pub fn full_xml_string_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v10() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .data(xml_datacontenttype(), xml_data()) + .build() +} + +pub fn full_xml_binary_data() -> Event { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + EventBuilder::v10() + .id(id()) + .source(source()) + .ty(ty()) + .subject(subject()) + .time(time()) + .extension(&string_ext_name, string_ext_value) + .extension(&bool_ext_name, bool_ext_value) + .extension(&int_ext_name, int_ext_value) + .data(xml_datacontenttype(), Vec::from(xml_data())) + .build() +} + +pub fn full_xml_string_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "1.0", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": xml_datacontenttype(), + "data": xml_data() + }) +} + +pub fn full_xml_base64_data_json() -> Value { + let (string_ext_name, string_ext_value) = string_extension(); + let (bool_ext_name, bool_ext_value) = bool_extension(); + let (int_ext_name, int_ext_value) = int_extension(); + + json!({ + "specversion": "1.0", + "id": id(), + "type": ty(), + "source": source(), + "subject": subject(), + "time": time(), + string_ext_name: string_ext_value, + bool_ext_name: bool_ext_value, + int_ext_name: int_ext_value, + "datacontenttype": xml_datacontenttype(), + "data_base64": base64::encode(Vec::from(xml_data())) + }) +} diff --git a/tests/version_conversion.rs b/tests/version_conversion.rs new file mode 100644 index 00000000..5ba05e99 --- /dev/null +++ b/tests/version_conversion.rs @@ -0,0 +1,17 @@ +mod test_data; +use cloudevents::event::{EventBuilderV03, EventBuilderV10}; +use test_data::*; + +#[test] +fn v10_to_v03() { + let in_event = v10::full_json_data(); + let out_event = EventBuilderV03::from(in_event).build(); + assert_eq!(v03::full_json_data(), out_event) +} + +#[test] +fn v03_to_v10() { + let in_event = v03::full_json_data(); + let out_event = EventBuilderV10::from(in_event).build(); + assert_eq!(v10::full_json_data(), out_event) +}