V0.3 implementation (#24)

* Added String variant to Data

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

* Started V0.3 work

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

* Reworked EventDeserializer trait

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

* Now event parsing works with v1 and changes

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

* Cargo fmt

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

* Reorganized test data

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

* Fixed serde for v03

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

* Implemented spec version conversion

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

* cargo fmt

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
This commit is contained in:
Francesco Guardiani 2020-04-10 09:21:26 +02:00 committed by GitHub
parent ad7ec80cde
commit e49453f4a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1089 additions and 255 deletions

View File

@ -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<impl Into<DateTime<Utc>>>);
}
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<impl Into<String>>);
fn set_dataschema(&mut self, dataschema: Option<impl Into<String>>);
@ -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<Utc>> {
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<String>) {
match self {
Attributes::V03(a) => a.set_id(id),
Attributes::V10(a) => a.set_id(id),
}
}
fn set_source(&mut self, source: impl Into<String>) {
match self {
Attributes::V03(a) => a.set_source(source),
Attributes::V10(a) => a.set_source(source),
}
}
fn set_type(&mut self, ty: impl Into<String>) {
match self {
Attributes::V03(a) => a.set_type(ty),
Attributes::V10(a) => a.set_type(ty),
}
}
fn set_subject(&mut self, subject: Option<impl Into<String>>) {
match self {
Attributes::V03(a) => a.set_subject(subject),
Attributes::V10(a) => a.set_subject(subject),
}
}
fn set_time(&mut self, time: Option<impl Into<DateTime<Utc>>>) {
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<impl Into<String>>) {
match self {
Attributes::V03(a) => a.set_datacontenttype(datacontenttype),
Attributes::V10(a) => a.set_datacontenttype(datacontenttype),
}
}
fn set_dataschema(&mut self, dataschema: Option<impl Into<String>>) {
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,
}
}
}

View File

@ -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();
}
}

View File

@ -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<u8>),
/// 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<Data> for serde_json::Value {
fn into(self) -> Data {
Data::Json(self)
@ -44,7 +51,7 @@ impl Into<Data> for Vec<u8> {
impl Into<Data> for String {
fn into(self) -> Data {
Data::Json(self.into())
Data::String(self)
}
}
@ -55,6 +62,7 @@ impl TryFrom<Data> 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<Data> for Vec<u8> {
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<Data> for String {
fn try_from(value: Data) -> Result<Self, Self::Error> {
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),
}
}
}

View File

@ -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;

View File

@ -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<E: serde::de::Error>(
&self,
map: &mut BTreeMap<String, Value>,
) -> Result<Attributes, E>;
fn deserialize_data<E: serde::de::Error>(
&self,
content_type: &str,
map: &mut BTreeMap<String, Value>,
) -> Result<Option<Data>, E>;
fn deserialize_event<E: serde::de::Error>(
mut map: BTreeMap<String, Value>,
) -> Result<Event, E> {
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::<Result<HashMap<String, ExtensionValue>, serde_value::DeserializerError>>()
.map_err(|e| E::custom(e))?;
Ok(Event {
attributes,
data,
extensions,
})
}
}
pub(crate) trait EventSerializer<S: Serializer, A: Sized> {
@ -67,30 +132,11 @@ impl<'de> Deserialize<'de> for Event {
})
.collect::<Result<BTreeMap<String, Value>, <D as Deserializer<'de>>::Error>>()?;
let event_deserializer =
match parse_field!(map, "specversion", String, <D as Deserializer<'de>>::Error)?
.as_str()
{
"1.0" => Ok(EventDeserializerV10 {}),
s => Err(<D as Deserializer<'de>>::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::<Result<HashMap<String, ExtensionValue>, serde_value::DeserializerError>>()
.map_err(|e| <D as Deserializer<'de>>::Error::custom(e))?;
Ok(Event {
attributes,
data,
extensions,
})
match parse_field!(map, "specversion", String, <D as Deserializer<'de>>::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)
}

124
src/event/v03/attributes.rs Normal file
View File

@ -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<String>,
pub(crate) schemaurl: Option<String>,
pub(crate) subject: Option<String>,
pub(crate) time: Option<DateTime<Utc>>,
}
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<Utc>> {
self.time.as_ref()
}
}
impl AttributesWriter for Attributes {
fn set_id(&mut self, id: impl Into<String>) {
self.id = id.into()
}
fn set_source(&mut self, source: impl Into<String>) {
self.source = source.into()
}
fn set_type(&mut self, ty: impl Into<String>) {
self.ty = ty.into()
}
fn set_subject(&mut self, subject: Option<impl Into<String>>) {
self.subject = subject.map(Into::into)
}
fn set_time(&mut self, time: Option<impl Into<DateTime<Utc>>>) {
self.time = time.map(Into::into)
}
}
impl DataAttributesWriter for Attributes {
fn set_datacontenttype(&mut self, datacontenttype: Option<impl Into<String>>) {
self.datacontenttype = datacontenttype.map(Into::into)
}
fn set_dataschema(&mut self, dataschema: Option<impl Into<String>>) {
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,
}
}
}

132
src/event/v03/builder.rs Normal file
View File

@ -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<String>) -> Self {
self.event.set_id(id);
return self;
}
pub fn source(mut self, source: impl Into<String>) -> Self {
self.event.set_source(source);
return self;
}
pub fn ty(mut self, ty: impl Into<String>) -> Self {
self.event.set_type(ty);
return self;
}
pub fn subject(mut self, subject: impl Into<String>) -> Self {
self.event.set_subject(Some(subject));
return self;
}
pub fn time(mut self, time: impl Into<DateTime<Utc>>) -> Self {
self.event.set_time(Some(time));
return self;
}
pub fn extension(
mut self,
extension_name: &str,
extension_value: impl Into<ExtensionValue>,
) -> Self {
self.event.set_extension(extension_name, extension_value);
return self;
}
pub fn data(mut self, datacontenttype: impl Into<String>, data: impl Into<Data>) -> Self {
self.event.write_data(datacontenttype, data);
return self;
}
pub fn data_with_schema(
mut self,
datacontenttype: impl Into<String>,
schemaurl: impl Into<String>,
data: impl Into<Data>,
) -> 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> = 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);
}
}

