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 <francescoguard@gmail.com>
This commit is contained in:
Francesco Guardiani 2020-03-16 08:50:59 +01:00 committed by GitHub
parent 5bd7fe3ee1
commit 9e9122385e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 225 additions and 30 deletions

View File

@ -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<FixedOffset>>;
fn get_time(&self) -> Option<&DateTime<Utc>>;
/// 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<String>);
fn set_type(&mut self, ty: impl Into<String>);
fn set_subject(&mut self, subject: Option<impl Into<String>>);
fn set_time(&mut self, time: Option<impl Into<DateTime<FixedOffset>>>);
fn set_time(&mut self, time: Option<impl Into<DateTime<Utc>>>);
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<impl Into<String>>);
}
#[derive(PartialEq, Debug, Clone)]
pub enum Attributes {
V10(AttributesV10),
}
@ -95,7 +96,7 @@ impl AttributesReader for Attributes {
}
}
fn get_time(&self) -> Option<&DateTime<FixedOffset>> {
fn get_time(&self) -> Option<&DateTime<Utc>> {
match self {
Attributes::V10(a) => a.get_time(),
}
@ -139,7 +140,7 @@ impl AttributesWriter for Attributes {
}
}
fn set_time(&mut self, time: Option<impl Into<DateTime<FixedOffset>>>) {
fn set_time(&mut self, time: Option<impl Into<DateTime<Utc>>>) {
match self {
Attributes::V10(a) => a.set_time(time),
}

21
src/event/builder.rs Normal file
View File

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

View File

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

View File

@ -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<Data>,
@ -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<FixedOffset>>;
fn get_time(&self) -> Option<&DateTime<Utc>>;
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<String>);
fn set_type(&mut self, ty: impl Into<String>);
fn set_subject(&mut self, subject: Option<impl Into<String>>);
fn set_time(&mut self, time: Option<impl Into<DateTime<FixedOffset>>>);
fn set_time(&mut self, time: Option<impl Into<DateTime<Utc>>>);
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<String>);
self.attributes.set_datacontenttype(None as Option<String>);
}
/// 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<S: Into<String>, D: Into<Data>>(
pub fn write_data(&mut self, datacontenttype: impl Into<String>, data: impl Into<Data>) {
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<S>,
value: D,
datacontenttype: impl Into<String>,
dataschema: impl Into<String>,
data: impl Into<Data>,
) {
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<T: Sized + From<Data>>(&self) -> Option<T> {
@ -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::<serde_json::Value, serde_json::Error>()
.is_none());
assert!(e.get_dataschema().is_none());
assert!(e.get_datacontenttype().is_none());
}
}

View File

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

View File

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

View File

@ -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<String>,
dataschema: Option<String>,
subject: Option<String>,
time: Option<DateTime<FixedOffset>>,
time: Option<DateTime<Utc>>,
extensions: HashMap<String, ExtensionValue>,
}
@ -54,7 +55,7 @@ impl AttributesReader for Attributes {
}
}
fn get_time(&self) -> Option<&DateTime<FixedOffset>> {
fn get_time(&self) -> Option<&DateTime<Utc>> {
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<impl Into<DateTime<FixedOffset>>>) {
fn set_time(&mut self, time: Option<impl Into<DateTime<Utc>>>) {
self.time = time.map(Into::into)
}

125
src/event/v10/builder.rs Normal file
View File

@ -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<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>,
dataschema: impl Into<String>,
data: impl Into<Data>,
) -> 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> = 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);
}
}

View File

@ -1,3 +1,5 @@
mod attributes;
mod builder;
pub use attributes::Attributes;
pub use builder::EventBuilder;

View File

@ -4,3 +4,4 @@ extern crate serde_json;
pub mod event;
pub use event::Event;
pub use event::EventBuilder;