Proper BinarySerializer & StructuredSerializer implementations for Event (#61)

* WIP

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Progress

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Seems like everything works

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* fmt'ed more stuff

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
This commit is contained in:
Francesco Guardiani 2020-07-28 09:52:58 +02:00 committed by GitHub
parent fc3790a7bd
commit e87605734e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 281 additions and 183 deletions

View File

@ -181,7 +181,7 @@ mod tests {
//TODO this is required now because the message deserializer implictly set default values
// As soon as this defaulting doesn't happen anymore, we can remove it (Issues #40/#41)
.time(time)
.data("application/json", j.clone())
.data("application/json", j.to_string().into_bytes())
.extension("someint", "10")
.build()
.unwrap();

View File

@ -195,7 +195,7 @@ mod tests {
// As soon as this defaulting doesn't happen anymore, we can remove it (Issues #40/#41)
.time(time)
.source(Url::from_str("http://localhost").unwrap())
.data("application/json", j.clone())
.data("application/json", j.to_string().into_bytes())
.extension("someint", "10")
.build()
.unwrap();

View File

@ -46,7 +46,7 @@ impl Data {
}
pub(crate) fn is_json_content_type(ct: &str) -> bool {
ct == "application/json" || ct == "text/json" || ct.ends_with("+json")
ct.starts_with("application/json") || ct.starts_with("text/json") || ct.ends_with("+json")
}
impl Into<Data> for serde_json::Value {

View File

@ -6,6 +6,7 @@ use crate::message::{
BinaryDeserializer, BinarySerializer, MessageAttributeValue, Result, StructuredDeserializer,
StructuredSerializer,
};
use crate::{EventBuilder, EventBuilderV03, EventBuilderV10};
impl StructuredDeserializer for Event {
fn deserialize_structured<R, V: StructuredSerializer<R>>(self, visitor: V) -> Result<R> {
@ -37,10 +38,6 @@ pub(crate) trait AttributesDeserializer {
fn deserialize_attributes<R: Sized, V: BinarySerializer<R>>(self, visitor: V) -> Result<V>;
}
pub(crate) trait AttributesSerializer {
fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()>;
}
impl AttributesDeserializer for Attributes {
fn deserialize_attributes<R: Sized, V: BinarySerializer<R>>(self, visitor: V) -> Result<V> {
match self {
@ -50,50 +47,125 @@ impl AttributesDeserializer for Attributes {
}
}
impl AttributesSerializer for Attributes {
fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()> {
match self {
Attributes::V03(v03) => v03.serialize_attribute(name, value),
Attributes::V10(v10) => v10.serialize_attribute(name, value),
}
pub(crate) trait AttributesSerializer {
fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()>;
}
#[derive(Debug)]
pub(crate) struct EventStructuredSerializer {}
impl StructuredSerializer<Event> for EventStructuredSerializer {
fn set_structured_event(self, bytes: Vec<u8>) -> Result<Event> {
Ok(serde_json::from_slice(&bytes)?)
}
}
impl StructuredSerializer<Event> for Event {
fn set_structured_event(mut self, bytes: Vec<u8>) -> Result<Event> {
let new_event: Event = serde_json::from_slice(&bytes)?;
self.attributes = new_event.attributes;
self.data = new_event.data;
self.extensions = new_event.extensions;
Ok(self)
#[derive(Debug)]
pub(crate) enum EventBinarySerializer {
V10(EventBuilderV10),
V03(EventBuilderV03),
}
impl EventBinarySerializer {
pub(crate) fn new() -> Self {
EventBinarySerializer::V10(EventBuilderV10::new())
}
}
impl BinarySerializer<Event> for Event {
fn set_spec_version(mut self, spec_version: SpecVersion) -> Result<Self> {
match spec_version {
SpecVersion::V03 => self.attributes = self.attributes.clone().into_v03(),
SpecVersion::V10 => self.attributes = self.attributes.clone().into_v10(),
}
Ok(self)
impl BinarySerializer<Event> for EventBinarySerializer {
fn set_spec_version(self, spec_version: SpecVersion) -> Result<Self> {
Ok(match spec_version {
SpecVersion::V03 => EventBinarySerializer::V03(EventBuilderV03::new()),
SpecVersion::V10 => EventBinarySerializer::V10(EventBuilderV10::new()),
})
}
fn set_attribute(mut self, name: &str, value: MessageAttributeValue) -> Result<Self> {
self.attributes.serialize_attribute(name, value)?;
match &mut self {
EventBinarySerializer::V03(eb) => eb.serialize_attribute(name, value)?,
EventBinarySerializer::V10(eb) => eb.serialize_attribute(name, value)?,
}
Ok(self)
}
fn set_extension(mut self, name: &str, value: MessageAttributeValue) -> Result<Self> {
self.extensions.insert(name.to_string(), value.into());
Ok(self)
fn set_extension(self, name: &str, value: MessageAttributeValue) -> Result<Self> {
Ok(match self {
EventBinarySerializer::V03(eb) => EventBinarySerializer::V03(eb.extension(name, value)),
EventBinarySerializer::V10(eb) => EventBinarySerializer::V10(eb.extension(name, value)),
})
}
fn end_with_data(mut self, bytes: Vec<u8>) -> Result<Event> {
self.data = Some(Data::from_binary(self.get_datacontenttype(), bytes)?);
Ok(self)
fn end_with_data(self, bytes: Vec<u8>) -> Result<Event> {
Ok(match self {
EventBinarySerializer::V03(eb) => {
eb.data_without_content_type(Data::Binary(bytes)).build()
}
EventBinarySerializer::V10(eb) => {
eb.data_without_content_type(Data::Binary(bytes)).build()
}
}?)
}
fn end(self) -> Result<Event> {
Ok(self)
Ok(match self {
EventBinarySerializer::V03(eb) => eb.build(),
EventBinarySerializer::V10(eb) => eb.build(),
}?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message::Error;
#[test]
fn binary_deserializer_unrecognized_attribute_v03() {
assert_eq!(
Error::UnrecognizedAttributeName {
name: "dataschema".to_string()
}
.to_string(),
EventBinarySerializer::new()
.set_spec_version(SpecVersion::V03)
.unwrap()
.set_attribute("dataschema", MessageAttributeValue::Boolean(true))
.expect_err("Should return an error")
.to_string()
)
}
#[test]
fn binary_deserializer_missing_id() {
assert_eq!(
Error::EventBuilderError {
source: crate::event::EventBuilderError::MissingRequiredAttribute {
attribute_name: "id"
},
}
.to_string(),
EventBinarySerializer::new()
.set_spec_version(SpecVersion::V10)
.unwrap()
.end()
.unwrap_err()
.to_string()
)
}
#[test]
fn binary_deserializer_unrecognized_attribute_v10() {
assert_eq!(
Error::UnrecognizedAttributeName {
name: "schemaurl".to_string()
}
.to_string(),
EventBinarySerializer::new()
.set_spec_version(SpecVersion::V10)
.unwrap()
.set_attribute("schemaurl", MessageAttributeValue::Boolean(true))
.expect_err("Should return an error")
.to_string()
)
}
}

View File

@ -17,6 +17,8 @@ pub use builder::EventBuilder;
pub use data::Data;
pub use event::Event;
pub use extensions::ExtensionValue;
pub(crate) use message::EventBinarySerializer;
pub(crate) use message::EventStructuredSerializer;
pub use spec_version::InvalidSpecVersion;
pub use spec_version::SpecVersion;
pub use types::{TryIntoTime, TryIntoUrl};

View File

@ -3,6 +3,7 @@ use crate::event::attributes::{
};
use crate::event::AttributesV10;
use crate::event::{AttributesReader, AttributesWriter, SpecVersion};
use crate::message::{BinarySerializer, MessageAttributeValue};
use chrono::{DateTime, Utc};
use url::Url;
use uuid::Uuid;
@ -184,6 +185,40 @@ impl AttributesConverter for Attributes {
}
}
impl crate::event::message::AttributesDeserializer for super::Attributes {
fn deserialize_attributes<R: Sized, V: BinarySerializer<R>>(
self,
mut visitor: V,
) -> crate::message::Result<V> {
visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?;
visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?;
visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?;
if self.datacontenttype.is_some() {
visitor = visitor.set_attribute(
"datacontenttype",
MessageAttributeValue::String(self.datacontenttype.unwrap()),
)?;
}
if self.schemaurl.is_some() {
visitor = visitor.set_attribute(
"schemaurl",
MessageAttributeValue::Uri(self.schemaurl.unwrap()),
)?;
}
if self.subject.is_some() {
visitor = visitor.set_attribute(
"subject",
MessageAttributeValue::String(self.subject.unwrap()),
)?;
}
if self.time.is_some() {
visitor = visitor
.set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?;
}
Ok(visitor)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -2,12 +2,14 @@ use super::Attributes as AttributesV03;
use crate::event::{
Attributes, Data, Event, EventBuilderError, ExtensionValue, TryIntoTime, TryIntoUrl,
};
use crate::message::MessageAttributeValue;
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use std::convert::TryInto;
use url::Url;
/// Builder to create a CloudEvent V0.3
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct EventBuilder {
id: Option<String>,
ty: Option<String>,
@ -73,6 +75,11 @@ impl EventBuilder {
self
}
pub(crate) fn data_without_content_type(mut self, data: impl Into<Data>) -> Self {
self.data = Some(data.into());
self
}
pub fn data(mut self, datacontenttype: impl Into<String>, data: impl Into<Data>) -> Self {
self.datacontenttype = Some(datacontenttype.into());
self.data = Some(data.into());
@ -173,3 +180,27 @@ impl crate::event::builder::EventBuilder for EventBuilder {
}
}
}
impl crate::event::message::AttributesSerializer for EventBuilder {
fn serialize_attribute(
&mut self,
name: &str,
value: MessageAttributeValue,
) -> crate::message::Result<()> {
match name {
"id" => self.id = Some(value.to_string()),
"type" => self.ty = Some(value.to_string()),
"source" => self.source = Some(value.try_into()?),
"datacontenttype" => self.datacontenttype = Some(value.to_string()),
"schemaurl" => self.schemaurl = Some(value.try_into()?),
"subject" => self.subject = Some(value.to_string()),
"time" => self.time = Some(value.try_into()?),
_ => {
return Err(crate::message::Error::UnrecognizedAttributeName {
name: name.to_string(),
})
}
};
Ok(())
}
}

View File

@ -1,53 +0,0 @@
use crate::message::{BinarySerializer, Error, MessageAttributeValue, Result};
use std::convert::TryInto;
impl crate::event::message::AttributesDeserializer for super::Attributes {
fn deserialize_attributes<R: Sized, V: BinarySerializer<R>>(self, mut visitor: V) -> Result<V> {
visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?;
visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?;
visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?;
if self.datacontenttype.is_some() {
visitor = visitor.set_attribute(
"datacontenttype",
MessageAttributeValue::String(self.datacontenttype.unwrap()),
)?;
}
if self.schemaurl.is_some() {
visitor = visitor.set_attribute(
"schemaurl",
MessageAttributeValue::Uri(self.schemaurl.unwrap()),
)?;
}
if self.subject.is_some() {
visitor = visitor.set_attribute(
"subject",
MessageAttributeValue::String(self.subject.unwrap()),
)?;
}
if self.time.is_some() {
visitor = visitor
.set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?;
}
Ok(visitor)
}
}
impl crate::event::message::AttributesSerializer for super::Attributes {
fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()> {
match name {
"id" => self.id = value.to_string(),
"type" => self.ty = value.to_string(),
"source" => self.source = value.try_into()?,
"datacontenttype" => self.datacontenttype = Some(value.to_string()),
"schemaurl" => self.schemaurl = Some(value.try_into()?),
"subject" => self.subject = Some(value.to_string()),
"time" => self.time = Some(value.try_into()?),
_ => {
return Err(Error::UnrecognizedAttributeName {
name: name.to_string(),
})
}
};
Ok(())
}
}

View File

@ -1,7 +1,6 @@
mod attributes;
mod builder;
mod format;
mod message;
pub use attributes::Attributes;
pub(crate) use attributes::AttributesIntoIterator;

View File

@ -2,6 +2,7 @@ use crate::event::attributes::{
default_hostname, AttributeValue, AttributesConverter, DataAttributesWriter,
};
use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion};
use crate::message::{BinarySerializer, MessageAttributeValue};
use chrono::{DateTime, Utc};
use core::fmt::Debug;
use url::Url;
@ -166,6 +167,40 @@ impl Default for Attributes {
}
}
impl crate::event::message::AttributesDeserializer for super::Attributes {
fn deserialize_attributes<R: Sized, V: BinarySerializer<R>>(
self,
mut visitor: V,
) -> crate::message::Result<V> {
visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?;
visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?;
visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?;
if self.datacontenttype.is_some() {
visitor = visitor.set_attribute(
"datacontenttype",
MessageAttributeValue::String(self.datacontenttype.unwrap()),
)?;
}
if self.dataschema.is_some() {
visitor = visitor.set_attribute(
"dataschema",
MessageAttributeValue::Uri(self.dataschema.unwrap()),
)?;
}
if self.subject.is_some() {
visitor = visitor.set_attribute(
"subject",
MessageAttributeValue::String(self.subject.unwrap()),
)?;
}
if self.time.is_some() {
visitor = visitor
.set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?;
}
Ok(visitor)
}
}
impl AttributesConverter for Attributes {
fn into_v10(self) -> Self {
self

View File

@ -2,12 +2,14 @@ use super::Attributes as AttributesV10;
use crate::event::{
Attributes, Data, Event, EventBuilderError, ExtensionValue, TryIntoTime, TryIntoUrl,
};
use crate::message::MessageAttributeValue;
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use std::convert::TryInto;
use url::Url;
/// Builder to create a CloudEvent V1.0
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct EventBuilder {
id: Option<String>,
ty: Option<String>,
@ -73,6 +75,11 @@ impl EventBuilder {
self
}
pub(crate) fn data_without_content_type(mut self, data: impl Into<Data>) -> Self {
self.data = Some(data.into());
self
}
pub fn data(mut self, datacontenttype: impl Into<String>, data: impl Into<Data>) -> Self {
self.datacontenttype = Some(datacontenttype.into());
self.data = Some(data.into());
@ -173,3 +180,27 @@ impl crate::event::builder::EventBuilder for EventBuilder {
}
}
}
impl crate::event::message::AttributesSerializer for EventBuilder {
fn serialize_attribute(
&mut self,
name: &str,
value: MessageAttributeValue,
) -> crate::message::Result<()> {
match name {
"id" => self.id = Some(value.to_string()),
"type" => self.ty = Some(value.to_string()),
"source" => self.source = Some(value.try_into()?),
"datacontenttype" => self.datacontenttype = Some(value.to_string()),
"dataschema" => self.dataschema = Some(value.try_into()?),
"subject" => self.subject = Some(value.to_string()),
"time" => self.time = Some(value.try_into()?),
_ => {
return Err(crate::message::Error::UnrecognizedAttributeName {
name: name.to_string(),
})
}
}
Ok(())
}
}

View File

@ -1,53 +0,0 @@
use crate::message::{BinarySerializer, Error, MessageAttributeValue, Result};
use std::convert::TryInto;
impl crate::event::message::AttributesDeserializer for super::Attributes {
fn deserialize_attributes<R: Sized, V: BinarySerializer<R>>(self, mut visitor: V) -> Result<V> {
visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?;
visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?;
visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?;
if self.datacontenttype.is_some() {
visitor = visitor.set_attribute(
"datacontenttype",
MessageAttributeValue::String(self.datacontenttype.unwrap()),
)?;
}
if self.dataschema.is_some() {
visitor = visitor.set_attribute(
"dataschema",
MessageAttributeValue::Uri(self.dataschema.unwrap()),
)?;
}
if self.subject.is_some() {
visitor = visitor.set_attribute(
"subject",
MessageAttributeValue::String(self.subject.unwrap()),
)?;
}
if self.time.is_some() {
visitor = visitor
.set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?;
}
Ok(visitor)
}
}
impl crate::event::message::AttributesSerializer for super::Attributes {
fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()> {
match name {
"id" => self.id = value.to_string(),
"type" => self.ty = value.to_string(),
"source" => self.source = value.try_into()?,
"datacontenttype" => self.datacontenttype = Some(value.to_string()),
"dataschema" => self.dataschema = Some(value.try_into()?),
"subject" => self.subject = Some(value.to_string()),
"time" => self.time = Some(value.try_into()?),
_ => {
return Err(Error::UnrecognizedAttributeName {
name: name.to_string(),
})
}
}
Ok(())
}
}

View File

@ -1,7 +1,6 @@
mod attributes;
mod builder;
mod format;
mod message;
pub use attributes::Attributes;
pub(crate) use attributes::AttributesIntoIterator;

View File

@ -1,4 +1,5 @@
use super::{BinarySerializer, Encoding, Result, StructuredSerializer};
use super::{BinarySerializer, Encoding, Error, Result, StructuredSerializer};
use crate::event::{EventBinarySerializer, EventStructuredSerializer};
use crate::Event;
/// Deserializer trait for a Message that can be encoded as structured mode
@ -14,7 +15,7 @@ where
/// Convert this Message to [`Event`]
fn into_event(self) -> Result<Event> {
self.deserialize_structured(Event::default())
self.deserialize_structured(EventStructuredSerializer {})
}
}
@ -28,7 +29,7 @@ where
/// Convert this Message to [`Event`]
fn into_event(self) -> Result<Event> {
self.deserialize_binary(Event::default())
self.deserialize_binary(EventBinarySerializer::new())
}
}
@ -42,7 +43,11 @@ where
/// Convert this Message to [`Event`]
fn into_event(self) -> Result<Event> {
self.deserialize_to(Event::default())
match self.encoding() {
Encoding::BINARY => BinaryDeserializer::into_event(self),
Encoding::STRUCTURED => StructuredDeserializer::into_event(self),
_ => Err(Error::WrongEncoding {}),
}
}
/// Deserialize the message to [`BinarySerializer`]

View File

@ -12,6 +12,11 @@ pub enum Error {
},
#[snafu(display("Unrecognized attribute name: {}", name))]
UnrecognizedAttributeName { name: String },
#[snafu(display("Error while building the final event: {}", source))]
#[snafu(context(false))]
EventBuilderError {
source: crate::event::EventBuilderError,
},
#[snafu(display("Error while parsing a time string: {}", source))]
#[snafu(context(false))]
ParseTimeError { source: chrono::ParseError },

View File

@ -1,9 +1,7 @@
mod test_data;
use cloudevents::message::{
BinaryDeserializer, BinarySerializer, Error, MessageAttributeValue, Result,
StructuredDeserializer,
};
use cloudevents::message::{BinaryDeserializer, Result, StructuredDeserializer};
use cloudevents::{AttributesReader, EventBuilder, EventBuilderV03, EventBuilderV10};
use test_data::*;
#[test]
@ -17,8 +15,18 @@ fn message_v03_roundtrip_structured() -> Result<()> {
#[test]
fn message_v03_roundtrip_binary() -> Result<()> {
//TODO this code smells because we're missing a proper way in the public APIs
// to destructure an event and rebuild it
let wanna_be_expected = v03::full_json_data();
let data: serde_json::Value = wanna_be_expected.try_get_data()?.unwrap();
let bytes = serde_json::to_vec(&data)?;
let expected = EventBuilderV03::from(wanna_be_expected.clone())
.data(wanna_be_expected.get_datacontenttype().unwrap(), bytes)
.build()
.unwrap();
assert_eq!(
v03::full_json_data(),
expected,
BinaryDeserializer::into_event(v03::full_json_data())?
);
Ok(())
@ -35,37 +43,19 @@ fn message_v10_roundtrip_structured() -> Result<()> {
#[test]
fn message_v10_roundtrip_binary() -> Result<()> {
//TODO this code smells because we're missing a proper way in the public APIs
// to destructure an event and rebuild it
let wanna_be_expected = v10::full_json_data();
let data: serde_json::Value = wanna_be_expected.try_get_data()?.unwrap();
let bytes = serde_json::to_vec(&data)?;
let expected = EventBuilderV10::from(wanna_be_expected.clone())
.data(wanna_be_expected.get_datacontenttype().unwrap(), bytes)
.build()
.unwrap();
assert_eq!(
v10::full_json_data(),
expected,
BinaryDeserializer::into_event(v10::full_json_data())?
);
Ok(())
}
#[test]
fn message_v03_invalid_attribute_name() {
assert_eq!(
Error::UnrecognizedAttributeName {
name: "dataschema".to_string()
}
.to_string(),
v03::full_json_data()
.set_attribute("dataschema", MessageAttributeValue::Boolean(true))
.unwrap_err()
.to_string()
)
}
#[test]
fn message_v10_invalid_attribute_name() {
assert_eq!(
Error::UnrecognizedAttributeName {
name: "schemaurl".to_string()
}
.to_string(),
v10::full_json_data()
.set_attribute("schemaurl", MessageAttributeValue::Boolean(true))
.unwrap_err()
.to_string()
)
}