View File

@ -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;

110
src/event/v03/serde.rs Normal file
View File

@ -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<E: serde::de::Error>(
map: &mut BTreeMap<String, Value>,
) -> Result<crate::event::Attributes, E> {
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::<Utc>::from(d)),
Err(e) => Err(E::invalid_value(
Unexpected::Str(&s),
&e.to_string().as_str(),
)),
})
.transpose()?,
}))
}
fn deserialize_data<E: serde::de::Error>(
content_type: &str,
map: &mut BTreeMap<String, Value>,
) -> Result<Option<Data>, 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<S: serde::Serializer> crate::event::serde::EventSerializer<S, Attributes> for EventSerializer {
fn serialize(
attributes: &Attributes,
data: &Option<Data>,
extensions: &HashMap<String, ExtensionValue>,
serializer: S,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::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()
}
}

View File

@ -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,
}
}
}

View File

@ -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 {

View File

@ -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<E: serde::de::Error>(
&self,
map: &mut BTreeMap<String, Value>,
) -> Result<crate::event::Attributes, E> {
Ok(crate::event::Attributes::V10(Attributes {
@ -34,28 +34,22 @@ impl crate::event::serde::EventDeserializer for EventDeserializer {
}
fn deserialize_data<E: serde::de::Error>(
&self,
content_type: &str,
map: &mut BTreeMap<String, Value>,
) -> Result<Option<Data>, 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<S: serde::Serializer> crate::event::serde::EventSerializer<S, Attributes> 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))?,
_ => (),
};

View File

@ -1,2 +0,0 @@
#[test]
fn use_event() {}

View File

@ -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<Event, serde_json::Error> =
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<Event, serde_json::Error> = serde_json::from_value(in_json);
assert_ok!(&deserialize_result);
let deserialize_json = deserialize_result.unwrap();
assert_eq!(deserialize_json, out_event)
}

58
tests/test_data/data.rs Normal file
View File

@ -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<u8> {
serde_json::to_vec(&json!({"hello": "world"})).unwrap()
}
pub fn xml_data() -> String {
"<hello>world</hello>".to_string()
}
pub fn subject() -> String {
"cloudevents-sdk".to_string()
}
pub fn time() -> DateTime<Utc> {
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)
}

View File

@ -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<u8> {
serde_json::to_vec(&json!({"hello": "world"})).unwrap()
}
pub fn subject() -> String {
"cloudevents-sdk".to_string()
}
pub fn time() -> DateTime<Utc> {
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;

193
tests/test_data/v03.rs Normal file
View File

@ -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()))
})
}

191
tests/test_data/v10.rs Normal file
View File

@ -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()))
})
}

View File

@ -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)
}