From 9e9122385e38bf12a5e64e5b29bbbd235ccabe11 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Mon, 16 Mar 2020 08:50:59 +0100 Subject: [PATCH] EventBuilder (#14) * Created Builder * Now all DateTimes used are Utc (as spec states) * Splitted `write_data` to provide one version with schema and one version without schema Signed-off-by: Francesco Guardiani --- src/event/attributes.rs | 11 ++-- src/event/builder.rs | 21 ++++++ src/event/data.rs | 3 +- src/event/event.rs | 76 +++++++++++++++++----- src/event/extensions.rs | 4 +- src/event/mod.rs | 3 + src/event/v10/attributes.rs | 9 +-- src/event/v10/builder.rs | 125 ++++++++++++++++++++++++++++++++++++ src/event/v10/mod.rs | 2 + src/lib.rs | 1 + 10 files changed, 225 insertions(+), 30 deletions(-) create mode 100644 src/event/builder.rs create mode 100644 src/event/v10/builder.rs diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 9082091..da5a323 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,6 +1,6 @@ use super::SpecVersion; use crate::event::{AttributesV10, ExtensionValue}; -use chrono::{DateTime, FixedOffset}; +use chrono::{DateTime, Utc}; /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { @@ -19,7 +19,7 @@ pub trait AttributesReader { /// Get the [subject](https://github.com/cloudevents/spec/blob/master/spec.md#subject). fn get_subject(&self) -> Option<&str>; /// Get the [time](https://github.com/cloudevents/spec/blob/master/spec.md#time). - fn get_time(&self) -> Option<&DateTime>; + fn get_time(&self) -> Option<&DateTime>; /// Get the [extension](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) named `extension_name` fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; /// Get all the [extensions](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) @@ -31,7 +31,7 @@ pub trait AttributesWriter { fn set_source(&mut self, source: impl Into); fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); - fn set_time(&mut self, time: Option>>); + fn set_time(&mut self, time: Option>>); fn set_extension<'name, 'event: 'name>( &'event mut self, extension_name: &'name str, @@ -48,6 +48,7 @@ pub(crate) trait DataAttributesWriter { fn set_dataschema(&mut self, dataschema: Option>); } +#[derive(PartialEq, Debug, Clone)] pub enum Attributes { V10(AttributesV10), } @@ -95,7 +96,7 @@ impl AttributesReader for Attributes { } } - fn get_time(&self) -> Option<&DateTime> { + fn get_time(&self) -> Option<&DateTime> { match self { Attributes::V10(a) => a.get_time(), } @@ -139,7 +140,7 @@ impl AttributesWriter for Attributes { } } - fn set_time(&mut self, time: Option>>) { + fn set_time(&mut self, time: Option>>) { match self { Attributes::V10(a) => a.set_time(time), } diff --git a/src/event/builder.rs b/src/event/builder.rs new file mode 100644 index 0000000..b72dbc8 --- /dev/null +++ b/src/event/builder.rs @@ -0,0 +1,21 @@ +use super::EventBuilderV10; + +/// Builder to create [`Event`]: +/// ``` +/// use cloudevents::EventBuilder; +/// use chrono::Utc; +/// +/// let event = EventBuilder::v10() +/// .id("my_event.my_application") +/// .source("http://localhost:8080") +/// .time(Utc::now()) +/// .build(); +/// ``` +pub struct EventBuilder {} + +impl EventBuilder { + /// Creates a new builder for CloudEvents V1.0 + pub fn v10() -> EventBuilderV10 { + return EventBuilderV10::new(); + } +} diff --git a/src/event/data.rs b/src/event/data.rs index f8a2be9..1811dd3 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -1,7 +1,6 @@ -use serde::{Deserialize, Serialize}; use std::convert::{Into, TryFrom}; -#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone)] /// Possible data values pub enum Data { String(String), diff --git a/src/event/event.rs b/src/event/event.rs index b2e873e..6b1108d 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -3,14 +3,16 @@ use super::{ SpecVersion, }; use crate::event::attributes::DataAttributesWriter; -use chrono::{DateTime, FixedOffset}; +use chrono::{DateTime, Utc}; use delegate::delegate; use std::convert::TryFrom; /// Data structure that represents a [CloudEvent](https://github.com/cloudevents/spec/blob/master/spec.md). /// It provides methods to get the attributes through [`AttributesReader`] /// and write them through [`AttributesWriter`]. -/// It also provides methods to read and write the [event data](https://github.com/cloudevents/spec/blob/master/spec.md#event-data) +/// It also provides methods to read and write the [event data](https://github.com/cloudevents/spec/blob/master/spec.md#event-data). +/// +/// You can build events using [`EventBuilder`] /// ``` /// use cloudevents::Event; /// use cloudevents::event::AttributesReader; @@ -19,7 +21,6 @@ use std::convert::TryFrom; /// let mut e = Event::default(); /// e.write_data( /// "application/json", -/// None, /// serde_json::json!({"hello": "world"}) /// ); /// @@ -30,6 +31,7 @@ use std::convert::TryFrom; /// let data: serde_json::Value = e.try_get_data().unwrap().unwrap(); /// println!("Event data: {}", data) /// ``` +#[derive(PartialEq, Debug, Clone)] pub struct Event { pub attributes: Attributes, pub data: Option, @@ -45,7 +47,7 @@ impl AttributesReader for Event { fn get_datacontenttype(&self) -> Option<&str>; fn get_dataschema(&self) -> Option<&str>; fn get_subject(&self) -> Option<&str>; - fn get_time(&self) -> Option<&DateTime>; + fn get_time(&self) -> Option<&DateTime>; fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)>; } @@ -59,7 +61,7 @@ impl AttributesWriter for Event { fn set_source(&mut self, source: impl Into); fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); - fn set_time(&mut self, time: Option>>); + fn set_time(&mut self, time: Option>>); fn set_extension<'name, 'event: 'name>( &'event mut self, extension_name: &'name str, @@ -85,9 +87,11 @@ impl Default for Event { impl Event { pub fn remove_data(&mut self) { self.data = None; + self.attributes.set_dataschema(None as Option); + self.attributes.set_datacontenttype(None as Option); } - /// Write data into the `Event`. You must provide a `content_type` and you can optionally provide a `schema`. + /// Write data into the `Event` with the specified `datacontenttype`. /// /// ``` /// use cloudevents::Event; @@ -95,17 +99,33 @@ impl Event { /// use std::convert::Into; /// /// let mut e = Event::default(); - /// e.write_data("application/json", None, json!({})) + /// e.write_data("application/json", json!({})) /// ``` - pub fn write_data, D: Into>( + pub fn write_data(&mut self, datacontenttype: impl Into, data: impl Into) { + self.attributes.set_datacontenttype(Some(datacontenttype)); + self.attributes.set_dataschema(None as Option<&str>); + self.data = Some(data.into()); + } + + /// Write data into the `Event` with the specified `datacontenttype` and `dataschema`. + /// + /// ``` + /// use cloudevents::Event; + /// use serde_json::json; + /// use std::convert::Into; + /// + /// let mut e = Event::default(); + /// e.write_data_with_schema("application/json", "http://myapplication.com/schema", json!({})) + /// ``` + pub fn write_data_with_schema( &mut self, - content_type: S, - schema: Option, - value: D, + datacontenttype: impl Into, + dataschema: impl Into, + data: impl Into, ) { - self.attributes.set_datacontenttype(Some(content_type)); - self.attributes.set_dataschema(schema); - self.data = Some(value.into()); + self.attributes.set_datacontenttype(Some(datacontenttype)); + self.attributes.set_dataschema(Some(dataschema)); + self.data = Some(data.into()); } pub fn get_data>(&self) -> Option { @@ -145,10 +165,34 @@ mod tests { }); let mut e = Event::default(); - e.write_data("application/json", None, expected_data.clone()); + e.write_data_with_schema( + "application/json", + "http://localhost:8080/schema", + expected_data.clone(), + ); let data: serde_json::Value = e.try_get_data().unwrap().unwrap(); assert_eq!(expected_data, data); - assert_eq!("application/json", e.get_datacontenttype().unwrap()) + assert_eq!("application/json", e.get_datacontenttype().unwrap()); + assert_eq!("http://localhost:8080/schema", e.get_dataschema().unwrap()) + } + + #[test] + fn remove_data() { + let mut e = Event::default(); + e.write_data( + "application/json", + serde_json::json!({ + "hello": "world" + }), + ); + + e.remove_data(); + + assert!(e + .try_get_data::() + .is_none()); + assert!(e.get_dataschema().is_none()); + assert!(e.get_datacontenttype().is_none()); } } diff --git a/src/event/extensions.rs b/src/event/extensions.rs index 6baffbe..ac8f001 100644 --- a/src/event/extensions.rs +++ b/src/event/extensions.rs @@ -1,9 +1,7 @@ -use serde::{Deserialize, Serialize}; use serde_json::Value; use std::convert::From; -#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] -#[serde(untagged)] +#[derive(Debug, PartialEq, Clone)] /// Represents all the possible [CloudEvents extension](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) values pub enum ExtensionValue { /// Represents a [`String`](std::string::String) value. diff --git a/src/event/mod.rs b/src/event/mod.rs index 9deb7d0..888b50c 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -1,4 +1,5 @@ mod attributes; +mod builder; mod data; mod event; mod extensions; @@ -6,6 +7,7 @@ mod spec_version; pub use attributes::Attributes; pub use attributes::{AttributesReader, AttributesWriter}; +pub use builder::EventBuilder; pub use data::Data; pub use event::Event; pub use extensions::ExtensionValue; @@ -14,3 +16,4 @@ pub use spec_version::SpecVersion; mod v10; pub use v10::Attributes as AttributesV10; +pub use v10::EventBuilder as EventBuilderV10; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 82ab779..e035cbb 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,10 +1,11 @@ use crate::event::attributes::DataAttributesWriter; use crate::event::{AttributesReader, AttributesWriter, ExtensionValue, SpecVersion}; -use chrono::{DateTime, FixedOffset}; +use chrono::{DateTime, Utc}; use hostname::get_hostname; use std::collections::HashMap; use uuid::Uuid; +#[derive(PartialEq, Debug, Clone)] pub struct Attributes { id: String, ty: String, @@ -12,7 +13,7 @@ pub struct Attributes { datacontenttype: Option, dataschema: Option, subject: Option, - time: Option>, + time: Option>, extensions: HashMap, } @@ -54,7 +55,7 @@ impl AttributesReader for Attributes { } } - fn get_time(&self) -> Option<&DateTime> { + fn get_time(&self) -> Option<&DateTime> { self.time.as_ref() } @@ -87,7 +88,7 @@ impl AttributesWriter for Attributes { self.subject = subject.map(Into::into) } - fn set_time(&mut self, time: Option>>) { + fn set_time(&mut self, time: Option>>) { self.time = time.map(Into::into) } diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs new file mode 100644 index 0000000..662bace --- /dev/null +++ b/src/event/v10/builder.rs @@ -0,0 +1,125 @@ +use super::Attributes as AttributesV10; +use crate::event::{Attributes, AttributesWriter, Data, Event, ExtensionValue}; +use chrono::{DateTime, Utc}; + +pub struct EventBuilder { + event: Event, +} + +impl EventBuilder { + // This works as soon as we have an event version converter + // pub fn from(event: Event) -> Self { + // EventBuilder { event } + // } + + pub fn new() -> Self { + EventBuilder { + event: Event { + attributes: Attributes::V10(AttributesV10::default()), + data: None, + }, + } + } + + 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, + dataschema: impl Into, + data: impl Into, + ) -> Self { + self.event + .write_data_with_schema(datacontenttype, dataschema, 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::V10, 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/v10/mod.rs b/src/event/v10/mod.rs index 6a0db7f..14b945f 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -1,3 +1,5 @@ mod attributes; +mod builder; pub use attributes::Attributes; +pub use builder::EventBuilder; diff --git a/src/lib.rs b/src/lib.rs index 0e56a16..8c9840b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,4 @@ extern crate serde_json; pub mod event; pub use event::Event; +pub use event::EventBuilder;