From cc99f21a358c969f3a610fbd508a10cb2af5a38e Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Mon, 16 Mar 2020 20:56:25 +0100 Subject: [PATCH 01/56] Reworked try_get_data (#17) Signed-off-by: Francesco Guardiani Signed-off-by: Pranav Bhatt --- src/event/event.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/event/event.rs b/src/event/event.rs index 6b1108d0..1f33dc36 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -135,22 +135,20 @@ impl Event { } } - pub fn try_get_data, E: std::error::Error>( - &self, - ) -> Option> { + pub fn try_get_data>(&self) -> Result, T::Error> { match self.data.as_ref() { Some(d) => Some(T::try_from(d.clone())), None => None, } + .transpose() } - pub fn into_data, E: std::error::Error>( - self, - ) -> Option> { + pub fn into_data>(self) -> Result, T::Error> { match self.data { Some(d) => Some(T::try_from(d)), None => None, } + .transpose() } } @@ -189,9 +187,7 @@ mod tests { e.remove_data(); - assert!(e - .try_get_data::() - .is_none()); + assert!(e.try_get_data::().unwrap().is_none()); assert!(e.get_dataschema().is_none()); assert!(e.get_datacontenttype().is_none()); } From 8e1547c2e2ce9fbbf5e8669d440295c8a84106c8 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Tue, 17 Mar 2020 08:21:59 +0100 Subject: [PATCH 02/56] Added CONTRIBUTING.md (#19) Signed-off-by: Francesco Guardiani Signed-off-by: Pranav Bhatt --- CONTRIBUTING.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a7614ad1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,129 @@ +# Contributing to CloudEvents SDK Rust + +This page contains information about reporting issues, how to suggest changes as +well as the guidelines we follow for how our documents are formatted. + +## Table of Contents + +- [Reporting an Issue](#reporting-an-issue) +- [Preparing the environment](#preparing-the-environment) +- [Suggesting a Change](#suggesting-a-change) + +## Reporting an Issue + +To report an issue, or to suggest an idea for a change that you haven't had time +to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It +is best to check our existing +[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar +one has already been opened and discussed. + +## Preparing the environment + +In order to start developing this project, +you need to install the Rust tooling using [rustup](https://rustup.rs/). + +### Development commands + +To build the project: + +```sh +cargo build --all-features +``` + +To run all tests: + +```sh +cargo test --all-features +``` + +To build and open the documentation: + +```sh +cargo doc --lib --open +``` + +To run the code formatter: + +```sh +cargo fmt +``` + +## Suggesting a change + +To suggest a change to this repository, submit a +[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete +set of changes you'd like to see. See the +[Spec Formatting Conventions](#spec-formatting-conventions) section for the +guidelines we follow for how documents are formatted. + +Each PR must be signed per the following section. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. + +Note: If your git config information is set properly then viewing the `git log` +information for your commit will look something like this: + +``` +Author: Joe Smith +Date: Thu Feb 2 11:41:15 2018 -0800 + + Update README + + Signed-off-by: Joe Smith +``` + +Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will +be rejected by the automated DCO check. diff --git a/README.md b/README.md index 23849afd..2e9344c7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ -# CloudEvents Rust SDK +# CloudEvents SDK Rust Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) + +## Development & Contributing + +If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) \ No newline at end of file From ed27147168a0dcce43e35b47e83311eec9ea2146 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Thu, 19 Mar 2020 08:26:30 +0100 Subject: [PATCH 03/56] Serde (#18) Signed-off-by: Francesco Guardiani Co-authored-by: Fabrizio Lazzaretti Signed-off-by: Pranav Bhatt --- Cargo.lock | 48 ++++++++++ Cargo.toml | 4 + src/event/attributes.rs | 5 +- src/event/data.rs | 62 ++++++++++-- src/event/event.rs | 10 +- src/event/extensions.rs | 20 +--- src/event/spec_version.rs | 5 +- src/event/v10/attributes.rs | 9 +- tests/serde_json.rs | 36 +++++++ tests/test_data/mod.rs | 183 ++++++++++++++++++++++++++++++++++++ 10 files changed, 350 insertions(+), 32 deletions(-) create mode 100644 tests/serde_json.rs create mode 100644 tests/test_data/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 8516a3cd..40f12fb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,14 +30,25 @@ dependencies = [ "time", ] +[[package]] +name = "claim" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2e893ee68bf12771457cceea72497bc9cb7da404ec8a5311226d354b895ba4" +dependencies = [ + "autocfg", +] + [[package]] name = "cloudevents-sdk" version = "0.0.1" dependencies = [ "base64", "chrono", + "claim", "delegate", "hostname", + "rstest", "serde", "serde_json", "uuid", @@ -177,12 +188,49 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "rstest" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d5f9396fa6a44e2aa2068340b17208794515e2501c5bf3e680a0c3422a5971" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.104" diff --git a/Cargo.toml b/Cargo.toml index d678c6f2..f7d244aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,9 @@ uuid = { version = "^0.8", features = ["serde", "v4"] } hostname = "^0.1" base64 = "^0.12" +[dev-dependencies] +rstest = "0.6" +claim = "0.3.1" + [lib] name = "cloudevents" diff --git a/src/event/attributes.rs b/src/event/attributes.rs index da5a3232..df6916ca 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,6 +1,7 @@ use super::SpecVersion; use crate::event::{AttributesV10, ExtensionValue}; use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { @@ -48,8 +49,10 @@ pub(crate) trait DataAttributesWriter { fn set_dataschema(&mut self, dataschema: Option>); } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "specversion")] pub enum Attributes { + #[serde(rename = "1.0")] V10(AttributesV10), } diff --git a/src/event/data.rs b/src/event/data.rs index 1811dd35..96aed588 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -1,10 +1,17 @@ +use serde::de::Visitor; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::{Into, TryFrom}; +use std::fmt::{self, Formatter}; -#[derive(Debug, PartialEq, Clone)] -/// Possible data values +/// Event [data attribute](https://github.com/cloudevents/spec/blob/master/spec.md#event-data) representation +/// +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Data { - String(String), + #[serde(rename = "data_base64")] + #[serde(serialize_with = "serialize_base64")] + #[serde(deserialize_with = "deserialize_base64")] Binary(Vec), + #[serde(rename = "data")] Json(serde_json::Value), } @@ -30,6 +37,37 @@ impl Data { } } +fn serialize_base64(data: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&base64::encode(&data)) +} + +struct Base64Visitor; + +impl<'de> Visitor<'de> for Base64Visitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a Base64 encoded string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + base64::decode(v).map_err(|e| serde::de::Error::custom(e.to_string())) + } +} + +fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + deserializer.deserialize_str(Base64Visitor) +} + impl Into for serde_json::Value { fn into(self) -> Data { Data::Json(self) @@ -44,7 +82,7 @@ impl Into for Vec { impl Into for String { fn into(self) -> Data { - Data::String(self) + Data::Json(self.into()) } } @@ -53,21 +91,31 @@ impl TryFrom for serde_json::Value { fn try_from(value: Data) -> Result { match value { - Data::String(s) => Ok(serde_json::from_str(&s)?), Data::Binary(v) => Ok(serde_json::from_slice(&v)?), Data::Json(v) => Ok(v), } } } +impl TryFrom for Vec { + type Error = serde_json::Error; + + fn try_from(value: Data) -> Result { + match value { + Data::Binary(v) => Ok(serde_json::from_slice(&v)?), + Data::Json(v) => Ok(serde_json::to_vec(&v)?), + } + } +} + impl TryFrom for String { type Error = std::string::FromUtf8Error; fn try_from(value: Data) -> Result { match value { - Data::String(s) => Ok(s), Data::Binary(v) => Ok(String::from_utf8(v)?), - Data::Json(s) => Ok(s.to_string()), + Data::Json(serde_json::Value::String(s)) => Ok(s), // Return the string without quotes + Data::Json(v) => Ok(v.to_string()), } } } diff --git a/src/event/event.rs b/src/event/event.rs index 1f33dc36..4d6a063a 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -5,6 +5,7 @@ use super::{ use crate::event::attributes::DataAttributesWriter; use chrono::{DateTime, Utc}; use delegate::delegate; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; /// Data structure that represents a [CloudEvent](https://github.com/cloudevents/spec/blob/master/spec.md). @@ -31,10 +32,15 @@ use std::convert::TryFrom; /// let data: serde_json::Value = e.try_get_data().unwrap().unwrap(); /// println!("Event data: {}", data) /// ``` -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] pub struct Event { - pub attributes: Attributes, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "data_base64")] + #[serde(alias = "data")] + #[serde(flatten)] pub data: Option, + #[serde(flatten)] + pub attributes: Attributes, } impl AttributesReader for Event { diff --git a/src/event/extensions.rs b/src/event/extensions.rs index ac8f0015..3abca5c1 100644 --- a/src/event/extensions.rs +++ b/src/event/extensions.rs @@ -1,7 +1,8 @@ -use serde_json::Value; +use serde::{Deserialize, Serialize}; use std::convert::From; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged)] /// 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. @@ -10,8 +11,6 @@ pub enum ExtensionValue { Boolean(bool), /// Represents an integer [`i64`](i64) value. Integer(i64), - /// Represents a [Json `Value`](serde_json::value::Value). - Json(Value), } impl From for ExtensionValue { @@ -32,12 +31,6 @@ impl From for ExtensionValue { } } -impl From for ExtensionValue { - fn from(s: Value) -> Self { - ExtensionValue::Json(s) - } -} - impl ExtensionValue { pub fn from_string(s: S) -> Self where @@ -59,11 +52,4 @@ impl ExtensionValue { { ExtensionValue::from(s.into()) } - - pub fn from_json_value(s: S) -> Self - where - S: Into, - { - ExtensionValue::from(s.into()) - } } diff --git a/src/event/spec_version.rs b/src/event/spec_version.rs index 113fd906..d14cb088 100644 --- a/src/event/spec_version.rs +++ b/src/event/spec_version.rs @@ -1,12 +1,9 @@ -use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone)] pub enum SpecVersion { - #[serde(rename = "0.3")] V03, - #[serde(rename = "1.0")] V10, } diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index e035cbbc..6aa9d0db 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -2,18 +2,25 @@ use crate::event::attributes::DataAttributesWriter; use crate::event::{AttributesReader, AttributesWriter, ExtensionValue, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use uuid::Uuid; -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] pub struct Attributes { id: String, + #[serde(rename = "type")] ty: String, source: String, + #[serde(skip_serializing_if = "Option::is_none")] datacontenttype: Option, + #[serde(skip_serializing_if = "Option::is_none")] dataschema: Option, + #[serde(skip_serializing_if = "Option::is_none")] subject: Option, + #[serde(skip_serializing_if = "Option::is_none")] time: Option>, + #[serde(flatten)] extensions: HashMap, } diff --git a/tests/serde_json.rs b/tests/serde_json.rs new file mode 100644 index 00000000..ac54cf02 --- /dev/null +++ b/tests/serde_json.rs @@ -0,0 +1,36 @@ +use claim::*; +use cloudevents::Event; +use rstest::rstest; +use serde_json::Value; + +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()) +)] +fn serialize_deserialize_should_succeed(event: Event, expected_json: Value) { + // Event -> serde_json::Value + let serialize_result = serde_json::to_value(event.clone()); + assert_ok!(&serialize_result); + let actual_json = serialize_result.unwrap(); + assert_eq!(&actual_json, &expected_json); + + // serde_json::Value -> String + let actual_json_serialized = actual_json.to_string(); + assert_eq!(actual_json_serialized, expected_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) +} diff --git a/tests/test_data/mod.rs b/tests/test_data/mod.rs new file mode 100644 index 00000000..cd582d97 --- /dev/null +++ b/tests/test_data/mod.rs @@ -0,0 +1,183 @@ +use chrono::{DateTime, TimeZone, Utc}; +use cloudevents::{Event, EventBuilder}; +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 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 + }) +} From 8b9587361caa2eff8a55c9b2eaaf078dd8f64d96 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 20 Mar 2020 10:31:09 -0400 Subject: [PATCH 04/56] just to align with other sdks (#21) Signed-off-by: Doug Davis Signed-off-by: Pranav Bhatt --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e9344c7..7fe30cc0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,25 @@ Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) +## Status + +This SDK current supports the following versions of CloudEvents: +- TBD + ## Development & Contributing -If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) \ No newline at end of file +If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) + +## Community + +- There are bi-weekly calls immediately following the + [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) + at 9am PT (US Pacific). Which means they will typically start at 10am PT, but + if the other call ends early then the SDK call will start early as well. See + the + [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) + to determine which week will have the call. +- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under + [CNCF's Slack workspace](https://slack.cncf.io/). +- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` + on slack). From fe20dcaa04eb09fd8de98d2f888220db3ea10657 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 20 Mar 2020 10:55:41 -0400 Subject: [PATCH 05/56] add link to email (#22) Signed-off-by: Doug Davis Signed-off-by: Pranav Bhatt --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7fe30cc0..9abde530 100644 --- a/README.md +++ b/README.md @@ -22,5 +22,6 @@ If you're interested in contributing to sdk-rust, look at [Contributing document to determine which week will have the call. - Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under [CNCF's Slack workspace](https://slack.cncf.io/). +- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk - Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` on slack). From b889cb4c56d0a1c9b54e77b4ba4a931f8909fe5f Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Fri, 20 Mar 2020 13:21:16 +0100 Subject: [PATCH 06/56] WIP iter attributes Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 42 +++++++++++++++++-- src/event/event.rs | 2 +- .../{extensions.rs => extension_value.rs} | 0 src/event/mod.rs | 4 +- src/event/v10/attributes.rs | 7 +--- 5 files changed, 44 insertions(+), 11 deletions(-) rename src/event/{extensions.rs => extension_value.rs} (100%) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index df6916ca..5961ae89 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -2,6 +2,42 @@ use super::SpecVersion; use crate::event::{AttributesV10, ExtensionValue}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use std::fmt; + +impl ExactSizeIterator for Iter { + type Item = (&'a str, AttributeValue<'a>); + + fn next(&mut self) -> Option { + let new_next = self.curr + self.next; + + self.curr = self.next; + self.next = new_next; + + // Since there's no endpoint to a Fibonacci sequence, the `Iterator` + // will never return `None`, and `Some` is always returned. + Some(self.curr) + } +} + +pub enum AttributeValue<'a> { + SpecVersion(SpecVersion), + String(&'a str), + URI(&'a str), + URIRef(&'a str), + Time(&'a DateTime) +} + +impl fmt::Display for AttributeValue<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AttributeValue::SpecVersion(s) => s.fmt(f), + AttributeValue::String(s) => f.write_str(s), + AttributeValue::URI(s) => f.write_str(s), + AttributeValue::URIRef(s) => f.write_str(s), + AttributeValue::Time(s) => f.write_str(&s.to_rfc2822()), + } + } +} /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { @@ -24,7 +60,7 @@ pub trait AttributesReader { /// 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) - fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)>; + fn iter_extensions(&self) -> std::collections::hash_map::Iter; } pub trait AttributesWriter { @@ -111,9 +147,9 @@ impl AttributesReader for Attributes { } } - fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> { + fn iter_extensions(&self) -> std::collections::hash_map::Iter { match self { - Attributes::V10(a) => a.get_extensions(), + Attributes::V10(a) => a.iter_extensions(), } } } diff --git a/src/event/event.rs b/src/event/event.rs index 4d6a063a..2db0e276 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -55,7 +55,7 @@ impl AttributesReader for Event { fn get_subject(&self) -> Option<&str>; fn get_time(&self) -> Option<&DateTime>; fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; - fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)>; + fn iter_extensions(&self) -> std::collections::hash_map::Iter; } } } diff --git a/src/event/extensions.rs b/src/event/extension_value.rs similarity index 100% rename from src/event/extensions.rs rename to src/event/extension_value.rs diff --git a/src/event/mod.rs b/src/event/mod.rs index 888b50c8..46be8f71 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -2,7 +2,7 @@ mod attributes; mod builder; mod data; mod event; -mod extensions; +mod extension_value; mod spec_version; pub use attributes::Attributes; @@ -10,7 +10,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extensions::ExtensionValue; +pub use extension_value::ExtensionValue; pub use spec_version::SpecVersion; mod v10; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 6aa9d0db..5e94eae2 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -70,11 +70,8 @@ impl AttributesReader for Attributes { self.extensions.get(extension_name) } - fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> { - self.extensions - .iter() - .map(|(k, v)| (k.as_str(), v)) - .collect() + fn iter_extensions(&self) -> std::collections::hash_map::Iter { + self.extensions.iter() } } From 1f6f88664d22ca644d6fb67d81aac2c354d36e0a Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Tue, 14 Apr 2020 18:24:42 +0530 Subject: [PATCH 07/56] adding iterator to v10/attributes (WIP) Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 15 --------- src/event/v10/attributes.rs | 63 ++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 5961ae89..864a660f 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -4,21 +4,6 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; -impl ExactSizeIterator for Iter { - type Item = (&'a str, AttributeValue<'a>); - - fn next(&mut self) -> Option { - let new_next = self.curr + self.next; - - self.curr = self.next; - self.next = new_next; - - // Since there's no endpoint to a Fibonacci sequence, the `Iterator` - // will never return `None`, and `Some` is always returned. - Some(self.curr) - } -} - pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 5e94eae2..69d1589d 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,6 +1,7 @@ -use crate::event::attributes::DataAttributesWriter; +use crate::event::attributes::{DataAttributesWriter, AttributeValue}; use crate::event::{AttributesReader, AttributesWriter, ExtensionValue, SpecVersion}; use chrono::{DateTime, Utc}; +use chrono::NaiveDate; use hostname::get_hostname; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -24,6 +25,66 @@ pub struct Attributes { extensions: HashMap, } +impl<'a> IntoIterator for &'a Attributes { + type Item = (&'a str, AttributeValue<'a>); + type IntoIter = AttributesIntoIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + AttributesIntoIterator { + attributes: self, + index: 0, + } + } +} + +fn time_to_string(input:&Option>) -> &str { + let result = match *input { + Some(x) => &x.to_rfc2822(), + None => "", + }; + result +} + +fn option_to_time(input:&Option>) -> &DateTime { + let result = match *input { + Some(x) => &x, + None => &DateTime::::from_utc(NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0), Utc), + }; + result +} + +fn option_to_string(input:&Option) -> &str { + let result = match *input { + Some(x) => &x[..], + None => "", + }; + result +} + +struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, +} + +impl<'a> Iterator for AttributesIntoIterator<'a> { + type Item = (&'a str, AttributeValue<'a>); + fn next(&mut self) -> Option { + let result = match self.index { + 0 => ("id", AttributeValue::String(&self.attributes.id[..])), + 1 => ("ty", AttributeValue::String(&self.attributes.ty[..])), + 2 => ("source", AttributeValue::String(&self.attributes.source[..])), + 3 => ("datacontenttype", AttributeValue::String(option_to_string(&self.attributes.datacontenttype))), + 4 => ("dataschema", AttributeValue::String(option_to_string(&self.attributes.dataschema))), + 5 => ("subject", AttributeValue::String(option_to_string(&self.attributes.subject))), + 6 => ("time", AttributeValue::Time(option_to_time(&self.attributes.time))), + 7 => ("extensions",self.attributes.extensions), + _ => return None, + }; + self.index += 1; + Some(result) + } +} + impl AttributesReader for Attributes { fn get_id(&self) -> &str { &self.id From 846d3770a30befe7d0fc3df5ee73b7789145b7b8 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Tue, 14 Apr 2020 23:08:53 +0530 Subject: [PATCH 08/56] rebasing upstream Signed-off-by: Pranav Bhatt --- src/event/v10/attributes.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 69d1589d..9b6592c7 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -37,14 +37,6 @@ impl<'a> IntoIterator for &'a Attributes { } } -fn time_to_string(input:&Option>) -> &str { - let result = match *input { - Some(x) => &x.to_rfc2822(), - None => "", - }; - result -} - fn option_to_time(input:&Option>) -> &DateTime { let result = match *input { Some(x) => &x, @@ -70,14 +62,13 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { type Item = (&'a str, AttributeValue<'a>); fn next(&mut self) -> Option { let result = match self.index { - 0 => ("id", AttributeValue::String(&self.attributes.id[..])), - 1 => ("ty", AttributeValue::String(&self.attributes.ty[..])), - 2 => ("source", AttributeValue::String(&self.attributes.source[..])), + 0 => ("id", AttributeValue::String(&self.attributes.id)), + 1 => ("ty", AttributeValue::String(&self.attributes.ty)), + 2 => ("source", AttributeValue::String(&self.attributes.source)), 3 => ("datacontenttype", AttributeValue::String(option_to_string(&self.attributes.datacontenttype))), 4 => ("dataschema", AttributeValue::String(option_to_string(&self.attributes.dataschema))), 5 => ("subject", AttributeValue::String(option_to_string(&self.attributes.subject))), 6 => ("time", AttributeValue::Time(option_to_time(&self.attributes.time))), - 7 => ("extensions",self.attributes.extensions), _ => return None, }; self.index += 1; From d698cb589296909248f84b1bee0d70654dff7f6f Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 12:11:00 +0530 Subject: [PATCH 09/56] Completed Iterator Signed-off-by: Pranav Bhatt --- src/event/v10/attributes.rs | 115 +++++++++++++++--------------------- 1 file changed, 49 insertions(+), 66 deletions(-) diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 9b6592c7..fecd09fe 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,28 +1,18 @@ -use crate::event::attributes::{DataAttributesWriter, AttributeValue}; -use crate::event::{AttributesReader, AttributesWriter, ExtensionValue, SpecVersion}; +use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; +use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; -use chrono::NaiveDate; use hostname::get_hostname; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use uuid::Uuid; -#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Debug, Clone)] pub struct Attributes { - id: String, - #[serde(rename = "type")] - ty: String, - source: String, - #[serde(skip_serializing_if = "Option::is_none")] - datacontenttype: Option, - #[serde(skip_serializing_if = "Option::is_none")] - dataschema: Option, - #[serde(skip_serializing_if = "Option::is_none")] - subject: Option, - #[serde(skip_serializing_if = "Option::is_none")] - time: Option>, - #[serde(flatten)] - extensions: HashMap, + pub(crate) id: String, + pub(crate) ty: String, + pub(crate) source: String, + pub(crate) datacontenttype: Option, + pub(crate) dataschema: Option, + pub(crate) subject: Option, + pub(crate) time: Option>, } impl<'a> IntoIterator for &'a Attributes { @@ -37,42 +27,42 @@ impl<'a> IntoIterator for &'a Attributes { } } -fn option_to_time(input:&Option>) -> &DateTime { - let result = match *input { - Some(x) => &x, - None => &DateTime::::from_utc(NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0), Utc), - }; - result +struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, } -fn option_to_string(input:&Option) -> &str { - let result = match *input { - Some(x) => &x[..], - None => "", +fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::String(x))), + None => None, }; result } -struct AttributesIntoIterator<'a> { - attributes: &'a Attributes, - index: usize, +fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::Time(x))), + None => None, + }; + result } impl<'a> Iterator for AttributesIntoIterator<'a> { type Item = (&'a str, AttributeValue<'a>); fn next(&mut self) -> Option { let result = match self.index { - 0 => ("id", AttributeValue::String(&self.attributes.id)), - 1 => ("ty", AttributeValue::String(&self.attributes.ty)), - 2 => ("source", AttributeValue::String(&self.attributes.source)), - 3 => ("datacontenttype", AttributeValue::String(option_to_string(&self.attributes.datacontenttype))), - 4 => ("dataschema", AttributeValue::String(option_to_string(&self.attributes.dataschema))), - 5 => ("subject", AttributeValue::String(option_to_string(&self.attributes.subject))), - 6 => ("time", AttributeValue::Time(option_to_time(&self.attributes.time))), + 0 => Some(("id", AttributeValue::String(&self.attributes.id))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), + 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), + 5 => option_checker_string("subject",self.attributes.subject.get_subject()), + 6 => option_checker_time("time",self.attributes.time.get_time()), _ => return None, }; self.index += 1; - Some(result) + result } } @@ -117,14 +107,6 @@ impl AttributesReader for Attributes { fn get_time(&self) -> Option<&DateTime> { self.time.as_ref() } - - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - self.extensions.get(extension_name) - } - - fn iter_extensions(&self) -> std::collections::hash_map::Iter { - self.extensions.iter() - } } impl AttributesWriter for Attributes { @@ -147,22 +129,6 @@ impl AttributesWriter for Attributes { fn set_time(&mut self, time: Option>>) { self.time = time.map(Into::into) } - - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { - self.extensions - .insert(extension_name.to_owned(), extension_value.into()); - } - - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option { - self.extensions.remove(extension_name) - } } impl DataAttributesWriter for Attributes { @@ -185,7 +151,24 @@ impl Default for Attributes { dataschema: None, subject: None, time: None, - extensions: HashMap::new(), + } + } +} + +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, } } } From f5cd2bca74f75151fd6eb5d34e3a59c9b455c4ab Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Thu, 26 Mar 2020 18:30:30 +0100 Subject: [PATCH 10/56] Implemented custom deserialization process Signed-off-by: Francesco Guardiani Signed-off-by: Pranav Bhatt --- Cargo.lock | 30 +++++++++++ Cargo.toml | 2 + README.md | 15 ++++-- src/event/attributes.rs | 48 +---------------- src/event/data.rs | 29 +---------- src/event/event.rs | 52 ++++++++++++------ src/event/mod.rs | 5 +- src/event/serde.rs | 110 +++++++++++++++++++++++++++++++++++++++ src/event/v10/builder.rs | 2 + src/event/v10/mod.rs | 2 + src/event/v10/serde.rs | 59 +++++++++++++++++++++ src/lib.rs | 1 + 12 files changed, 262 insertions(+), 93 deletions(-) create mode 100644 src/event/serde.rs create mode 100644 src/event/v10/serde.rs diff --git a/Cargo.lock b/Cargo.lock index 40f12fb8..f40b1b79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,8 @@ dependencies = [ "hostname", "rstest", "serde", + "serde-transcode", + "serde-value", "serde_json", "uuid", ] @@ -117,6 +119,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "ordered-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +dependencies = [ + "num-traits", +] + [[package]] name = "ppv-lite86" version = "0.2.6" @@ -240,6 +251,25 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-transcode" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97528f0dfcf8ce2d51d995cb513a103b9cd301dc3f387a9cae5ef974381d4e1c" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-value" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.104" diff --git a/Cargo.toml b/Cargo.toml index f7d244aa..b4c5a5a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ repository = "https://github.com/cloudevents/sdk-rust" [dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" +serde-value = "^0.6" +serde-transcode = "^1.1" chrono = { version = "^0.4", features = ["serde"] } delegate = "^0.4" uuid = { version = "^0.8", features = ["serde", "v4"] } diff --git a/README.md b/README.md index 9abde530..e3fca189 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,19 @@ Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) -## Status +## Spec support -This SDK current supports the following versions of CloudEvents: -- TBD +| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | +| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | +| CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: | +| AMQP Protocol Binding | :x: | :x: | +| AVRO Event Format | :x: | :x: | +| HTTP Protocol Binding | :x: | :x: | +| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: | +| Kafka Protocol Binding | :x: | :x: | +| MQTT Protocol Binding | :x: | :x: | +| NATS Protocol Binding | :x: | :x: | +| Web hook | :x: | :x: | ## Development & Contributing diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 864a660f..91025b79 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,5 +1,5 @@ use super::SpecVersion; -use crate::event::{AttributesV10, ExtensionValue}; +use crate::event::AttributesV10; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -42,10 +42,6 @@ pub trait AttributesReader { 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>; - /// 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) - fn iter_extensions(&self) -> std::collections::hash_map::Iter; } pub trait AttributesWriter { @@ -54,15 +50,6 @@ pub trait AttributesWriter { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ); - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option; } pub(crate) trait DataAttributesWriter { @@ -70,7 +57,7 @@ pub(crate) trait DataAttributesWriter { fn set_dataschema(&mut self, dataschema: Option>); } -#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Debug, Clone, Serialize)] #[serde(tag = "specversion")] pub enum Attributes { #[serde(rename = "1.0")] @@ -125,18 +112,6 @@ impl AttributesReader for Attributes { Attributes::V10(a) => a.get_time(), } } - - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - match self { - Attributes::V10(a) => a.get_extension(extension_name), - } - } - - fn iter_extensions(&self) -> std::collections::hash_map::Iter { - match self { - Attributes::V10(a) => a.iter_extensions(), - } - } } impl AttributesWriter for Attributes { @@ -169,25 +144,6 @@ impl AttributesWriter for Attributes { Attributes::V10(a) => a.set_time(time), } } - - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { - match self { - Attributes::V10(a) => a.set_extension(extension_name, extension_value), - } - } - - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option { - match self { - Attributes::V10(a) => a.remove_extension(extension_name), - } - } } impl DataAttributesWriter for Attributes { diff --git a/src/event/data.rs b/src/event/data.rs index 96aed588..72649be2 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -1,15 +1,14 @@ use serde::de::Visitor; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Serialize, Serializer}; use std::convert::{Into, TryFrom}; use std::fmt::{self, Formatter}; /// Event [data attribute](https://github.com/cloudevents/spec/blob/master/spec.md#event-data) representation /// -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Clone, Serialize)] pub enum Data { #[serde(rename = "data_base64")] #[serde(serialize_with = "serialize_base64")] - #[serde(deserialize_with = "deserialize_base64")] Binary(Vec), #[serde(rename = "data")] Json(serde_json::Value), @@ -44,30 +43,6 @@ where serializer.serialize_str(&base64::encode(&data)) } -struct Base64Visitor; - -impl<'de> Visitor<'de> for Base64Visitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("a Base64 encoded string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - base64::decode(v).map_err(|e| serde::de::Error::custom(e.to_string())) - } -} - -fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - deserializer.deserialize_str(Base64Visitor) -} - impl Into for serde_json::Value { fn into(self) -> Data { Data::Json(self) diff --git a/src/event/event.rs b/src/event/event.rs index 2db0e276..60617885 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -5,7 +5,8 @@ use super::{ use crate::event::attributes::DataAttributesWriter; use chrono::{DateTime, Utc}; use delegate::delegate; -use serde::{Deserialize, Serialize}; +use serde::Serialize; +use std::collections::HashMap; use std::convert::TryFrom; /// Data structure that represents a [CloudEvent](https://github.com/cloudevents/spec/blob/master/spec.md). @@ -32,15 +33,15 @@ use std::convert::TryFrom; /// let data: serde_json::Value = e.try_get_data().unwrap().unwrap(); /// println!("Event data: {}", data) /// ``` -#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Debug, Clone, Serialize)] pub struct Event { + #[serde(flatten)] + pub attributes: Attributes, #[serde(skip_serializing_if = "Option::is_none")] - #[serde(alias = "data_base64")] - #[serde(alias = "data")] #[serde(flatten)] pub data: Option, #[serde(flatten)] - pub attributes: Attributes, + pub extensions: HashMap, } impl AttributesReader for Event { @@ -54,8 +55,6 @@ impl AttributesReader for Event { fn get_dataschema(&self) -> Option<&str>; fn get_subject(&self) -> Option<&str>; fn get_time(&self) -> Option<&DateTime>; - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; - fn iter_extensions(&self) -> std::collections::hash_map::Iter; } } } @@ -68,15 +67,6 @@ impl AttributesWriter for Event { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ); - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option; } } } @@ -86,6 +76,7 @@ impl Default for Event { Event { attributes: Attributes::V10(AttributesV10::default()), data: None, + extensions: HashMap::default(), } } } @@ -156,6 +147,35 @@ impl Event { } .transpose() } + + /// Get the [extension](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) named `extension_name` + pub fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { + self.extensions.get(extension_name) + } + + /// Get all the [extensions](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) + pub fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> { + self.extensions + .iter() + .map(|(k, v)| (k.as_str(), v)) + .collect() + } + + pub fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ) { + self.extensions + .insert(extension_name.to_owned(), extension_value.into()); + } + + pub fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option { + self.extensions.remove(extension_name) + } } #[cfg(test)] diff --git a/src/event/mod.rs b/src/event/mod.rs index 46be8f71..3a89441d 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -2,7 +2,9 @@ mod attributes; mod builder; mod data; mod event; -mod extension_value; +mod extensions; +#[macro_use] +mod serde; mod spec_version; pub use attributes::Attributes; @@ -17,3 +19,4 @@ mod v10; pub use v10::Attributes as AttributesV10; pub use v10::EventBuilder as EventBuilderV10; +pub(crate) use v10::EventDeserializer as EventDeseriazerV10; diff --git a/src/event/serde.rs b/src/event/serde.rs new file mode 100644 index 00000000..7b5d5ef7 --- /dev/null +++ b/src/event/serde.rs @@ -0,0 +1,110 @@ +use super::{Attributes, Data, Event, EventDeseriazerV10}; +use serde::de::{Error, Unexpected, IntoDeserializer}; +use serde::{Deserialize, Deserializer}; +use serde_value::Value; +use std::collections::{BTreeMap, HashMap}; +use crate::event::ExtensionValue; + +const SPEC_VERSIONS: [&'static str; 1] = ["1.0"]; + +macro_rules! parse_optional_field { + ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { + $map.remove($name) + .map(|val| match val { + Value::$value_variant(v) => Ok(v), + other => Err(<$error>::invalid_type( + crate::event::serde::value_to_unexpected(&other), + &stringify!($value_variant), + )), + }) + .transpose() + }; +} + +macro_rules! parse_field { + ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { + parse_optional_field!($map, $name, $value_variant, $error)? + .ok_or_else(|| <$error>::missing_field($name)) + }; +} + +pub(crate) trait EventDeserializer { + fn deserialize_attributes( + &self, + map: &mut BTreeMap, + ) -> Result; + + fn deserialize_data( + &self, + map: &mut BTreeMap, + ) -> Result, E>; +} + +impl<'de> Deserialize<'de> for Event { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + let map = match Value::deserialize(deserializer)? { + Value::Map(m) => Ok(m), + v => Err(Error::invalid_type(value_to_unexpected(&v), &"a map")), + }?; + + let mut map: BTreeMap = map + .into_iter() + .map(|(k, v)| match k { + Value::String(s) => Ok((s, v)), + k => Err(Error::invalid_type(value_to_unexpected(&k), &"a string")), + }) + .collect::, >::Error>>()?; + + let event_deserializer = + match parse_field!(map, "specversion", String, >::Error)? + .as_str() + { + "1.0" => Ok(EventDeseriazerV10 {}), + 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, + }) + } +} + +// This should be provided by the Value package itself +pub(crate) fn value_to_unexpected(v: &Value) -> Unexpected { + match v { + Value::Bool(b) => serde::de::Unexpected::Bool(*b), + Value::U8(n) => serde::de::Unexpected::Unsigned(*n as u64), + Value::U16(n) => serde::de::Unexpected::Unsigned(*n as u64), + Value::U32(n) => serde::de::Unexpected::Unsigned(*n as u64), + Value::U64(n) => serde::de::Unexpected::Unsigned(*n), + Value::I8(n) => serde::de::Unexpected::Signed(*n as i64), + Value::I16(n) => serde::de::Unexpected::Signed(*n as i64), + Value::I32(n) => serde::de::Unexpected::Signed(*n as i64), + Value::I64(n) => serde::de::Unexpected::Signed(*n), + Value::F32(n) => serde::de::Unexpected::Float(*n as f64), + Value::F64(n) => serde::de::Unexpected::Float(*n), + Value::Char(c) => serde::de::Unexpected::Char(*c), + Value::String(s) => serde::de::Unexpected::Str(s), + Value::Unit => serde::de::Unexpected::Unit, + Value::Option(_) => serde::de::Unexpected::Option, + Value::Newtype(_) => serde::de::Unexpected::NewtypeStruct, + Value::Seq(_) => serde::de::Unexpected::Seq, + Value::Map(_) => serde::de::Unexpected::Map, + Value::Bytes(b) => serde::de::Unexpected::Bytes(b), + } +} diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 662bacee..3b3ed949 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -1,6 +1,7 @@ use super::Attributes as AttributesV10; use crate::event::{Attributes, AttributesWriter, Data, Event, ExtensionValue}; use chrono::{DateTime, Utc}; +use std::collections::HashMap; pub struct EventBuilder { event: Event, @@ -17,6 +18,7 @@ impl EventBuilder { event: Event { attributes: Attributes::V10(AttributesV10::default()), data: None, + extensions: HashMap::new(), }, } } diff --git a/src/event/v10/mod.rs b/src/event/v10/mod.rs index 14b945f1..3a7c0151 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -1,5 +1,7 @@ mod attributes; mod builder; +mod serde; +pub(crate) use crate::event::v10::serde::EventDeserializer; pub use attributes::Attributes; pub use builder::EventBuilder; diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs new file mode 100644 index 00000000..ae2e5883 --- /dev/null +++ b/src/event/v10/serde.rs @@ -0,0 +1,59 @@ +use super::Attributes; +use crate::event::Data; +use chrono::{DateTime, Utc}; +use serde::de::{IntoDeserializer, Unexpected}; +use serde::Deserialize; +use serde_value::Value; +use std::collections::BTreeMap; + +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 { + 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)?, + dataschema: parse_optional_field!(map, "dataschema", 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( + &self, + 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), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 8c9840be..6c04514a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ extern crate serde; extern crate serde_json; +extern crate serde_value; pub mod event; From 5e267812697bae964fda888b6ed8bb9ca1c69256 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Thu, 26 Mar 2020 19:46:37 +0100 Subject: [PATCH 11/56] Implemented custom serialization process Signed-off-by: Francesco Guardiani Signed-off-by: Pranav Bhatt --- Cargo.lock | 10 ------- Cargo.toml | 1 - README.md | 4 +-- src/event/attributes.rs | 4 +-- src/event/data.rs | 15 +---------- src/event/event.rs | 7 +---- src/event/mod.rs | 3 ++- src/event/serde.rs | 35 ++++++++++++++++++++----- src/event/v10/mod.rs | 1 + src/event/v10/serde.rs | 58 ++++++++++++++++++++++++++++++++++++++--- 10 files changed, 92 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f40b1b79..357809b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,6 @@ dependencies = [ "hostname", "rstest", "serde", - "serde-transcode", "serde-value", "serde_json", "uuid", @@ -251,15 +250,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-transcode" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97528f0dfcf8ce2d51d995cb513a103b9cd301dc3f387a9cae5ef974381d4e1c" -dependencies = [ - "serde", -] - [[package]] name = "serde-value" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index b4c5a5a8..cb68b4f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ repository = "https://github.com/cloudevents/sdk-rust" serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" serde-value = "^0.6" -serde-transcode = "^1.1" chrono = { version = "^0.4", features = ["serde"] } delegate = "^0.4" uuid = { version = "^0.8", features = ["serde", "v4"] } diff --git a/README.md b/README.md index e3fca189..996af602 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) | | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | | :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | -| CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: | +| CloudEvents Core | :x: | :heavy_check_mark: | | AMQP Protocol Binding | :x: | :x: | | AVRO Event Format | :x: | :x: | | HTTP Protocol Binding | :x: | :x: | -| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: | +| JSON Event Format | :x: | :heavy_check_mark: | | Kafka Protocol Binding | :x: | :x: | | MQTT Protocol Binding | :x: | :x: | | NATS Protocol Binding | :x: | :x: | diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 91025b79..0827e1e6 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -57,10 +57,8 @@ pub(crate) trait DataAttributesWriter { fn set_dataschema(&mut self, dataschema: Option>); } -#[derive(PartialEq, Debug, Clone, Serialize)] -#[serde(tag = "specversion")] +#[derive(PartialEq, Debug, Clone)] pub enum Attributes { - #[serde(rename = "1.0")] V10(AttributesV10), } diff --git a/src/event/data.rs b/src/event/data.rs index 72649be2..fcea8393 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -1,16 +1,10 @@ -use serde::de::Visitor; -use serde::{Serialize, Serializer}; use std::convert::{Into, TryFrom}; -use std::fmt::{self, Formatter}; /// Event [data attribute](https://github.com/cloudevents/spec/blob/master/spec.md#event-data) representation /// -#[derive(Debug, PartialEq, Clone, Serialize)] +#[derive(Debug, PartialEq, Clone)] pub enum Data { - #[serde(rename = "data_base64")] - #[serde(serialize_with = "serialize_base64")] Binary(Vec), - #[serde(rename = "data")] Json(serde_json::Value), } @@ -36,13 +30,6 @@ impl Data { } } -fn serialize_base64(data: &Vec, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&base64::encode(&data)) -} - impl Into for serde_json::Value { fn into(self) -> Data { Data::Json(self) diff --git a/src/event/event.rs b/src/event/event.rs index 60617885..e1c138a8 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -5,7 +5,6 @@ use super::{ use crate::event::attributes::DataAttributesWriter; use chrono::{DateTime, Utc}; use delegate::delegate; -use serde::Serialize; use std::collections::HashMap; use std::convert::TryFrom; @@ -33,14 +32,10 @@ use std::convert::TryFrom; /// let data: serde_json::Value = e.try_get_data().unwrap().unwrap(); /// println!("Event data: {}", data) /// ``` -#[derive(PartialEq, Debug, Clone, Serialize)] +#[derive(PartialEq, Debug, Clone)] pub struct Event { - #[serde(flatten)] pub attributes: Attributes, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(flatten)] pub data: Option, - #[serde(flatten)] pub extensions: HashMap, } diff --git a/src/event/mod.rs b/src/event/mod.rs index 3a89441d..b709a0e7 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -19,4 +19,5 @@ mod v10; pub use v10::Attributes as AttributesV10; pub use v10::EventBuilder as EventBuilderV10; -pub(crate) use v10::EventDeserializer as EventDeseriazerV10; +pub(crate) use v10::EventDeserializer as EventDeserializerV10; +pub(crate) use v10::EventSerializer as EventSerializerV10; diff --git a/src/event/serde.rs b/src/event/serde.rs index 7b5d5ef7..cdaee561 100644 --- a/src/event/serde.rs +++ b/src/event/serde.rs @@ -1,9 +1,9 @@ -use super::{Attributes, Data, Event, EventDeseriazerV10}; -use serde::de::{Error, Unexpected, IntoDeserializer}; -use serde::{Deserialize, Deserializer}; +use super::{Attributes, Data, Event, EventDeserializerV10, EventSerializerV10}; +use crate::event::ExtensionValue; +use serde::de::{Error, IntoDeserializer, Unexpected}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_value::Value; use std::collections::{BTreeMap, HashMap}; -use crate::event::ExtensionValue; const SPEC_VERSIONS: [&'static str; 1] = ["1.0"]; @@ -40,6 +40,15 @@ pub(crate) trait EventDeserializer { ) -> Result, E>; } +pub(crate) trait EventSerializer { + fn serialize( + attributes: &A, + data: &Option, + extensions: &HashMap, + serializer: S, + ) -> Result<::Ok, ::Error>; +} + impl<'de> Deserialize<'de> for Event { fn deserialize(deserializer: D) -> Result>::Error> where @@ -62,7 +71,7 @@ impl<'de> Deserialize<'de> for Event { match parse_field!(map, "specversion", String, >::Error)? .as_str() { - "1.0" => Ok(EventDeseriazerV10 {}), + "1.0" => Ok(EventDeserializerV10 {}), s => Err(>::Error::unknown_variant( s, &SPEC_VERSIONS, @@ -71,7 +80,8 @@ impl<'de> Deserialize<'de> for Event { let attributes = event_deserializer.deserialize_attributes(&mut map)?; let data = event_deserializer.deserialize_data(&mut map)?; - let extensions = map.into_iter() + 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))?; @@ -84,6 +94,19 @@ impl<'de> Deserialize<'de> for Event { } } +impl Serialize for Event { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + match &self.attributes { + Attributes::V10(a) => { + EventSerializerV10::serialize(a, &self.data, &self.extensions, serializer) + } + } + } +} + // This should be provided by the Value package itself pub(crate) fn value_to_unexpected(v: &Value) -> Unexpected { match v { diff --git a/src/event/v10/mod.rs b/src/event/v10/mod.rs index 3a7c0151..026793bb 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -3,5 +3,6 @@ mod builder; mod serde; pub(crate) use crate::event::v10::serde::EventDeserializer; +pub(crate) use crate::event::v10::serde::EventSerializer; pub use attributes::Attributes; pub use builder::EventBuilder; diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs index ae2e5883..e6a4a9aa 100644 --- a/src/event/v10/serde.rs +++ b/src/event/v10/serde.rs @@ -1,10 +1,11 @@ use super::Attributes; -use crate::event::Data; +use crate::event::{Data, ExtensionValue}; use chrono::{DateTime, Utc}; use serde::de::{IntoDeserializer, Unexpected}; -use serde::Deserialize; +use serde::ser::SerializeMap; +use serde::{Deserialize, Serializer}; use serde_value::Value; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; pub(crate) struct EventDeserializer {} @@ -57,3 +58,54 @@ impl crate::event::serde::EventDeserializer for EventDeserializer { } } } + +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.dataschema.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", "1.0")?; + 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(dataschema) = &attributes.dataschema { + state.serialize_entry("dataschema", dataschema)?; + } + 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::Binary(v)) => state.serialize_entry("data_base64", &base64::encode(v))?, + _ => (), + }; + for (k, v) in extensions { + state.serialize_entry(k, v)?; + } + state.end() + } +} From f7cca6994a8b99030e5eb5acba21db3b6dbd4e84 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Fri, 10 Apr 2020 09:21:26 +0200 Subject: [PATCH 12/56] V0.3 implementation (#24) * Added String variant to Data Signed-off-by: Francesco Guardiani * Started V0.3 work Signed-off-by: Francesco Guardiani * Reworked EventDeserializer trait Signed-off-by: Francesco Guardiani * Now event parsing works with v1 and changes Signed-off-by: Francesco Guardiani * Cargo fmt Signed-off-by: Francesco Guardiani * Reorganized test data Signed-off-by: Francesco Guardiani * Fixed serde for v03 Signed-off-by: Francesco Guardiani * Implemented spec version conversion Signed-off-by: Francesco Guardiani * cargo fmt Signed-off-by: Francesco Guardiani Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 39 +++++++- src/event/builder.rs | 12 ++- src/event/data.rs | 15 ++- src/event/mod.rs | 7 ++ src/event/serde.rs | 107 ++++++++++++++------ src/event/v03/attributes.rs | 124 +++++++++++++++++++++++ src/event/v03/builder.rs | 132 ++++++++++++++++++++++++ src/event/v03/mod.rs | 8 ++ src/event/v03/serde.rs | 110 ++++++++++++++++++++ src/event/v10/builder.rs | 13 ++- src/event/v10/serde.rs | 31 +++--- tests/event.rs | 2 - tests/serde_json.rs | 77 +++++++++++--- tests/test_data/data.rs | 58 +++++++++++ tests/test_data/mod.rs | 186 +--------------------------------- tests/test_data/v03.rs | 193 ++++++++++++++++++++++++++++++++++++ tests/test_data/v10.rs | 191 +++++++++++++++++++++++++++++++++++ tests/version_conversion.rs | 17 ++++ 18 files changed, 1069 insertions(+), 253 deletions(-) create mode 100644 src/event/v03/attributes.rs create mode 100644 src/event/v03/builder.rs create mode 100644 src/event/v03/serde.rs delete mode 100644 tests/event.rs create mode 100644 tests/test_data/data.rs create mode 100644 tests/test_data/v03.rs create mode 100644 tests/test_data/v10.rs create mode 100644 tests/version_conversion.rs diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 0827e1e6..50e0807d 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}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -52,6 +51,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>); @@ -59,54 +63,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(), } } @@ -115,30 +128,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), } } @@ -147,13 +165,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 b709a0e7..282deb6c 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -15,6 +15,13 @@ pub use event::Event; pub use extension_value::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/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) +} From d8a0966b7ce94fed6df11e7f741ae6edbaadc02c Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 16:52:46 +0530 Subject: [PATCH 13/56] Correcting errors Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 1 - src/event/{extension_value.rs => extensions.rs} | 0 src/event/mod.rs | 2 +- 3 files changed, 1 insertion(+), 2 deletions(-) rename src/event/{extension_value.rs => extensions.rs} (100%) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 50e0807d..be89c434 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,6 +1,5 @@ use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use std::fmt; pub enum AttributeValue<'a> { diff --git a/src/event/extension_value.rs b/src/event/extensions.rs similarity index 100% rename from src/event/extension_value.rs rename to src/event/extensions.rs diff --git a/src/event/mod.rs b/src/event/mod.rs index 282deb6c..3af14943 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -12,7 +12,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extension_value::ExtensionValue; +pub use extensions::ExtensionValue; pub use spec_version::SpecVersion; mod v03; From 33813d6e9e789b80e7455baca7b0bc8add6ef31e Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 13:14:21 +0530 Subject: [PATCH 14/56] roll back Signed-off-by: Pranav Bhatt --- CONTRIBUTING.md | 129 ------------------------ Cargo.lock | 68 ------------- Cargo.toml | 5 - README.md | 35 +------ src/event/attributes.rs | 94 +++++++++++------- src/event/builder.rs | 12 +-- src/event/data.rs | 29 +----- src/event/event.rs | 57 ++++------- src/event/extensions.rs | 20 +++- src/event/mod.rs | 13 +-- src/event/serde.rs | 182 ---------------------------------- src/event/spec_version.rs | 5 +- src/event/v03/attributes.rs | 124 ----------------------- src/event/v03/builder.rs | 132 ------------------------ src/event/v03/mod.rs | 8 -- src/event/v03/serde.rs | 110 -------------------- src/event/v10/attributes.rs | 114 +++++++-------------- src/event/v10/builder.rs | 15 +-- src/event/v10/mod.rs | 3 - src/event/v10/serde.rs | 106 -------------------- src/lib.rs | 1 - tests/event.rs | 2 + tests/serde_json.rs | 89 ----------------- tests/test_data/data.rs | 58 ----------- tests/test_data/mod.rs | 5 - tests/test_data/v03.rs | 193 ------------------------------------ tests/test_data/v10.rs | 191 ----------------------------------- tests/version_conversion.rs | 17 ---- 28 files changed, 150 insertions(+), 1667 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 src/event/serde.rs delete mode 100644 src/event/v03/attributes.rs delete mode 100644 src/event/v03/builder.rs delete mode 100644 src/event/v03/serde.rs delete mode 100644 src/event/v10/serde.rs create mode 100644 tests/event.rs delete mode 100644 tests/serde_json.rs delete mode 100644 tests/test_data/data.rs delete mode 100644 tests/test_data/mod.rs delete mode 100644 tests/test_data/v03.rs delete mode 100644 tests/test_data/v10.rs delete mode 100644 tests/version_conversion.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a7614ad1..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,129 +0,0 @@ -# Contributing to CloudEvents SDK Rust - -This page contains information about reporting issues, how to suggest changes as -well as the guidelines we follow for how our documents are formatted. - -## Table of Contents - -- [Reporting an Issue](#reporting-an-issue) -- [Preparing the environment](#preparing-the-environment) -- [Suggesting a Change](#suggesting-a-change) - -## Reporting an Issue - -To report an issue, or to suggest an idea for a change that you haven't had time -to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It -is best to check our existing -[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar -one has already been opened and discussed. - -## Preparing the environment - -In order to start developing this project, -you need to install the Rust tooling using [rustup](https://rustup.rs/). - -### Development commands - -To build the project: - -```sh -cargo build --all-features -``` - -To run all tests: - -```sh -cargo test --all-features -``` - -To build and open the documentation: - -```sh -cargo doc --lib --open -``` - -To run the code formatter: - -```sh -cargo fmt -``` - -## Suggesting a change - -To suggest a change to this repository, submit a -[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete -set of changes you'd like to see. See the -[Spec Formatting Conventions](#spec-formatting-conventions) section for the -guidelines we follow for how documents are formatted. - -Each PR must be signed per the following section. - -### Sign your work - -The sign-off is a simple line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. The rules are pretty simple: if you can certify -the below (from [developercertificate.org](http://developercertificate.org/)): - -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -Then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -Use your real name (sorry, no pseudonyms or anonymous contributions.) - -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. - -Note: If your git config information is set properly then viewing the `git log` -information for your commit will look something like this: - -``` -Author: Joe Smith -Date: Thu Feb 2 11:41:15 2018 -0800 - - Update README - - Signed-off-by: Joe Smith -``` - -Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will -be rejected by the automated DCO check. diff --git a/Cargo.lock b/Cargo.lock index 357809b0..8516a3cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,27 +30,15 @@ dependencies = [ "time", ] -[[package]] -name = "claim" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2e893ee68bf12771457cceea72497bc9cb7da404ec8a5311226d354b895ba4" -dependencies = [ - "autocfg", -] - [[package]] name = "cloudevents-sdk" version = "0.0.1" dependencies = [ "base64", "chrono", - "claim", "delegate", "hostname", - "rstest", "serde", - "serde-value", "serde_json", "uuid", ] @@ -118,15 +106,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "ordered-float" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" -dependencies = [ - "num-traits", -] - [[package]] name = "ppv-lite86" version = "0.2.6" @@ -198,49 +177,12 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -[[package]] -name = "rstest" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d5f9396fa6a44e2aa2068340b17208794515e2501c5bf3e680a0c3422a5971" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.104" @@ -250,16 +192,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.104" diff --git a/Cargo.toml b/Cargo.toml index cb68b4f3..d678c6f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,16 +13,11 @@ repository = "https://github.com/cloudevents/sdk-rust" [dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" -serde-value = "^0.6" chrono = { version = "^0.4", features = ["serde"] } delegate = "^0.4" uuid = { version = "^0.8", features = ["serde", "v4"] } hostname = "^0.1" base64 = "^0.12" -[dev-dependencies] -rstest = "0.6" -claim = "0.3.1" - [lib] name = "cloudevents" diff --git a/README.md b/README.md index 996af602..23849afd 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,3 @@ -# CloudEvents SDK Rust +# CloudEvents Rust SDK Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) - -## Spec support - -| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | -| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | -| CloudEvents Core | :x: | :heavy_check_mark: | -| AMQP Protocol Binding | :x: | :x: | -| AVRO Event Format | :x: | :x: | -| HTTP Protocol Binding | :x: | :x: | -| JSON Event Format | :x: | :heavy_check_mark: | -| Kafka Protocol Binding | :x: | :x: | -| MQTT Protocol Binding | :x: | :x: | -| NATS Protocol Binding | :x: | :x: | -| Web hook | :x: | :x: | - -## Development & Contributing - -If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) - -## Community - -- There are bi-weekly calls immediately following the - [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) - at 9am PT (US Pacific). Which means they will typically start at 10am PT, but - if the other call ends early then the SDK call will start early as well. See - the - [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) - to determine which week will have the call. -- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under - [CNCF's Slack workspace](https://slack.cncf.io/). -- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk -- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` - on slack). diff --git a/src/event/attributes.rs b/src/event/attributes.rs index be89c434..95210436 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,7 +1,23 @@ -use super::{AttributesV03, AttributesV10, SpecVersion}; +use super::SpecVersion; +use crate::event::{AttributesV10, ExtensionValue}; use chrono::{DateTime, Utc}; use std::fmt; +impl ExactSizeIterator for Iter { + type Item = (&'a str, AttributeValue<'a>); + + fn next(&mut self) -> Option { + let new_next = self.curr + self.next; + + self.curr = self.next; + self.next = new_next; + + // Since there's no endpoint to a Fibonacci sequence, the `Iterator` + // will never return `None`, and `Some` is always returned. + Some(self.curr) + } +} + pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), @@ -40,6 +56,10 @@ pub trait AttributesReader { 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>; + /// 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) + fn iter_extensions(&self) -> std::collections::hash_map::Iter; } pub trait AttributesWriter { @@ -48,11 +68,15 @@ pub trait AttributesWriter { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); -} - -pub(crate) trait AttributesConverter { - fn into_v03(self) -> AttributesV03; - fn into_v10(self) -> AttributesV10; + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ); + fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option; } pub(crate) trait DataAttributesWriter { @@ -62,132 +86,132 @@ 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(), } } + + fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { + match self { + Attributes::V10(a) => a.get_extension(extension_name), + } + } + + fn iter_extensions(&self) -> std::collections::hash_map::Iter { + match self { + Attributes::V10(a) => a.iter_extensions(), + } + } } 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), } } -} -impl DataAttributesWriter for Attributes { - fn set_datacontenttype(&mut self, datacontenttype: Option>) { + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ) { match self { - Attributes::V03(a) => a.set_datacontenttype(datacontenttype), - Attributes::V10(a) => a.set_datacontenttype(datacontenttype), + Attributes::V10(a) => a.set_extension(extension_name, extension_value), } } - fn set_dataschema(&mut self, dataschema: Option>) { + fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option { match self { - Attributes::V03(a) => a.set_dataschema(dataschema), - Attributes::V10(a) => a.set_dataschema(dataschema), + Attributes::V10(a) => a.remove_extension(extension_name), } } } -impl Attributes { - pub fn into_v10(self) -> Self { +impl DataAttributesWriter for Attributes { + fn set_datacontenttype(&mut self, datacontenttype: Option>) { match self { - Attributes::V03(v03) => Attributes::V10(v03.into_v10()), - _ => self, + Attributes::V10(a) => a.set_datacontenttype(datacontenttype), } } - pub fn into_v03(self) -> Self { + + fn set_dataschema(&mut self, dataschema: Option>) { match self { - Attributes::V10(v10) => Attributes::V03(v10.into_v03()), - _ => self, + Attributes::V10(a) => a.set_dataschema(dataschema), } } } diff --git a/src/event/builder.rs b/src/event/builder.rs index 5dbfef35..b72dbc87 100644 --- a/src/event/builder.rs +++ b/src/event/builder.rs @@ -1,4 +1,4 @@ -use super::{EventBuilderV03, EventBuilderV10}; +use super::EventBuilderV10; /// Builder to create [`Event`]: /// ``` @@ -14,18 +14,8 @@ use super::{EventBuilderV03, 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 e511e512..1811dd35 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -1,13 +1,10 @@ use std::convert::{Into, TryFrom}; -/// Event [data attribute](https://github.com/cloudevents/spec/blob/master/spec.md#event-data) representation #[derive(Debug, PartialEq, Clone)] +/// Possible data values pub enum Data { - /// Event has a binary payload - Binary(Vec), - /// Event has a non-json string payload String(String), - /// Event has a json payload + Binary(Vec), Json(serde_json::Value), } @@ -33,10 +30,6 @@ 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) @@ -60,21 +53,9 @@ impl TryFrom for serde_json::Value { fn try_from(value: Data) -> Result { 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)?), - } - } -} - -impl TryFrom for Vec { - type Error = serde_json::Error; - - fn try_from(value: Data) -> Result { - 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()), + Data::Json(v) => Ok(v), } } } @@ -84,9 +65,9 @@ impl TryFrom for String { fn try_from(value: Data) -> Result { match value { - Data::Binary(v) => Ok(String::from_utf8(v)?), - Data::Json(v) => Ok(v.to_string()), Data::String(s) => Ok(s), + Data::Binary(v) => Ok(String::from_utf8(v)?), + Data::Json(s) => Ok(s.to_string()), } } } diff --git a/src/event/event.rs b/src/event/event.rs index e1c138a8..679f78a6 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -5,7 +5,6 @@ use super::{ use crate::event::attributes::DataAttributesWriter; use chrono::{DateTime, Utc}; use delegate::delegate; -use std::collections::HashMap; use std::convert::TryFrom; /// Data structure that represents a [CloudEvent](https://github.com/cloudevents/spec/blob/master/spec.md). @@ -36,7 +35,6 @@ use std::convert::TryFrom; pub struct Event { pub attributes: Attributes, pub data: Option, - pub extensions: HashMap, } impl AttributesReader for Event { @@ -50,6 +48,8 @@ impl AttributesReader for Event { fn get_dataschema(&self) -> Option<&str>; fn get_subject(&self) -> Option<&str>; fn get_time(&self) -> Option<&DateTime>; + fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; + fn iter_extensions(&self) -> std::collections::hash_map::Iter; } } } @@ -62,6 +62,15 @@ impl AttributesWriter for Event { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ); + fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option; } } } @@ -71,7 +80,6 @@ impl Default for Event { Event { attributes: Attributes::V10(AttributesV10::default()), data: None, - extensions: HashMap::default(), } } } @@ -127,49 +135,22 @@ impl Event { } } - pub fn try_get_data>(&self) -> Result, T::Error> { + pub fn try_get_data, E: std::error::Error>( + &self, + ) -> Option> { match self.data.as_ref() { Some(d) => Some(T::try_from(d.clone())), None => None, } - .transpose() } - pub fn into_data>(self) -> Result, T::Error> { + pub fn into_data, E: std::error::Error>( + self, + ) -> Option> { match self.data { Some(d) => Some(T::try_from(d)), None => None, } - .transpose() - } - - /// Get the [extension](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) named `extension_name` - pub fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - self.extensions.get(extension_name) - } - - /// Get all the [extensions](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) - pub fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> { - self.extensions - .iter() - .map(|(k, v)| (k.as_str(), v)) - .collect() - } - - pub fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { - self.extensions - .insert(extension_name.to_owned(), extension_value.into()); - } - - pub fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option { - self.extensions.remove(extension_name) } } @@ -208,7 +189,9 @@ mod tests { e.remove_data(); - assert!(e.try_get_data::().unwrap().is_none()); + 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 3abca5c1..ac8f0015 100644 --- a/src/event/extensions.rs +++ b/src/event/extensions.rs @@ -1,8 +1,7 @@ -use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::convert::From; -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[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. @@ -11,6 +10,8 @@ pub enum ExtensionValue { Boolean(bool), /// Represents an integer [`i64`](i64) value. Integer(i64), + /// Represents a [Json `Value`](serde_json::value::Value). + Json(Value), } impl From for ExtensionValue { @@ -31,6 +32,12 @@ impl From for ExtensionValue { } } +impl From for ExtensionValue { + fn from(s: Value) -> Self { + ExtensionValue::Json(s) + } +} + impl ExtensionValue { pub fn from_string(s: S) -> Self where @@ -52,4 +59,11 @@ impl ExtensionValue { { ExtensionValue::from(s.into()) } + + pub fn from_json_value(s: S) -> Self + where + S: Into, + { + ExtensionValue::from(s.into()) + } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 3af14943..3fac6da4 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -2,9 +2,7 @@ mod attributes; mod builder; mod data; mod event; -mod extensions; -#[macro_use] -mod serde; +mod extension_value; mod spec_version; pub use attributes::Attributes; @@ -15,16 +13,7 @@ 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; pub use v10::EventBuilder as EventBuilderV10; -pub(crate) use v10::EventDeserializer as EventDeserializerV10; -pub(crate) use v10::EventSerializer as EventSerializerV10; diff --git a/src/event/serde.rs b/src/event/serde.rs deleted file mode 100644 index c3910097..00000000 --- a/src/event/serde.rs +++ /dev/null @@ -1,182 +0,0 @@ -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; 2] = ["0.3", "1.0"]; - -macro_rules! parse_optional_field { - ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { - $map.remove($name) - .map(|val| match val { - Value::$value_variant(v) => Ok(v), - other => Err(<$error>::invalid_type( - crate::event::serde::value_to_unexpected(&other), - &stringify!($value_variant), - )), - }) - .transpose() - }; -} - -macro_rules! parse_field { - ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { - parse_optional_field!($map, $name, $value_variant, $error)? - .ok_or_else(|| <$error>::missing_field($name)) - }; -} - -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( - map: &mut BTreeMap, - ) -> Result; - - fn deserialize_data( - 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 { - fn serialize( - attributes: &A, - data: &Option, - extensions: &HashMap, - serializer: S, - ) -> Result<::Ok, ::Error>; -} - -impl<'de> Deserialize<'de> for Event { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - let map = match Value::deserialize(deserializer)? { - Value::Map(m) => Ok(m), - v => Err(Error::invalid_type(value_to_unexpected(&v), &"a map")), - }?; - - let mut map: BTreeMap = map - .into_iter() - .map(|(k, v)| match k { - Value::String(s) => Ok((s, v)), - k => Err(Error::invalid_type(value_to_unexpected(&k), &"a string")), - }) - .collect::, >::Error>>()?; - - 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)), - } - } -} - -impl Serialize for Event { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - 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) - } - } - } -} - -// This should be provided by the Value package itself -pub(crate) fn value_to_unexpected(v: &Value) -> Unexpected { - match v { - Value::Bool(b) => serde::de::Unexpected::Bool(*b), - Value::U8(n) => serde::de::Unexpected::Unsigned(*n as u64), - Value::U16(n) => serde::de::Unexpected::Unsigned(*n as u64), - Value::U32(n) => serde::de::Unexpected::Unsigned(*n as u64), - Value::U64(n) => serde::de::Unexpected::Unsigned(*n), - Value::I8(n) => serde::de::Unexpected::Signed(*n as i64), - Value::I16(n) => serde::de::Unexpected::Signed(*n as i64), - Value::I32(n) => serde::de::Unexpected::Signed(*n as i64), - Value::I64(n) => serde::de::Unexpected::Signed(*n), - Value::F32(n) => serde::de::Unexpected::Float(*n as f64), - Value::F64(n) => serde::de::Unexpected::Float(*n), - Value::Char(c) => serde::de::Unexpected::Char(*c), - Value::String(s) => serde::de::Unexpected::Str(s), - Value::Unit => serde::de::Unexpected::Unit, - Value::Option(_) => serde::de::Unexpected::Option, - Value::Newtype(_) => serde::de::Unexpected::NewtypeStruct, - Value::Seq(_) => serde::de::Unexpected::Seq, - Value::Map(_) => serde::de::Unexpected::Map, - Value::Bytes(b) => serde::de::Unexpected::Bytes(b), - } -} diff --git a/src/event/spec_version.rs b/src/event/spec_version.rs index d14cb088..113fd906 100644 --- a/src/event/spec_version.rs +++ b/src/event/spec_version.rs @@ -1,9 +1,12 @@ +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; -#[derive(PartialEq, Debug, Clone)] +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] pub enum SpecVersion { + #[serde(rename = "0.3")] V03, + #[serde(rename = "1.0")] V10, } diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs deleted file mode 100644 index 1e75629e..00000000 --- a/src/event/v03/attributes.rs +++ /dev/null @@ -1,124 +0,0 @@ -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 deleted file mode 100644 index 41aca7c4..00000000 --- a/src/event/v03/builder.rs +++ /dev/null @@ -1,132 +0,0 @@ -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 4ab6a7a0..e69de29b 100644 --- a/src/event/v03/mod.rs +++ b/src/event/v03/mod.rs @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 58f0f650..00000000 --- a/src/event/v03/serde.rs +++ /dev/null @@ -1,110 +0,0 @@ -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 fecd09fe..3dea738a 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,69 +1,20 @@ -use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; -use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; +use crate::event::attributes::DataAttributesWriter; +use crate::event::{AttributesReader, AttributesWriter, ExtensionValue, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; +use std::collections::HashMap; 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) dataschema: Option, - pub(crate) subject: Option, - pub(crate) time: Option>, -} - -impl<'a> IntoIterator for &'a Attributes { - type Item = (&'a str, AttributeValue<'a>); - type IntoIter = AttributesIntoIterator<'a>; - - fn into_iter(self) -> Self::IntoIter { - AttributesIntoIterator { - attributes: self, - index: 0, - } - } -} - -struct AttributesIntoIterator<'a> { - attributes: &'a Attributes, - index: usize, -} - -fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::String(x))), - None => None, - }; - result -} - -fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::Time(x))), - None => None, - }; - result -} - -impl<'a> Iterator for AttributesIntoIterator<'a> { - type Item = (&'a str, AttributeValue<'a>); - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(("id", AttributeValue::String(&self.attributes.id))), - 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::String(&self.attributes.source))), - 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), - 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), - 5 => option_checker_string("subject",self.attributes.subject.get_subject()), - 6 => option_checker_time("time",self.attributes.time.get_time()), - _ => return None, - }; - self.index += 1; - result - } + id: String, + ty: String, + source: String, + datacontenttype: Option, + dataschema: Option, + subject: Option, + time: Option>, + extensions: HashMap, } impl AttributesReader for Attributes { @@ -107,6 +58,14 @@ impl AttributesReader for Attributes { fn get_time(&self) -> Option<&DateTime> { self.time.as_ref() } + + fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { + self.extensions.get(extension_name) + } + + fn iter_extensions(&self) -> std::collections::hash_map::Iter { + self.extensions.iter() + } } impl AttributesWriter for Attributes { @@ -129,6 +88,22 @@ impl AttributesWriter for Attributes { fn set_time(&mut self, time: Option>>) { self.time = time.map(Into::into) } + + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ) { + self.extensions + .insert(extension_name.to_owned(), extension_value.into()); + } + + fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option { + self.extensions.remove(extension_name) + } } impl DataAttributesWriter for Attributes { @@ -151,24 +126,7 @@ impl Default for Attributes { dataschema: None, subject: None, time: None, - } - } -} - -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, + extensions: HashMap::new(), } } } diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 9b7f8888..662bacee 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -1,29 +1,22 @@ use super::Attributes as AttributesV10; 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_v10(), - data: event.data, - extensions: event.extensions, - }, - } - } + // 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, - extensions: HashMap::new(), }, } } diff --git a/src/event/v10/mod.rs b/src/event/v10/mod.rs index 026793bb..14b945f1 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -1,8 +1,5 @@ mod attributes; mod builder; -mod serde; -pub(crate) use crate::event::v10::serde::EventDeserializer; -pub(crate) use crate::event::v10::serde::EventSerializer; pub use attributes::Attributes; pub use builder::EventBuilder; diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs deleted file mode 100644 index fa219549..00000000 --- a/src/event/v10/serde.rs +++ /dev/null @@ -1,106 +0,0 @@ -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::V10(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)?, - dataschema: parse_optional_field!(map, "dataschema", 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 data_base64 = map.remove("data_base64"); - - 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, - }) - } -} - -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.dataschema.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", "1.0")?; - 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(dataschema) = &attributes.dataschema { - state.serialize_entry("dataschema", dataschema)?; - } - 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", &base64::encode(v))?, - _ => (), - }; - for (k, v) in extensions { - state.serialize_entry(k, v)?; - } - state.end() - } -} diff --git a/src/lib.rs b/src/lib.rs index 6c04514a..8c9840be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ extern crate serde; extern crate serde_json; -extern crate serde_value; pub mod event; diff --git a/tests/event.rs b/tests/event.rs new file mode 100644 index 00000000..7ad4c3a3 --- /dev/null +++ b/tests/event.rs @@ -0,0 +1,2 @@ +#[test] +fn use_event() {} diff --git a/tests/serde_json.rs b/tests/serde_json.rs deleted file mode 100644 index 14f01619..00000000 --- a/tests/serde_json.rs +++ /dev/null @@ -1,89 +0,0 @@ -use claim::*; -use cloudevents::Event; -use rstest::rstest; -use serde_json::Value; - -mod test_data; -use test_data::*; - -/// This test is a parametrized test that uses data from tests/test_data -#[rstest( - 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_should_succeed(in_event: Event, out_json: Value) { - // Event -> serde_json::Value - let serialize_result = serde_json::to_value(in_event.clone()); - assert_ok!(&serialize_result); - let actual_json = serialize_result.unwrap(); - assert_eq!(&actual_json, &out_json); - - // serde_json::Value -> String - let actual_json_serialized = actual_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, 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 deleted file mode 100644 index 087b20d2..00000000 --- a/tests/test_data/data.rs +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index 2e4f45c3..00000000 --- a/tests/test_data/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod data; -pub use data::*; - -pub mod v03; -pub mod v10; diff --git a/tests/test_data/v03.rs b/tests/test_data/v03.rs deleted file mode 100644 index 00e53478..00000000 --- a/tests/test_data/v03.rs +++ /dev/null @@ -1,193 +0,0 @@ -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 deleted file mode 100644 index f564c4d6..00000000 --- a/tests/test_data/v10.rs +++ /dev/null @@ -1,191 +0,0 @@ -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 deleted file mode 100644 index 5ba05e99..00000000 --- a/tests/version_conversion.rs +++ /dev/null @@ -1,17 +0,0 @@ -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) -} From 28d1148aa0759797cd246b97090e442b1e77867f Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Fri, 20 Mar 2020 13:21:16 +0100 Subject: [PATCH 15/56] WIP iter attributes Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 15 --------------- src/event/mod.rs | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 95210436..a659ed38 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -3,21 +3,6 @@ use crate::event::{AttributesV10, ExtensionValue}; use chrono::{DateTime, Utc}; use std::fmt; -impl ExactSizeIterator for Iter { - type Item = (&'a str, AttributeValue<'a>); - - fn next(&mut self) -> Option { - let new_next = self.curr + self.next; - - self.curr = self.next; - self.next = new_next; - - // Since there's no endpoint to a Fibonacci sequence, the `Iterator` - // will never return `None`, and `Some` is always returned. - Some(self.curr) - } -} - pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), diff --git a/src/event/mod.rs b/src/event/mod.rs index 3fac6da4..46be8f71 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -10,7 +10,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extensions::ExtensionValue; +pub use extension_value::ExtensionValue; pub use spec_version::SpecVersion; mod v10; From c60a5ccad6fc018838ab2d01874f8c9403c26c05 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 16:22:29 +0530 Subject: [PATCH 16/56] Completed Iterator for v10/attribute Signed-off-by: Pranav Bhatt --- src/event/mod.rs | 2 +- src/event/v10/attributes.rs | 57 +++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/event/mod.rs b/src/event/mod.rs index 46be8f71..3fac6da4 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -10,7 +10,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extension_value::ExtensionValue; +pub use extensions::ExtensionValue; pub use spec_version::SpecVersion; mod v10; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 3dea738a..61a9777f 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, ExtensionValue, SpecVersion}; +use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; +use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; use std::collections::HashMap; @@ -17,6 +17,57 @@ pub struct Attributes { extensions: HashMap, } +impl<'a> IntoIterator for &'a Attributes { + type Item = (&'a str, AttributeValue<'a>); + type IntoIter = AttributesIntoIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + AttributesIntoIterator { + attributes: self, + index: 0, + } + } +} + +struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, +} + +fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::String(x))), + None => None, + }; + result +} + +fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::Time(x))), + None => None, + }; + result +} + +impl<'a> Iterator for AttributesIntoIterator<'a> { + type Item = (&'a str, AttributeValue<'a>); + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(("id", AttributeValue::String(&self.attributes.id))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), + 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), + 5 => option_checker_string("subject",self.attributes.subject.get_subject()), + 6 => option_checker_time("time",self.attributes.time.get_time()), + _ => return None, + }; + self.index += 1; + result + } +} + impl AttributesReader for Attributes { fn get_id(&self) -> &str { &self.id @@ -129,4 +180,4 @@ impl Default for Attributes { extensions: HashMap::new(), } } -} +} \ No newline at end of file From 426316f853297d13878369ec8d956383f96c5811 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 17:00:29 +0530 Subject: [PATCH 17/56] Error fix 2 Signed-off-by: Pranav Bhatt --- src/event/mod.rs | 2 +- src/event/v10/attributes.rs | 73 ++++++++++++++++--------------------- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/event/mod.rs b/src/event/mod.rs index 3fac6da4..888b50c8 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -2,7 +2,7 @@ mod attributes; mod builder; mod data; mod event; -mod extension_value; +mod extensions; mod spec_version; pub use attributes::Attributes; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 61a9777f..9f7e5432 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -2,19 +2,17 @@ use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttribut use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; 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, - source: String, - datacontenttype: Option, - dataschema: Option, - subject: Option, - time: Option>, - extensions: HashMap, + pub(crate) id: String, + pub(crate) ty: String, + pub(crate) source: String, + pub(crate) datacontenttype: Option, + pub(crate) dataschema: Option, + pub(crate) subject: Option, + pub(crate) time: Option>, } impl<'a> IntoIterator for &'a Attributes { @@ -29,12 +27,12 @@ impl<'a> IntoIterator for &'a Attributes { } } -struct AttributesIntoIterator<'a> { +pub struct AttributesIntoIterator<'a> { attributes: &'a Attributes, index: usize, } -fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { +fn option_checker_string<'a>(attribute_type: &'a str,input:Option<&'a str>) -> Option<(&'a str,AttributeValue<'a>)> { let result = match input { Some(x) => Some((attribute_type,AttributeValue::String(x))), None => None, @@ -42,7 +40,7 @@ fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Opti result } -fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { +fn option_checker_time<'a>(attribute_type: &'a str,input:Option<&'a DateTime>) -> Option<(&'a str,AttributeValue<'a>)> { let result = match input { Some(x) => Some((attribute_type,AttributeValue::Time(x))), None => None, @@ -58,9 +56,9 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), 2 => Some(("source", AttributeValue::String(&self.attributes.source))), 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), - 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), - 5 => option_checker_string("subject",self.attributes.subject.get_subject()), - 6 => option_checker_time("time",self.attributes.time.get_time()), + 4 => option_checker_string("dataschema",self.attributes.get_dataschema()), + 5 => option_checker_string("subject",self.attributes.get_subject()), + 6 => option_checker_time("time",self.attributes.get_time()), _ => return None, }; self.index += 1; @@ -109,14 +107,6 @@ impl AttributesReader for Attributes { fn get_time(&self) -> Option<&DateTime> { self.time.as_ref() } - - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - self.extensions.get(extension_name) - } - - fn iter_extensions(&self) -> std::collections::hash_map::Iter { - self.extensions.iter() - } } impl AttributesWriter for Attributes { @@ -139,22 +129,6 @@ impl AttributesWriter for Attributes { fn set_time(&mut self, time: Option>>) { self.time = time.map(Into::into) } - - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { - self.extensions - .insert(extension_name.to_owned(), extension_value.into()); - } - - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option { - self.extensions.remove(extension_name) - } } impl DataAttributesWriter for Attributes { @@ -177,7 +151,24 @@ impl Default for Attributes { dataschema: None, subject: None, time: None, - extensions: HashMap::new(), } } -} \ No newline at end of file +} + +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, + } + } +} From 38e03254bfb420246e26082cfab89dd723cee39d Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 17:10:08 +0530 Subject: [PATCH 18/56] Error fix 2 Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 21 +++++---------------- src/event/event.rs | 2 -- src/event/v10/builder.rs | 4 ---- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index a659ed38..6a3525ad 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -23,6 +23,11 @@ impl fmt::Display for AttributeValue<'_> { } } +pub(crate) trait AttributesConverter { + fn into_v03(self) -> AttributesV03; + fn into_v10(self) -> AttributesV10; +} + /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { /// Get the [id](https://github.com/cloudevents/spec/blob/master/spec.md#id). @@ -41,10 +46,6 @@ pub trait AttributesReader { 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>; - /// 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) - fn iter_extensions(&self) -> std::collections::hash_map::Iter; } pub trait AttributesWriter { @@ -122,18 +123,6 @@ impl AttributesReader for Attributes { Attributes::V10(a) => a.get_time(), } } - - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - match self { - Attributes::V10(a) => a.get_extension(extension_name), - } - } - - fn iter_extensions(&self) -> std::collections::hash_map::Iter { - match self { - Attributes::V10(a) => a.iter_extensions(), - } - } } impl AttributesWriter for Attributes { diff --git a/src/event/event.rs b/src/event/event.rs index 679f78a6..ff853720 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -48,8 +48,6 @@ impl AttributesReader for Event { fn get_dataschema(&self) -> Option<&str>; fn get_subject(&self) -> Option<&str>; fn get_time(&self) -> Option<&DateTime>; - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; - fn iter_extensions(&self) -> std::collections::hash_map::Iter; } } } diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 662bacee..cd114cab 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -112,10 +112,6 @@ mod tests { 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()); From 757e03ec5cd11d058f0f1792353fcb77210cd754 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 17:34:30 +0530 Subject: [PATCH 19/56] Completed iterator Signed-off-by: Pranav Bhatt --- CONTRIBUTING.md | 129 ++++++++++++++++++++++++ Cargo.lock | 68 +++++++++++++ Cargo.toml | 5 + README.md | 35 ++++++- src/event/attributes.rs | 68 +++++++------ src/event/builder.rs | 12 ++- src/event/data.rs | 29 +++++- src/event/event.rs | 55 ++++++---- src/event/extensions.rs | 20 +--- src/event/mod.rs | 11 ++ src/event/serde.rs | 182 ++++++++++++++++++++++++++++++++++ src/event/spec_version.rs | 5 +- src/event/v03/attributes.rs | 124 +++++++++++++++++++++++ src/event/v03/builder.rs | 132 ++++++++++++++++++++++++ src/event/v03/mod.rs | 8 ++ src/event/v03/serde.rs | 110 ++++++++++++++++++++ src/event/v10/builder.rs | 19 +++- src/event/v10/mod.rs | 3 + src/event/v10/serde.rs | 106 ++++++++++++++++++++ src/lib.rs | 1 + tests/event.rs | 2 - tests/serde_json.rs | 89 +++++++++++++++++ tests/test_data/data.rs | 58 +++++++++++ tests/test_data/mod.rs | 5 + tests/test_data/v03.rs | 193 ++++++++++++++++++++++++++++++++++++ tests/test_data/v10.rs | 191 +++++++++++++++++++++++++++++++++++ tests/version_conversion.rs | 17 ++++ 27 files changed, 1592 insertions(+), 85 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 src/event/serde.rs create mode 100644 src/event/v03/attributes.rs create mode 100644 src/event/v03/builder.rs create mode 100644 src/event/v03/serde.rs create mode 100644 src/event/v10/serde.rs delete mode 100644 tests/event.rs create mode 100644 tests/serde_json.rs create mode 100644 tests/test_data/data.rs create mode 100644 tests/test_data/mod.rs create mode 100644 tests/test_data/v03.rs create mode 100644 tests/test_data/v10.rs create mode 100644 tests/version_conversion.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a7614ad1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,129 @@ +# Contributing to CloudEvents SDK Rust + +This page contains information about reporting issues, how to suggest changes as +well as the guidelines we follow for how our documents are formatted. + +## Table of Contents + +- [Reporting an Issue](#reporting-an-issue) +- [Preparing the environment](#preparing-the-environment) +- [Suggesting a Change](#suggesting-a-change) + +## Reporting an Issue + +To report an issue, or to suggest an idea for a change that you haven't had time +to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It +is best to check our existing +[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar +one has already been opened and discussed. + +## Preparing the environment + +In order to start developing this project, +you need to install the Rust tooling using [rustup](https://rustup.rs/). + +### Development commands + +To build the project: + +```sh +cargo build --all-features +``` + +To run all tests: + +```sh +cargo test --all-features +``` + +To build and open the documentation: + +```sh +cargo doc --lib --open +``` + +To run the code formatter: + +```sh +cargo fmt +``` + +## Suggesting a change + +To suggest a change to this repository, submit a +[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete +set of changes you'd like to see. See the +[Spec Formatting Conventions](#spec-formatting-conventions) section for the +guidelines we follow for how documents are formatted. + +Each PR must be signed per the following section. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. + +Note: If your git config information is set properly then viewing the `git log` +information for your commit will look something like this: + +``` +Author: Joe Smith +Date: Thu Feb 2 11:41:15 2018 -0800 + + Update README + + Signed-off-by: Joe Smith +``` + +Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will +be rejected by the automated DCO check. diff --git a/Cargo.lock b/Cargo.lock index 8516a3cd..357809b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,15 +30,27 @@ dependencies = [ "time", ] +[[package]] +name = "claim" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2e893ee68bf12771457cceea72497bc9cb7da404ec8a5311226d354b895ba4" +dependencies = [ + "autocfg", +] + [[package]] name = "cloudevents-sdk" version = "0.0.1" dependencies = [ "base64", "chrono", + "claim", "delegate", "hostname", + "rstest", "serde", + "serde-value", "serde_json", "uuid", ] @@ -106,6 +118,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "ordered-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +dependencies = [ + "num-traits", +] + [[package]] name = "ppv-lite86" version = "0.2.6" @@ -177,12 +198,49 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "rstest" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d5f9396fa6a44e2aa2068340b17208794515e2501c5bf3e680a0c3422a5971" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.104" @@ -192,6 +250,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.104" diff --git a/Cargo.toml b/Cargo.toml index d678c6f2..cb68b4f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,16 @@ repository = "https://github.com/cloudevents/sdk-rust" [dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" +serde-value = "^0.6" chrono = { version = "^0.4", features = ["serde"] } delegate = "^0.4" uuid = { version = "^0.8", features = ["serde", "v4"] } hostname = "^0.1" base64 = "^0.12" +[dev-dependencies] +rstest = "0.6" +claim = "0.3.1" + [lib] name = "cloudevents" diff --git a/README.md b/README.md index 23849afd..996af602 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,36 @@ -# CloudEvents Rust SDK +# CloudEvents SDK Rust Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) + +## Spec support + +| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | +| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | +| CloudEvents Core | :x: | :heavy_check_mark: | +| AMQP Protocol Binding | :x: | :x: | +| AVRO Event Format | :x: | :x: | +| HTTP Protocol Binding | :x: | :x: | +| JSON Event Format | :x: | :heavy_check_mark: | +| Kafka Protocol Binding | :x: | :x: | +| MQTT Protocol Binding | :x: | :x: | +| NATS Protocol Binding | :x: | :x: | +| Web hook | :x: | :x: | + +## Development & Contributing + +If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) + +## Community + +- There are bi-weekly calls immediately following the + [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) + at 9am PT (US Pacific). Which means they will typically start at 10am PT, but + if the other call ends early then the SDK call will start early as well. See + the + [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) + to determine which week will have the call. +- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under + [CNCF's Slack workspace](https://slack.cncf.io/). +- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk +- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` + on slack). diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 6a3525ad..be89c434 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,5 +1,4 @@ -use super::SpecVersion; -use crate::event::{AttributesV10, ExtensionValue}; +use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; use std::fmt; @@ -23,11 +22,6 @@ impl fmt::Display for AttributeValue<'_> { } } -pub(crate) trait AttributesConverter { - fn into_v03(self) -> AttributesV03; - fn into_v10(self) -> AttributesV10; -} - /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { /// Get the [id](https://github.com/cloudevents/spec/blob/master/spec.md#id). @@ -54,15 +48,11 @@ pub trait AttributesWriter { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ); - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option; +} + +pub(crate) trait AttributesConverter { + fn into_v03(self) -> AttributesV03; + fn into_v10(self) -> AttributesV10; } pub(crate) trait DataAttributesWriter { @@ -72,54 +62,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(), } } @@ -128,64 +127,67 @@ 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), } } +} - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { +impl DataAttributesWriter for Attributes { + fn set_datacontenttype(&mut self, datacontenttype: Option>) { match self { - Attributes::V10(a) => a.set_extension(extension_name, extension_value), + Attributes::V03(a) => a.set_datacontenttype(datacontenttype), + Attributes::V10(a) => a.set_datacontenttype(datacontenttype), } } - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option { + fn set_dataschema(&mut self, dataschema: Option>) { match self { - Attributes::V10(a) => a.remove_extension(extension_name), + Attributes::V03(a) => a.set_dataschema(dataschema), + Attributes::V10(a) => a.set_dataschema(dataschema), } } } -impl DataAttributesWriter for Attributes { - fn set_datacontenttype(&mut self, datacontenttype: Option>) { +impl Attributes { + pub fn into_v10(self) -> Self { match self { - Attributes::V10(a) => a.set_datacontenttype(datacontenttype), + Attributes::V03(v03) => Attributes::V10(v03.into_v10()), + _ => self, } } - - fn set_dataschema(&mut self, dataschema: Option>) { + pub fn into_v03(self) -> Self { match self { - Attributes::V10(a) => a.set_dataschema(dataschema), + 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 1811dd35..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)] -/// Possible data values pub enum Data { - String(String), + /// 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) @@ -53,9 +60,21 @@ impl TryFrom for serde_json::Value { fn try_from(value: Data) -> Result { match value { - Data::String(s) => Ok(serde_json::from_str(&s)?), Data::Binary(v) => Ok(serde_json::from_slice(&v)?), Data::Json(v) => Ok(v), + Data::String(s) => Ok(serde_json::from_str(&s)?), + } + } +} + +impl TryFrom for Vec { + type Error = serde_json::Error; + + fn try_from(value: Data) -> Result { + 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()), } } } @@ -65,9 +84,9 @@ impl TryFrom for String { fn try_from(value: Data) -> Result { match value { - Data::String(s) => Ok(s), Data::Binary(v) => Ok(String::from_utf8(v)?), - Data::Json(s) => Ok(s.to_string()), + Data::Json(v) => Ok(v.to_string()), + Data::String(s) => Ok(s), } } } diff --git a/src/event/event.rs b/src/event/event.rs index ff853720..e1c138a8 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -5,6 +5,7 @@ use super::{ use crate::event::attributes::DataAttributesWriter; use chrono::{DateTime, Utc}; use delegate::delegate; +use std::collections::HashMap; use std::convert::TryFrom; /// Data structure that represents a [CloudEvent](https://github.com/cloudevents/spec/blob/master/spec.md). @@ -35,6 +36,7 @@ use std::convert::TryFrom; pub struct Event { pub attributes: Attributes, pub data: Option, + pub extensions: HashMap, } impl AttributesReader for Event { @@ -60,15 +62,6 @@ impl AttributesWriter for Event { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ); - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option; } } } @@ -78,6 +71,7 @@ impl Default for Event { Event { attributes: Attributes::V10(AttributesV10::default()), data: None, + extensions: HashMap::default(), } } } @@ -133,22 +127,49 @@ impl Event { } } - pub fn try_get_data, E: std::error::Error>( - &self, - ) -> Option> { + pub fn try_get_data>(&self) -> Result, T::Error> { match self.data.as_ref() { Some(d) => Some(T::try_from(d.clone())), None => None, } + .transpose() } - pub fn into_data, E: std::error::Error>( - self, - ) -> Option> { + pub fn into_data>(self) -> Result, T::Error> { match self.data { Some(d) => Some(T::try_from(d)), None => None, } + .transpose() + } + + /// Get the [extension](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) named `extension_name` + pub fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { + self.extensions.get(extension_name) + } + + /// Get all the [extensions](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) + pub fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> { + self.extensions + .iter() + .map(|(k, v)| (k.as_str(), v)) + .collect() + } + + pub fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ) { + self.extensions + .insert(extension_name.to_owned(), extension_value.into()); + } + + pub fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option { + self.extensions.remove(extension_name) } } @@ -187,9 +208,7 @@ mod tests { e.remove_data(); - assert!(e - .try_get_data::() - .is_none()); + assert!(e.try_get_data::().unwrap().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 ac8f0015..3abca5c1 100644 --- a/src/event/extensions.rs +++ b/src/event/extensions.rs @@ -1,7 +1,8 @@ -use serde_json::Value; +use serde::{Deserialize, Serialize}; use std::convert::From; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged)] /// 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. @@ -10,8 +11,6 @@ pub enum ExtensionValue { Boolean(bool), /// Represents an integer [`i64`](i64) value. Integer(i64), - /// Represents a [Json `Value`](serde_json::value::Value). - Json(Value), } impl From for ExtensionValue { @@ -32,12 +31,6 @@ impl From for ExtensionValue { } } -impl From for ExtensionValue { - fn from(s: Value) -> Self { - ExtensionValue::Json(s) - } -} - impl ExtensionValue { pub fn from_string(s: S) -> Self where @@ -59,11 +52,4 @@ impl ExtensionValue { { ExtensionValue::from(s.into()) } - - pub fn from_json_value(s: S) -> Self - where - S: Into, - { - ExtensionValue::from(s.into()) - } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 888b50c8..3af14943 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -3,6 +3,8 @@ mod builder; mod data; mod event; mod extensions; +#[macro_use] +mod serde; mod spec_version; pub use attributes::Attributes; @@ -13,7 +15,16 @@ 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; pub use v10::EventBuilder as EventBuilderV10; +pub(crate) use v10::EventDeserializer as EventDeserializerV10; +pub(crate) use v10::EventSerializer as EventSerializerV10; diff --git a/src/event/serde.rs b/src/event/serde.rs new file mode 100644 index 00000000..c3910097 --- /dev/null +++ b/src/event/serde.rs @@ -0,0 +1,182 @@ +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; 2] = ["0.3", "1.0"]; + +macro_rules! parse_optional_field { + ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { + $map.remove($name) + .map(|val| match val { + Value::$value_variant(v) => Ok(v), + other => Err(<$error>::invalid_type( + crate::event::serde::value_to_unexpected(&other), + &stringify!($value_variant), + )), + }) + .transpose() + }; +} + +macro_rules! parse_field { + ($map:ident, $name:literal, $value_variant:ident, $error:ty) => { + parse_optional_field!($map, $name, $value_variant, $error)? + .ok_or_else(|| <$error>::missing_field($name)) + }; +} + +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( + map: &mut BTreeMap, + ) -> Result; + + fn deserialize_data( + 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 { + fn serialize( + attributes: &A, + data: &Option, + extensions: &HashMap, + serializer: S, + ) -> Result<::Ok, ::Error>; +} + +impl<'de> Deserialize<'de> for Event { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + let map = match Value::deserialize(deserializer)? { + Value::Map(m) => Ok(m), + v => Err(Error::invalid_type(value_to_unexpected(&v), &"a map")), + }?; + + let mut map: BTreeMap = map + .into_iter() + .map(|(k, v)| match k { + Value::String(s) => Ok((s, v)), + k => Err(Error::invalid_type(value_to_unexpected(&k), &"a string")), + }) + .collect::, >::Error>>()?; + + 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)), + } + } +} + +impl Serialize for Event { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + 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) + } + } + } +} + +// This should be provided by the Value package itself +pub(crate) fn value_to_unexpected(v: &Value) -> Unexpected { + match v { + Value::Bool(b) => serde::de::Unexpected::Bool(*b), + Value::U8(n) => serde::de::Unexpected::Unsigned(*n as u64), + Value::U16(n) => serde::de::Unexpected::Unsigned(*n as u64), + Value::U32(n) => serde::de::Unexpected::Unsigned(*n as u64), + Value::U64(n) => serde::de::Unexpected::Unsigned(*n), + Value::I8(n) => serde::de::Unexpected::Signed(*n as i64), + Value::I16(n) => serde::de::Unexpected::Signed(*n as i64), + Value::I32(n) => serde::de::Unexpected::Signed(*n as i64), + Value::I64(n) => serde::de::Unexpected::Signed(*n), + Value::F32(n) => serde::de::Unexpected::Float(*n as f64), + Value::F64(n) => serde::de::Unexpected::Float(*n), + Value::Char(c) => serde::de::Unexpected::Char(*c), + Value::String(s) => serde::de::Unexpected::Str(s), + Value::Unit => serde::de::Unexpected::Unit, + Value::Option(_) => serde::de::Unexpected::Option, + Value::Newtype(_) => serde::de::Unexpected::NewtypeStruct, + Value::Seq(_) => serde::de::Unexpected::Seq, + Value::Map(_) => serde::de::Unexpected::Map, + Value::Bytes(b) => serde::de::Unexpected::Bytes(b), + } +} diff --git a/src/event/spec_version.rs b/src/event/spec_version.rs index 113fd906..d14cb088 100644 --- a/src/event/spec_version.rs +++ b/src/event/spec_version.rs @@ -1,12 +1,9 @@ -use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone)] pub enum SpecVersion { - #[serde(rename = "0.3")] V03, - #[serde(rename = "1.0")] V10, } 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/builder.rs b/src/event/v10/builder.rs index cd114cab..9b7f8888 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -1,22 +1,29 @@ use super::Attributes as AttributesV10; use crate::event::{Attributes, AttributesWriter, Data, Event, ExtensionValue}; use chrono::{DateTime, Utc}; +use std::collections::HashMap; 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 from(event: Event) -> Self { + EventBuilder { + event: Event { + attributes: event.attributes.into_v10(), + data: event.data, + extensions: event.extensions, + }, + } + } pub fn new() -> Self { EventBuilder { event: Event { attributes: Attributes::V10(AttributesV10::default()), data: None, + extensions: HashMap::new(), }, } } @@ -112,6 +119,10 @@ mod tests { 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()); diff --git a/src/event/v10/mod.rs b/src/event/v10/mod.rs index 14b945f1..026793bb 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -1,5 +1,8 @@ mod attributes; mod builder; +mod serde; +pub(crate) use crate::event::v10::serde::EventDeserializer; +pub(crate) use crate::event::v10::serde::EventSerializer; pub use attributes::Attributes; pub use builder::EventBuilder; diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs new file mode 100644 index 00000000..fa219549 --- /dev/null +++ b/src/event/v10/serde.rs @@ -0,0 +1,106 @@ +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::V10(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)?, + dataschema: parse_optional_field!(map, "dataschema", 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 data_base64 = map.remove("data_base64"); + + 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, + }) + } +} + +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.dataschema.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", "1.0")?; + 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(dataschema) = &attributes.dataschema { + state.serialize_entry("dataschema", dataschema)?; + } + 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", &base64::encode(v))?, + _ => (), + }; + for (k, v) in extensions { + state.serialize_entry(k, v)?; + } + state.end() + } +} diff --git a/src/lib.rs b/src/lib.rs index 8c9840be..6c04514a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ extern crate serde; extern crate serde_json; +extern crate serde_value; pub mod event; 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 new file mode 100644 index 00000000..14f01619 --- /dev/null +++ b/tests/serde_json.rs @@ -0,0 +1,89 @@ +use claim::*; +use cloudevents::Event; +use rstest::rstest; +use serde_json::Value; + +mod test_data; +use test_data::*; + +/// This test is a parametrized test that uses data from tests/test_data +#[rstest( + 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_should_succeed(in_event: Event, out_json: Value) { + // Event -> serde_json::Value + let serialize_result = serde_json::to_value(in_event.clone()); + assert_ok!(&serialize_result); + let actual_json = serialize_result.unwrap(); + assert_eq!(&actual_json, &out_json); + + // serde_json::Value -> String + let actual_json_serialized = actual_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, 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 new file mode 100644 index 00000000..2e4f45c3 --- /dev/null +++ b/tests/test_data/mod.rs @@ -0,0 +1,5 @@ +mod data; +pub use data::*; + +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) +} From dacf6254928132c4ed449aba1821fdbeb39b41d0 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 18:23:09 +0530 Subject: [PATCH 20/56] Update attributes.rs Signed-off-by: Pranav Bhatt --- src/event/v10/attributes.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 9f7e5432..051c4fe8 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -62,6 +62,9 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { _ => return None, }; self.index += 1; + if result.is_none() { + return self.next() + } result } } From fd9ba4cb4fbda940dc2d225eb0d5a9095803cf3c Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Fri, 17 Apr 2020 22:13:14 +0530 Subject: [PATCH 21/56] Optimised Code #1 Resolved: 1. https://github.com/cloudevents/sdk-rust/pull/26/files#r409586754 2. https://github.com/cloudevents/sdk-rust/pull/26/files#r409585595 Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 2 +- src/event/v10/attributes.rs | 24 ++++-------------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index be89c434..958edc67 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -17,7 +17,7 @@ impl fmt::Display for AttributeValue<'_> { AttributeValue::String(s) => f.write_str(s), AttributeValue::URI(s) => f.write_str(s), AttributeValue::URIRef(s) => f.write_str(s), - AttributeValue::Time(s) => f.write_str(&s.to_rfc2822()), + AttributeValue::Time(s) => f.write_str(&s.to_rfc3339()), } } } diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 051c4fe8..c6643eaf 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -32,22 +32,6 @@ pub struct AttributesIntoIterator<'a> { index: usize, } -fn option_checker_string<'a>(attribute_type: &'a str,input:Option<&'a str>) -> Option<(&'a str,AttributeValue<'a>)> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::String(x))), - None => None, - }; - result -} - -fn option_checker_time<'a>(attribute_type: &'a str,input:Option<&'a DateTime>) -> Option<(&'a str,AttributeValue<'a>)> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::Time(x))), - None => None, - }; - result -} - impl<'a> Iterator for AttributesIntoIterator<'a> { type Item = (&'a str, AttributeValue<'a>); fn next(&mut self) -> Option { @@ -55,10 +39,10 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), 2 => Some(("source", AttributeValue::String(&self.attributes.source))), - 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), - 4 => option_checker_string("dataschema",self.attributes.get_dataschema()), - 5 => option_checker_string("subject",self.attributes.get_subject()), - 6 => option_checker_time("time",self.attributes.get_time()), + 3 => self.attributes.datacontenttype.as_ref().map(|v| ("datacontenttype", AttributeValue::String(v))), + 4 => self.attributes.dataschema.as_ref().map(|v| ("dataschema", AttributeValue::String(v))), + 5 => self.attributes.subject.as_ref().map(|v| ("subject", AttributeValue::String(v))), + 6 => self.attributes.time.as_ref().map(|v| ("time", AttributeValue::Time(v))), _ => return None, }; self.index += 1; From d316616ec3e3eb814a392d2e884615fc57ccb112 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 19:48:18 +0530 Subject: [PATCH 22/56] Added Test in v10/attributes Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 1 + src/event/mod.rs | 1 + src/event/v10/attributes.rs | 22 +++++++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 958edc67..dcbb052f 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -2,6 +2,7 @@ use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; use std::fmt; +#[derive(Debug, PartialEq)] pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), diff --git a/src/event/mod.rs b/src/event/mod.rs index 3af14943..0ea3a42f 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -8,6 +8,7 @@ mod serde; mod spec_version; pub use attributes::Attributes; +pub use attributes::AttributeValue as AttributeValue; pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index c6643eaf..a3446c6f 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,6 +1,6 @@ use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc, NaiveDateTime}; use hostname::get_hostname; use uuid::Uuid; @@ -159,3 +159,23 @@ impl AttributesConverter for Attributes { } } } + +#[test] +fn iterator_test(){ + let a = Attributes{ + id: String::from("1"), + ty: String::from("someType"), + source: String::from("Test"), + datacontenttype: None, + dataschema: None, + subject: None, + time: Some(DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc)), + }; + let b = &mut a.into_iter(); + let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + + assert_eq!(("id",AttributeValue::String("1")),b.next().unwrap()); + assert_eq!(("ty",AttributeValue::String("someType")),b.next().unwrap()); + assert_eq!(("source",AttributeValue::String("Test")),b.next().unwrap()); + assert_eq!(("time",AttributeValue::Time(&time)),b.next().unwrap()); +} From 85226aeabfe69e3d6893b152cd249829dbb2228d Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 20:08:38 +0530 Subject: [PATCH 23/56] Ran cargo fmt Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 2 +- src/event/mod.rs | 2 +- src/event/v10/attributes.rs | 55 +++++++++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index dcbb052f..423cdc0f 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -8,7 +8,7 @@ pub enum AttributeValue<'a> { String(&'a str), URI(&'a str), URIRef(&'a str), - Time(&'a DateTime) + Time(&'a DateTime), } impl fmt::Display for AttributeValue<'_> { diff --git a/src/event/mod.rs b/src/event/mod.rs index 0ea3a42f..2a79eaa2 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -7,8 +7,8 @@ mod extensions; mod serde; mod spec_version; +pub use attributes::AttributeValue; pub use attributes::Attributes; -pub use attributes::AttributeValue as AttributeValue; pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index a3446c6f..bc8d845e 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,6 +1,6 @@ -use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; +use crate::event::attributes::{AttributeValue, AttributesConverter, DataAttributesWriter}; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; -use chrono::{DateTime, Utc, NaiveDateTime}; +use chrono::{DateTime, NaiveDateTime, Utc}; use hostname::get_hostname; use uuid::Uuid; @@ -37,17 +37,33 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { fn next(&mut self) -> Option { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), - 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), 2 => Some(("source", AttributeValue::String(&self.attributes.source))), - 3 => self.attributes.datacontenttype.as_ref().map(|v| ("datacontenttype", AttributeValue::String(v))), - 4 => self.attributes.dataschema.as_ref().map(|v| ("dataschema", AttributeValue::String(v))), - 5 => self.attributes.subject.as_ref().map(|v| ("subject", AttributeValue::String(v))), - 6 => self.attributes.time.as_ref().map(|v| ("time", AttributeValue::Time(v))), + 3 => self + .attributes + .datacontenttype + .as_ref() + .map(|v| ("datacontenttype", AttributeValue::String(v))), + 4 => self + .attributes + .dataschema + .as_ref() + .map(|v| ("dataschema", AttributeValue::String(v))), + 5 => self + .attributes + .subject + .as_ref() + .map(|v| ("subject", AttributeValue::String(v))), + 6 => self + .attributes + .time + .as_ref() + .map(|v| ("time", AttributeValue::Time(v))), _ => return None, }; self.index += 1; if result.is_none() { - return self.next() + return self.next(); } result } @@ -161,21 +177,30 @@ impl AttributesConverter for Attributes { } #[test] -fn iterator_test(){ - let a = Attributes{ +fn iterator_test() { + let a = Attributes { id: String::from("1"), ty: String::from("someType"), source: String::from("Test"), datacontenttype: None, dataschema: None, subject: None, - time: Some(DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc)), + time: Some(DateTime::::from_utc( + NaiveDateTime::from_timestamp(61, 0), + Utc, + )), }; let b = &mut a.into_iter(); let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); - assert_eq!(("id",AttributeValue::String("1")),b.next().unwrap()); - assert_eq!(("ty",AttributeValue::String("someType")),b.next().unwrap()); - assert_eq!(("source",AttributeValue::String("Test")),b.next().unwrap()); - assert_eq!(("time",AttributeValue::Time(&time)),b.next().unwrap()); + assert_eq!(("id", AttributeValue::String("1")), b.next().unwrap()); + assert_eq!( + ("ty", AttributeValue::String("someType")), + b.next().unwrap() + ); + assert_eq!( + ("source", AttributeValue::String("Test")), + b.next().unwrap() + ); + assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); } From eb3432fa2dbc85729a170da3cbfa905b014de25f Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 21:52:50 +0530 Subject: [PATCH 24/56] Added iterator to AttributeV03 Signed-off-by: Pranav Bhatt --- src/event/mod.rs | 1 - src/event/v03/attributes.rs | 88 ++++++++++++++++++++++++++++++++++++- src/event/v10/attributes.rs | 2 +- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/event/mod.rs b/src/event/mod.rs index 2a79eaa2..3af14943 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -7,7 +7,6 @@ mod extensions; mod serde; mod spec_version; -pub use attributes::AttributeValue; pub use attributes::Attributes; pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index 1e75629e..52166e5d 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -1,7 +1,7 @@ -use crate::event::attributes::{AttributesConverter, DataAttributesWriter}; +use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; use crate::event::AttributesV10; use crate::event::{AttributesReader, AttributesWriter, SpecVersion}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc, NaiveDateTime}; use hostname::get_hostname; use uuid::Uuid; @@ -16,6 +16,60 @@ pub struct Attributes { pub(crate) time: Option>, } +impl<'a> IntoIterator for &'a Attributes { + type Item = (&'a str, AttributeValue<'a>); + type IntoIter = AttributesIntoIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + AttributesIntoIterator { + attributes: self, + index: 0, + } + } +} + +pub struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, +} + +impl<'a> Iterator for AttributesIntoIterator<'a> { + type Item = (&'a str, AttributeValue<'a>); + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(("id", AttributeValue::String(&self.attributes.id))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 3 => self + .attributes + .datacontenttype + .as_ref() + .map(|v| ("datacontenttype", AttributeValue::String(v))), + 4 => self + .attributes + .schemaurl + .as_ref() + .map(|v| ("dataschema", AttributeValue::String(v))), + 5 => self + .attributes + .subject + .as_ref() + .map(|v| ("subject", AttributeValue::String(v))), + 6 => self + .attributes + .time + .as_ref() + .map(|v| ("time", AttributeValue::Time(v))), + _ => return None, + }; + self.index += 1; + if result.is_none() { + return self.next(); + } + result + } +} + impl AttributesReader for Attributes { fn get_id(&self) -> &str { &self.id @@ -122,3 +176,33 @@ impl AttributesConverter for Attributes { } } } + +#[test] +fn iterator_test_V03() { + let a = Attributes { + id: String::from("1"), + ty: String::from("someType"), + source: String::from("Test"), + datacontenttype: None, + schemaurl: None, + subject: None, + time: Some(DateTime::::from_utc( + NaiveDateTime::from_timestamp(61, 0), + Utc, + )), + }; + let b = &mut a.into_iter(); + let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + + assert_eq!(("id", AttributeValue::String("1")), b.next().unwrap()); + assert_eq!( + ("ty", AttributeValue::String("someType")), + b.next().unwrap() + ); + assert_eq!( + ("source", AttributeValue::String("Test")), + b.next().unwrap() + ); + assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); +} + diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index bc8d845e..19addad0 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -177,7 +177,7 @@ impl AttributesConverter for Attributes { } #[test] -fn iterator_test() { +fn iterator_test_V10() { let a = Attributes { id: String::from("1"), ty: String::from("someType"), From 664be4326bd5db44726289ae60e457e5b803534a Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 22:08:28 +0530 Subject: [PATCH 25/56] updated .gitignore for /target Signed-off-by: Pranav Bhatt --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 185ca35e..02a1b1cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target +.target .idea From 6714129a1fb32e78871061be9bb76457c007bda2 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 22:18:34 +0530 Subject: [PATCH 26/56] updated .gitignore Signed-off-by: Pranav Bhatt --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 02a1b1cb..e61a28bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/target +/target/ -.target .idea From f85ae18ae50cf3a172bc0b3e4a56dd79ac154aa0 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 22:54:31 +0530 Subject: [PATCH 27/56] Deleting incorrect EOL files Signed-off-by: Pranav Bhatt --- CONTRIBUTING.md | 129 ------------------------------------------------ README.md | 36 -------------- 2 files changed, 165 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a7614ad1..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,129 +0,0 @@ -# Contributing to CloudEvents SDK Rust - -This page contains information about reporting issues, how to suggest changes as -well as the guidelines we follow for how our documents are formatted. - -## Table of Contents - -- [Reporting an Issue](#reporting-an-issue) -- [Preparing the environment](#preparing-the-environment) -- [Suggesting a Change](#suggesting-a-change) - -## Reporting an Issue - -To report an issue, or to suggest an idea for a change that you haven't had time -to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It -is best to check our existing -[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar -one has already been opened and discussed. - -## Preparing the environment - -In order to start developing this project, -you need to install the Rust tooling using [rustup](https://rustup.rs/). - -### Development commands - -To build the project: - -```sh -cargo build --all-features -``` - -To run all tests: - -```sh -cargo test --all-features -``` - -To build and open the documentation: - -```sh -cargo doc --lib --open -``` - -To run the code formatter: - -```sh -cargo fmt -``` - -## Suggesting a change - -To suggest a change to this repository, submit a -[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete -set of changes you'd like to see. See the -[Spec Formatting Conventions](#spec-formatting-conventions) section for the -guidelines we follow for how documents are formatted. - -Each PR must be signed per the following section. - -### Sign your work - -The sign-off is a simple line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. The rules are pretty simple: if you can certify -the below (from [developercertificate.org](http://developercertificate.org/)): - -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -Then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -Use your real name (sorry, no pseudonyms or anonymous contributions.) - -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. - -Note: If your git config information is set properly then viewing the `git log` -information for your commit will look something like this: - -``` -Author: Joe Smith -Date: Thu Feb 2 11:41:15 2018 -0800 - - Update README - - Signed-off-by: Joe Smith -``` - -Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will -be rejected by the automated DCO check. diff --git a/README.md b/README.md deleted file mode 100644 index 996af602..00000000 --- a/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# CloudEvents SDK Rust - -Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) - -## Spec support - -| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | -| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | -| CloudEvents Core | :x: | :heavy_check_mark: | -| AMQP Protocol Binding | :x: | :x: | -| AVRO Event Format | :x: | :x: | -| HTTP Protocol Binding | :x: | :x: | -| JSON Event Format | :x: | :heavy_check_mark: | -| Kafka Protocol Binding | :x: | :x: | -| MQTT Protocol Binding | :x: | :x: | -| NATS Protocol Binding | :x: | :x: | -| Web hook | :x: | :x: | - -## Development & Contributing - -If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) - -## Community - -- There are bi-weekly calls immediately following the - [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) - at 9am PT (US Pacific). Which means they will typically start at 10am PT, but - if the other call ends early then the SDK call will start early as well. See - the - [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) - to determine which week will have the call. -- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under - [CNCF's Slack workspace](https://slack.cncf.io/). -- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk -- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` - on slack). From 01aa093f9524c5fb8d3d14f089e281c2ae00857c Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Fri, 20 Mar 2020 13:21:16 +0100 Subject: [PATCH 28/56] WIP iter attributes Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 37 +++++++++++++++++++ .../{extensions.rs => extension_value.rs} | 0 src/event/mod.rs | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) rename src/event/{extensions.rs => extension_value.rs} (100%) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 6470126f..4fad44db 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,6 +1,43 @@ use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; use url::Url; +use serde::{Deserialize, Serialize}; +use std::fmt; + +impl ExactSizeIterator for Iter { + type Item = (&'a str, AttributeValue<'a>); + + fn next(&mut self) -> Option { + let new_next = self.curr + self.next; + + self.curr = self.next; + self.next = new_next; + + // Since there's no endpoint to a Fibonacci sequence, the `Iterator` + // will never return `None`, and `Some` is always returned. + Some(self.curr) + } +} + +pub enum AttributeValue<'a> { + SpecVersion(SpecVersion), + String(&'a str), + URI(&'a str), + URIRef(&'a str), + Time(&'a DateTime) +} + +impl fmt::Display for AttributeValue<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AttributeValue::SpecVersion(s) => s.fmt(f), + AttributeValue::String(s) => f.write_str(s), + AttributeValue::URI(s) => f.write_str(s), + AttributeValue::URIRef(s) => f.write_str(s), + AttributeValue::Time(s) => f.write_str(&s.to_rfc2822()), + } + } +} /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { diff --git a/src/event/extensions.rs b/src/event/extension_value.rs similarity index 100% rename from src/event/extensions.rs rename to src/event/extension_value.rs diff --git a/src/event/mod.rs b/src/event/mod.rs index 41fb5b91..c5fc2719 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -13,7 +13,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extensions::ExtensionValue; +pub use extension_value::ExtensionValue; pub use spec_version::SpecVersion; mod v03; From d3ed64cf095927a2f33be23d64941d6c8fc9f32a Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Tue, 14 Apr 2020 18:24:42 +0530 Subject: [PATCH 29/56] adding iterator to v10/attributes (WIP) Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 15 --------- src/event/v10/attributes.rs | 63 ++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 4fad44db..1af5b13d 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -4,21 +4,6 @@ use url::Url; use serde::{Deserialize, Serialize}; use std::fmt; -impl ExactSizeIterator for Iter { - type Item = (&'a str, AttributeValue<'a>); - - fn next(&mut self) -> Option { - let new_next = self.curr + self.next; - - self.curr = self.next; - self.next = new_next; - - // Since there's no endpoint to a Fibonacci sequence, the `Iterator` - // will never return `None`, and `Some` is always returned. - Some(self.curr) - } -} - pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index f7a429ca..d5dbf959 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,6 +1,7 @@ -use crate::event::attributes::{AttributesConverter, DataAttributesWriter}; +use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; +use chrono::NaiveDate; use hostname::get_hostname; use url::Url; use uuid::Uuid; @@ -16,6 +17,66 @@ pub struct Attributes { pub(crate) time: Option>, } +impl<'a> IntoIterator for &'a Attributes { + type Item = (&'a str, AttributeValue<'a>); + type IntoIter = AttributesIntoIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + AttributesIntoIterator { + attributes: self, + index: 0, + } + } +} + +fn time_to_string(input:&Option>) -> &str { + let result = match *input { + Some(x) => &x.to_rfc2822(), + None => "", + }; + result +} + +fn option_to_time(input:&Option>) -> &DateTime { + let result = match *input { + Some(x) => &x, + None => &DateTime::::from_utc(NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0), Utc), + }; + result +} + +fn option_to_string(input:&Option) -> &str { + let result = match *input { + Some(x) => &x[..], + None => "", + }; + result +} + +struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, +} + +impl<'a> Iterator for AttributesIntoIterator<'a> { + type Item = (&'a str, AttributeValue<'a>); + fn next(&mut self) -> Option { + let result = match self.index { + 0 => ("id", AttributeValue::String(&self.attributes.id[..])), + 1 => ("ty", AttributeValue::String(&self.attributes.ty[..])), + 2 => ("source", AttributeValue::String(&self.attributes.source[..])), + 3 => ("datacontenttype", AttributeValue::String(option_to_string(&self.attributes.datacontenttype))), + 4 => ("dataschema", AttributeValue::String(option_to_string(&self.attributes.dataschema))), + 5 => ("subject", AttributeValue::String(option_to_string(&self.attributes.subject))), + 6 => ("time", AttributeValue::Time(option_to_time(&self.attributes.time))), + 7 => ("extensions",self.attributes.extensions), + _ => return None, + }; + self.index += 1; + Some(result) + } +} + impl AttributesReader for Attributes { fn get_id(&self) -> &str { &self.id From 27bc90f932fc345a4ef5902d069f78a7cf370d75 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Tue, 14 Apr 2020 23:08:53 +0530 Subject: [PATCH 30/56] rebasing upstream Signed-off-by: Pranav Bhatt --- src/event/v10/attributes.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index d5dbf959..5f9a04e2 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -29,14 +29,6 @@ impl<'a> IntoIterator for &'a Attributes { } } -fn time_to_string(input:&Option>) -> &str { - let result = match *input { - Some(x) => &x.to_rfc2822(), - None => "", - }; - result -} - fn option_to_time(input:&Option>) -> &DateTime { let result = match *input { Some(x) => &x, @@ -62,14 +54,13 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { type Item = (&'a str, AttributeValue<'a>); fn next(&mut self) -> Option { let result = match self.index { - 0 => ("id", AttributeValue::String(&self.attributes.id[..])), - 1 => ("ty", AttributeValue::String(&self.attributes.ty[..])), - 2 => ("source", AttributeValue::String(&self.attributes.source[..])), + 0 => ("id", AttributeValue::String(&self.attributes.id)), + 1 => ("ty", AttributeValue::String(&self.attributes.ty)), + 2 => ("source", AttributeValue::String(&self.attributes.source)), 3 => ("datacontenttype", AttributeValue::String(option_to_string(&self.attributes.datacontenttype))), 4 => ("dataschema", AttributeValue::String(option_to_string(&self.attributes.dataschema))), 5 => ("subject", AttributeValue::String(option_to_string(&self.attributes.subject))), 6 => ("time", AttributeValue::Time(option_to_time(&self.attributes.time))), - 7 => ("extensions",self.attributes.extensions), _ => return None, }; self.index += 1; From bde258c60fba5e47035523b7055159dae8230b14 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 12:11:00 +0530 Subject: [PATCH 31/56] Completed Iterator Signed-off-by: Pranav Bhatt --- src/event/v10/attributes.rs | 43 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 5f9a04e2..70b6a94f 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,7 +1,6 @@ use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; -use chrono::NaiveDate; use hostname::get_hostname; use url::Url; use uuid::Uuid; @@ -29,42 +28,42 @@ impl<'a> IntoIterator for &'a Attributes { } } -fn option_to_time(input:&Option>) -> &DateTime { - let result = match *input { - Some(x) => &x, - None => &DateTime::::from_utc(NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0), Utc), - }; - result +struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, } -fn option_to_string(input:&Option) -> &str { - let result = match *input { - Some(x) => &x[..], - None => "", +fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::String(x))), + None => None, }; result } -struct AttributesIntoIterator<'a> { - attributes: &'a Attributes, - index: usize, +fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::Time(x))), + None => None, + }; + result } impl<'a> Iterator for AttributesIntoIterator<'a> { type Item = (&'a str, AttributeValue<'a>); fn next(&mut self) -> Option { let result = match self.index { - 0 => ("id", AttributeValue::String(&self.attributes.id)), - 1 => ("ty", AttributeValue::String(&self.attributes.ty)), - 2 => ("source", AttributeValue::String(&self.attributes.source)), - 3 => ("datacontenttype", AttributeValue::String(option_to_string(&self.attributes.datacontenttype))), - 4 => ("dataschema", AttributeValue::String(option_to_string(&self.attributes.dataschema))), - 5 => ("subject", AttributeValue::String(option_to_string(&self.attributes.subject))), - 6 => ("time", AttributeValue::Time(option_to_time(&self.attributes.time))), + 0 => Some(("id", AttributeValue::String(&self.attributes.id))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), + 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), + 5 => option_checker_string("subject",self.attributes.subject.get_subject()), + 6 => option_checker_time("time",self.attributes.time.get_time()), _ => return None, }; self.index += 1; - Some(result) + result } } From 58242c77c9517f37694bf834187fa698b9eaaa11 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Thu, 26 Mar 2020 19:46:37 +0100 Subject: [PATCH 32/56] Implemented custom serialization process Signed-off-by: Francesco Guardiani Signed-off-by: Pranav Bhatt --- src/event/serde.rs | 22 ++++++++++++++++++ src/event/v10/serde.rs | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/event/serde.rs b/src/event/serde.rs index 89d29a48..5532f2eb 100644 --- a/src/event/serde.rs +++ b/src/event/serde.rs @@ -134,6 +134,15 @@ pub(crate) trait EventSerializer { ) -> Result<::Ok, ::Error>; } +pub(crate) trait EventSerializer { + fn serialize( + attributes: &A, + data: &Option, + extensions: &HashMap, + serializer: S, + ) -> Result<::Ok, ::Error>; +} + impl<'de> Deserialize<'de> for Event { fn deserialize(deserializer: D) -> Result>::Error> where @@ -179,6 +188,19 @@ impl Serialize for Event { } } +impl Serialize for Event { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + match &self.attributes { + Attributes::V10(a) => { + EventSerializerV10::serialize(a, &self.data, &self.extensions, serializer) + } + } + } +} + // This should be provided by the Value package itself pub(crate) fn value_to_unexpected(v: &Value) -> Unexpected { match v { diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs index 2b518ebd..c3ccdc61 100644 --- a/src/event/v10/serde.rs +++ b/src/event/v10/serde.rs @@ -100,3 +100,54 @@ impl crate::event::serde::EventSerializer f state.end() } } + +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.dataschema.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", "1.0")?; + 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(dataschema) = &attributes.dataschema { + state.serialize_entry("dataschema", dataschema)?; + } + 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::Binary(v)) => state.serialize_entry("data_base64", &base64::encode(v))?, + _ => (), + }; + for (k, v) in extensions { + state.serialize_entry(k, v)?; + } + state.end() + } +} From d3c3384098a6e1fef048e1fbe9b3276937141f9f Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Fri, 10 Apr 2020 09:21:26 +0200 Subject: [PATCH 33/56] V0.3 implementation (#24) * Added String variant to Data Signed-off-by: Francesco Guardiani * Started V0.3 work Signed-off-by: Francesco Guardiani * Reworked EventDeserializer trait Signed-off-by: Francesco Guardiani * Now event parsing works with v1 and changes Signed-off-by: Francesco Guardiani * Cargo fmt Signed-off-by: Francesco Guardiani * Reorganized test data Signed-off-by: Francesco Guardiani * Fixed serde for v03 Signed-off-by: Francesco Guardiani * Implemented spec version conversion Signed-off-by: Francesco Guardiani * cargo fmt Signed-off-by: Francesco Guardiani Signed-off-by: Pranav Bhatt --- src/event/data.rs | 4 ++++ src/event/serde.rs | 43 ++++++++++++++++++++++++++++++++++++++++++ src/event/v10/serde.rs | 1 + 3 files changed, 48 insertions(+) diff --git a/src/event/data.rs b/src/event/data.rs index 4acf4a87..25283c92 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -49,6 +49,10 @@ pub(crate) fn is_json_content_type(ct: &str) -> bool { ct == "application/json" || ct == "text/json" || ct.ends_with("+json") } +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) diff --git a/src/event/serde.rs b/src/event/serde.rs index 5532f2eb..1f8352a6 100644 --- a/src/event/serde.rs +++ b/src/event/serde.rs @@ -91,6 +91,46 @@ macro_rules! parse_data_base64 { }; } +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( map: &mut BTreeMap, @@ -194,6 +234,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/v10/serde.rs b/src/event/v10/serde.rs index c3ccdc61..acfd9136 100644 --- a/src/event/v10/serde.rs +++ b/src/event/v10/serde.rs @@ -142,6 +142,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))?, _ => (), }; From db179804cb8493933f2435be1e3450de92e46c4e Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 16:52:46 +0530 Subject: [PATCH 34/56] Correcting errors Signed-off-by: Pranav Bhatt --- src/event/{extension_value.rs => extensions.rs} | 0 src/event/mod.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/event/{extension_value.rs => extensions.rs} (100%) diff --git a/src/event/extension_value.rs b/src/event/extensions.rs similarity index 100% rename from src/event/extension_value.rs rename to src/event/extensions.rs diff --git a/src/event/mod.rs b/src/event/mod.rs index c5fc2719..41fb5b91 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -13,7 +13,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extension_value::ExtensionValue; +pub use extensions::ExtensionValue; pub use spec_version::SpecVersion; mod v03; From 4f9e279dddb5eb77e8ea5f4cc35a4dbcb64db8ce Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 13:14:21 +0530 Subject: [PATCH 35/56] roll back Signed-off-by: Pranav Bhatt --- CONTRIBUTING.md | 129 ------------------------------------ Cargo.lock | 59 ----------------- Cargo.toml | 5 -- README.md | 35 +--------- src/event/attributes.rs | 89 +++++++++++++++---------- src/event/builder.rs | 12 +--- src/event/data.rs | 29 ++------ src/event/event.rs | 57 ++++++---------- src/event/extensions.rs | 20 +++++- src/event/mod.rs | 9 --- src/event/spec_version.rs | 3 + src/event/v10/attributes.rs | 47 +++++++------ src/event/v10/builder.rs | 14 ++-- src/event/v10/mod.rs | 2 - tests/event.rs | 2 + tests/serde_json.rs | 89 ------------------------- tests/version_conversion.rs | 17 ----- 17 files changed, 135 insertions(+), 483 deletions(-) delete mode 100644 CONTRIBUTING.md create mode 100644 tests/event.rs delete mode 100644 tests/serde_json.rs delete mode 100644 tests/version_conversion.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a7614ad1..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,129 +0,0 @@ -# Contributing to CloudEvents SDK Rust - -This page contains information about reporting issues, how to suggest changes as -well as the guidelines we follow for how our documents are formatted. - -## Table of Contents - -- [Reporting an Issue](#reporting-an-issue) -- [Preparing the environment](#preparing-the-environment) -- [Suggesting a Change](#suggesting-a-change) - -## Reporting an Issue - -To report an issue, or to suggest an idea for a change that you haven't had time -to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It -is best to check our existing -[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar -one has already been opened and discussed. - -## Preparing the environment - -In order to start developing this project, -you need to install the Rust tooling using [rustup](https://rustup.rs/). - -### Development commands - -To build the project: - -```sh -cargo build --all-features -``` - -To run all tests: - -```sh -cargo test --all-features -``` - -To build and open the documentation: - -```sh -cargo doc --lib --open -``` - -To run the code formatter: - -```sh -cargo fmt -``` - -## Suggesting a change - -To suggest a change to this repository, submit a -[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete -set of changes you'd like to see. See the -[Spec Formatting Conventions](#spec-formatting-conventions) section for the -guidelines we follow for how documents are formatted. - -Each PR must be signed per the following section. - -### Sign your work - -The sign-off is a simple line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. The rules are pretty simple: if you can certify -the below (from [developercertificate.org](http://developercertificate.org/)): - -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -Then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -Use your real name (sorry, no pseudonyms or anonymous contributions.) - -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. - -Note: If your git config information is set properly then viewing the `git log` -information for your commit will look something like this: - -``` -Author: Joe Smith -Date: Thu Feb 2 11:41:15 2018 -0800 - - Update README - - Signed-off-by: Joe Smith -``` - -Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will -be rejected by the automated DCO check. diff --git a/Cargo.lock b/Cargo.lock index 700e3135..6a1b3439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,27 +30,15 @@ dependencies = [ "time", ] -[[package]] -name = "claim" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2e893ee68bf12771457cceea72497bc9cb7da404ec8a5311226d354b895ba4" -dependencies = [ - "autocfg", -] - [[package]] name = "cloudevents-sdk" version = "0.0.1" dependencies = [ "base64", "chrono", - "claim", "delegate", "hostname", - "rstest", "serde", - "serde-value", "serde_json", "snafu", "url", @@ -229,49 +217,12 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -[[package]] -name = "rstest" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d5f9396fa6a44e2aa2068340b17208794515e2501c5bf3e680a0c3422a5971" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.104" @@ -281,16 +232,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.104" diff --git a/Cargo.toml b/Cargo.toml index 4d19f895..9a478219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ repository = "https://github.com/cloudevents/sdk-rust" [dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" -serde-value = "^0.6" chrono = { version = "^0.4", features = ["serde"] } delegate = "^0.4" uuid = { version = "^0.8", features = ["serde", "v4"] } @@ -22,9 +21,5 @@ base64 = "^0.12" url = { version = "^2.1", features = ["serde"] } snafu = "^0.6" -[dev-dependencies] -rstest = "0.6" -claim = "0.3.1" - [lib] name = "cloudevents" diff --git a/README.md b/README.md index 996af602..23849afd 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,3 @@ -# CloudEvents SDK Rust +# CloudEvents Rust SDK Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) - -## Spec support - -| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | -| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | -| CloudEvents Core | :x: | :heavy_check_mark: | -| AMQP Protocol Binding | :x: | :x: | -| AVRO Event Format | :x: | :x: | -| HTTP Protocol Binding | :x: | :x: | -| JSON Event Format | :x: | :heavy_check_mark: | -| Kafka Protocol Binding | :x: | :x: | -| MQTT Protocol Binding | :x: | :x: | -| NATS Protocol Binding | :x: | :x: | -| Web hook | :x: | :x: | - -## Development & Contributing - -If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) - -## Community - -- There are bi-weekly calls immediately following the - [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) - at 9am PT (US Pacific). Which means they will typically start at 10am PT, but - if the other call ends early then the SDK call will start early as well. See - the - [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) - to determine which week will have the call. -- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under - [CNCF's Slack workspace](https://slack.cncf.io/). -- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk -- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` - on slack). diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 1af5b13d..a21c2164 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,9 +1,25 @@ -use super::{AttributesV03, AttributesV10, SpecVersion}; +use super::SpecVersion; +use crate::event::{AttributesV10, ExtensionValue}; use chrono::{DateTime, Utc}; use url::Url; use serde::{Deserialize, Serialize}; use std::fmt; +impl ExactSizeIterator for Iter { + type Item = (&'a str, AttributeValue<'a>); + + fn next(&mut self) -> Option { + let new_next = self.curr + self.next; + + self.curr = self.next; + self.next = new_next; + + // Since there's no endpoint to a Fibonacci sequence, the `Iterator` + // will never return `None`, and `Some` is always returned. + Some(self.curr) + } +} + pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), @@ -42,6 +58,10 @@ pub trait AttributesReader { 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>; + /// 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) + fn iter_extensions(&self) -> std::collections::hash_map::Iter; } pub trait AttributesWriter { @@ -50,11 +70,15 @@ pub trait AttributesWriter { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); -} - -pub(crate) trait AttributesConverter { - fn into_v03(self) -> AttributesV03; - fn into_v10(self) -> AttributesV10; + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ); + fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option; } pub(crate) trait DataAttributesWriter { @@ -64,132 +88,129 @@ 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) -> &Url { 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<&Url> { 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(), } } + + fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { + match self { + Attributes::V10(a) => a.get_extension(extension_name), + } + } + + fn iter_extensions(&self) -> std::collections::hash_map::Iter { + match self { + Attributes::V10(a) => a.iter_extensions(), + } + } } 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), } } -} -impl DataAttributesWriter for Attributes { - fn set_datacontenttype(&mut self, datacontenttype: Option>) { + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ) { match self { - Attributes::V03(a) => a.set_datacontenttype(datacontenttype), - Attributes::V10(a) => a.set_datacontenttype(datacontenttype), + Attributes::V10(a) => a.set_extension(extension_name, extension_value), } } fn set_dataschema(&mut self, dataschema: Option>) { match self { - Attributes::V03(a) => a.set_dataschema(dataschema), - Attributes::V10(a) => a.set_dataschema(dataschema), + Attributes::V10(a) => a.remove_extension(extension_name), } } } -impl Attributes { - pub fn into_v10(self) -> Self { +impl DataAttributesWriter for Attributes { + fn set_datacontenttype(&mut self, datacontenttype: Option>) { match self { - Attributes::V03(v03) => Attributes::V10(v03.into_v10()), - _ => self, + Attributes::V10(a) => a.set_datacontenttype(datacontenttype), } } - pub fn into_v03(self) -> Self { + + fn set_dataschema(&mut self, dataschema: Option>) { match self { - Attributes::V10(v10) => Attributes::V03(v10.into_v03()), - _ => self, + Attributes::V10(a) => a.set_dataschema(dataschema), } } } diff --git a/src/event/builder.rs b/src/event/builder.rs index ead19817..8c903184 100644 --- a/src/event/builder.rs +++ b/src/event/builder.rs @@ -1,4 +1,4 @@ -use super::{EventBuilderV03, EventBuilderV10}; +use super::EventBuilderV10; /// Builder to create [`Event`]: /// ``` @@ -15,18 +15,8 @@ use super::{EventBuilderV03, 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 25283c92..db7caa53 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -1,13 +1,10 @@ use std::convert::{Into, TryFrom}; -/// Event [data attribute](https://github.com/cloudevents/spec/blob/master/spec.md#event-data) representation #[derive(Debug, PartialEq, Clone)] +/// Possible data values pub enum Data { - /// Event has a binary payload - Binary(Vec), - /// Event has a non-json string payload String(String), - /// Event has a json payload + Binary(Vec), Json(serde_json::Value), } @@ -49,10 +46,6 @@ pub(crate) fn is_json_content_type(ct: &str) -> bool { ct == "application/json" || ct == "text/json" || ct.ends_with("+json") } -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) @@ -76,21 +69,9 @@ impl TryFrom for serde_json::Value { fn try_from(value: Data) -> Result { 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)?), - } - } -} - -impl TryFrom for Vec { - type Error = serde_json::Error; - - fn try_from(value: Data) -> Result { - 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()), + Data::Json(v) => Ok(v), } } } @@ -100,9 +81,9 @@ impl TryFrom for String { fn try_from(value: Data) -> Result { match value { - Data::Binary(v) => Ok(String::from_utf8(v)?), - Data::Json(v) => Ok(v.to_string()), Data::String(s) => Ok(s), + Data::Binary(v) => Ok(String::from_utf8(v)?), + Data::Json(s) => Ok(s.to_string()), } } } diff --git a/src/event/event.rs b/src/event/event.rs index 188be16b..5a57afa4 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -5,7 +5,6 @@ use super::{ use crate::event::attributes::DataAttributesWriter; use chrono::{DateTime, Utc}; use delegate::delegate; -use std::collections::HashMap; use std::convert::TryFrom; use url::Url; @@ -37,7 +36,6 @@ use url::Url; pub struct Event { pub attributes: Attributes, pub data: Option, - pub extensions: HashMap, } impl AttributesReader for Event { @@ -51,6 +49,8 @@ impl AttributesReader for Event { fn get_dataschema(&self) -> Option<&Url>; fn get_subject(&self) -> Option<&str>; fn get_time(&self) -> Option<&DateTime>; + fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; + fn iter_extensions(&self) -> std::collections::hash_map::Iter; } } } @@ -63,6 +63,15 @@ impl AttributesWriter for Event { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ); + fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option; } } } @@ -72,7 +81,6 @@ impl Default for Event { Event { attributes: Attributes::V10(AttributesV10::default()), data: None, - extensions: HashMap::default(), } } } @@ -133,49 +141,22 @@ impl Event { } } - pub fn try_get_data>(&self) -> Result, T::Error> { + pub fn try_get_data, E: std::error::Error>( + &self, + ) -> Option> { match self.data.as_ref() { Some(d) => Some(T::try_from(d.clone())), None => None, } - .transpose() } - pub fn into_data>(self) -> Result, T::Error> { + pub fn into_data, E: std::error::Error>( + self, + ) -> Option> { match self.data { Some(d) => Some(T::try_from(d)), None => None, } - .transpose() - } - - /// Get the [extension](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) named `extension_name` - pub fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - self.extensions.get(extension_name) - } - - /// Get all the [extensions](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) - pub fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> { - self.extensions - .iter() - .map(|(k, v)| (k.as_str(), v)) - .collect() - } - - pub fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { - self.extensions - .insert(extension_name.to_owned(), extension_value.into()); - } - - pub fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option { - self.extensions.remove(extension_name) } } @@ -217,7 +198,9 @@ mod tests { e.remove_data(); - assert!(e.try_get_data::().unwrap().is_none()); + 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 3abca5c1..ac8f0015 100644 --- a/src/event/extensions.rs +++ b/src/event/extensions.rs @@ -1,8 +1,7 @@ -use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::convert::From; -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[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. @@ -11,6 +10,8 @@ pub enum ExtensionValue { Boolean(bool), /// Represents an integer [`i64`](i64) value. Integer(i64), + /// Represents a [Json `Value`](serde_json::value::Value). + Json(Value), } impl From for ExtensionValue { @@ -31,6 +32,12 @@ impl From for ExtensionValue { } } +impl From for ExtensionValue { + fn from(s: Value) -> Self { + ExtensionValue::Json(s) + } +} + impl ExtensionValue { pub fn from_string(s: S) -> Self where @@ -52,4 +59,11 @@ impl ExtensionValue { { ExtensionValue::from(s.into()) } + + pub fn from_json_value(s: S) -> Self + where + S: Into, + { + ExtensionValue::from(s.into()) + } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 41fb5b91..68b26100 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -16,16 +16,7 @@ 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; pub use v10::EventBuilder as EventBuilderV10; -pub(crate) use v10::EventDeserializer as EventDeserializerV10; -pub(crate) use v10::EventSerializer as EventSerializerV10; diff --git a/src/event/spec_version.rs b/src/event/spec_version.rs index 663f28a9..c66e48b5 100644 --- a/src/event/spec_version.rs +++ b/src/event/spec_version.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; @@ -5,7 +6,9 @@ pub(crate) const SPEC_VERSIONS: [&'static str; 2] = ["0.3", "1.0"]; #[derive(PartialEq, Debug, Clone)] pub enum SpecVersion { + #[serde(rename = "0.3")] V03, + #[serde(rename = "1.0")] V10, } diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 70b6a94f..2d303c96 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,5 +1,5 @@ -use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; -use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; +use crate::event::attributes::DataAttributesWriter; +use crate::event::{AttributesReader, AttributesWriter, ExtensionValue, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; use url::Url; @@ -99,6 +99,14 @@ impl AttributesReader for Attributes { fn get_time(&self) -> Option<&DateTime> { self.time.as_ref() } + + fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { + self.extensions.get(extension_name) + } + + fn iter_extensions(&self) -> std::collections::hash_map::Iter { + self.extensions.iter() + } } impl AttributesWriter for Attributes { @@ -121,6 +129,22 @@ impl AttributesWriter for Attributes { fn set_time(&mut self, time: Option>>) { self.time = time.map(Into::into) } + + fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ) { + self.extensions + .insert(extension_name.to_owned(), extension_value.into()); + } + + fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option { + self.extensions.remove(extension_name) + } } impl DataAttributesWriter for Attributes { @@ -150,24 +174,7 @@ impl Default for Attributes { dataschema: None, subject: None, time: None, - } - } -} - -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, + extensions: HashMap::new(), } } } diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 265f63b2..83067948 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -9,22 +9,16 @@ pub struct EventBuilder { } impl EventBuilder { - pub fn from(event: Event) -> Self { - EventBuilder { - event: Event { - attributes: event.attributes.into_v10(), - data: event.data, - extensions: event.extensions, - }, - } - } + // 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, - extensions: HashMap::new(), }, } } diff --git a/src/event/v10/mod.rs b/src/event/v10/mod.rs index 1cf2fb2a..e33a1f0a 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -3,7 +3,5 @@ mod builder; mod message; mod serde; -pub(crate) use crate::event::v10::serde::EventDeserializer; -pub(crate) use crate::event::v10::serde::EventSerializer; pub use attributes::Attributes; pub use builder::EventBuilder; diff --git a/tests/event.rs b/tests/event.rs new file mode 100644 index 00000000..7ad4c3a3 --- /dev/null +++ b/tests/event.rs @@ -0,0 +1,2 @@ +#[test] +fn use_event() {} diff --git a/tests/serde_json.rs b/tests/serde_json.rs deleted file mode 100644 index 14f01619..00000000 --- a/tests/serde_json.rs +++ /dev/null @@ -1,89 +0,0 @@ -use claim::*; -use cloudevents::Event; -use rstest::rstest; -use serde_json::Value; - -mod test_data; -use test_data::*; - -/// This test is a parametrized test that uses data from tests/test_data -#[rstest( - 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_should_succeed(in_event: Event, out_json: Value) { - // Event -> serde_json::Value - let serialize_result = serde_json::to_value(in_event.clone()); - assert_ok!(&serialize_result); - let actual_json = serialize_result.unwrap(); - assert_eq!(&actual_json, &out_json); - - // serde_json::Value -> String - let actual_json_serialized = actual_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, 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/version_conversion.rs b/tests/version_conversion.rs deleted file mode 100644 index 5ba05e99..00000000 --- a/tests/version_conversion.rs +++ /dev/null @@ -1,17 +0,0 @@ -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) -} From 2b8737b582bd7e748fe3b4595b6af02e52f41a15 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Fri, 20 Mar 2020 13:21:16 +0100 Subject: [PATCH 36/56] WIP iter attributes Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 15 --------------- src/event/mod.rs | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index a21c2164..53e72f8f 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -5,21 +5,6 @@ use url::Url; use serde::{Deserialize, Serialize}; use std::fmt; -impl ExactSizeIterator for Iter { - type Item = (&'a str, AttributeValue<'a>); - - fn next(&mut self) -> Option { - let new_next = self.curr + self.next; - - self.curr = self.next; - self.next = new_next; - - // Since there's no endpoint to a Fibonacci sequence, the `Iterator` - // will never return `None`, and `Some` is always returned. - Some(self.curr) - } -} - pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), diff --git a/src/event/mod.rs b/src/event/mod.rs index 68b26100..ab5093db 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -13,7 +13,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extensions::ExtensionValue; +pub use extension_value::ExtensionValue; pub use spec_version::SpecVersion; mod v10; From 56abbfda7c7d7983b604b3dac4bdd9eb3d8ee611 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 16:22:29 +0530 Subject: [PATCH 37/56] Completed Iterator for v10/attribute Signed-off-by: Pranav Bhatt --- src/event/mod.rs | 2 +- src/event/v10/attributes.rs | 57 +++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/event/mod.rs b/src/event/mod.rs index ab5093db..68b26100 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -13,7 +13,7 @@ pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; pub use event::Event; -pub use extension_value::ExtensionValue; +pub use extensions::ExtensionValue; pub use spec_version::SpecVersion; mod v10; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 2d303c96..1c0697a9 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, ExtensionValue, SpecVersion}; +use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; +use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; use url::Url; @@ -67,6 +67,57 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { } } +impl<'a> IntoIterator for &'a Attributes { + type Item = (&'a str, AttributeValue<'a>); + type IntoIter = AttributesIntoIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + AttributesIntoIterator { + attributes: self, + index: 0, + } + } +} + +struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, +} + +fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::String(x))), + None => None, + }; + result +} + +fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { + let result = match input { + Some(x) => Some((attribute_type,AttributeValue::Time(x))), + None => None, + }; + result +} + +impl<'a> Iterator for AttributesIntoIterator<'a> { + type Item = (&'a str, AttributeValue<'a>); + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(("id", AttributeValue::String(&self.attributes.id))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), + 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), + 5 => option_checker_string("subject",self.attributes.subject.get_subject()), + 6 => option_checker_time("time",self.attributes.time.get_time()), + _ => return None, + }; + self.index += 1; + result + } +} + impl AttributesReader for Attributes { fn get_id(&self) -> &str { &self.id @@ -177,4 +228,4 @@ impl Default for Attributes { extensions: HashMap::new(), } } -} +} \ No newline at end of file From 36a026381e8482b9b7cc0fb72b5dc299a1dd2590 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 17:00:29 +0530 Subject: [PATCH 38/56] Error fix 2 Signed-off-by: Pranav Bhatt --- src/event/v10/attributes.rs | 57 ++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 1c0697a9..d68b78b1 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -79,12 +79,12 @@ impl<'a> IntoIterator for &'a Attributes { } } -struct AttributesIntoIterator<'a> { +pub struct AttributesIntoIterator<'a> { attributes: &'a Attributes, index: usize, } -fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { +fn option_checker_string<'a>(attribute_type: &'a str,input:Option<&'a str>) -> Option<(&'a str,AttributeValue<'a>)> { let result = match input { Some(x) => Some((attribute_type,AttributeValue::String(x))), None => None, @@ -92,7 +92,7 @@ fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Opti result } -fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { +fn option_checker_time<'a>(attribute_type: &'a str,input:Option<&'a DateTime>) -> Option<(&'a str,AttributeValue<'a>)> { let result = match input { Some(x) => Some((attribute_type,AttributeValue::Time(x))), None => None, @@ -108,9 +108,9 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), 2 => Some(("source", AttributeValue::String(&self.attributes.source))), 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), - 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), - 5 => option_checker_string("subject",self.attributes.subject.get_subject()), - 6 => option_checker_time("time",self.attributes.time.get_time()), + 4 => option_checker_string("dataschema",self.attributes.get_dataschema()), + 5 => option_checker_string("subject",self.attributes.get_subject()), + 6 => option_checker_time("time",self.attributes.get_time()), _ => return None, }; self.index += 1; @@ -150,14 +150,6 @@ impl AttributesReader for Attributes { fn get_time(&self) -> Option<&DateTime> { self.time.as_ref() } - - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - self.extensions.get(extension_name) - } - - fn iter_extensions(&self) -> std::collections::hash_map::Iter { - self.extensions.iter() - } } impl AttributesWriter for Attributes { @@ -180,22 +172,6 @@ impl AttributesWriter for Attributes { fn set_time(&mut self, time: Option>>) { self.time = time.map(Into::into) } - - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { - self.extensions - .insert(extension_name.to_owned(), extension_value.into()); - } - - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option { - self.extensions.remove(extension_name) - } } impl DataAttributesWriter for Attributes { @@ -225,7 +201,24 @@ impl Default for Attributes { dataschema: None, subject: None, time: None, - extensions: HashMap::new(), } } -} \ No newline at end of file +} + +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, + } + } +} From 11c9748d695cba9670c8665a1fe940aa95ab3475 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 17:10:08 +0530 Subject: [PATCH 39/56] Error fix 2 Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 21 +++++---------------- src/event/event.rs | 2 -- src/event/v10/builder.rs | 4 ---- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 53e72f8f..8855a8d3 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -25,6 +25,11 @@ impl fmt::Display for AttributeValue<'_> { } } +pub(crate) trait AttributesConverter { + fn into_v03(self) -> AttributesV03; + fn into_v10(self) -> AttributesV10; +} + /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { /// Get the [id](https://github.com/cloudevents/spec/blob/master/spec.md#id). @@ -43,10 +48,6 @@ pub trait AttributesReader { 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>; - /// 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) - fn iter_extensions(&self) -> std::collections::hash_map::Iter; } pub trait AttributesWriter { @@ -124,18 +125,6 @@ impl AttributesReader for Attributes { Attributes::V10(a) => a.get_time(), } } - - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { - match self { - Attributes::V10(a) => a.get_extension(extension_name), - } - } - - fn iter_extensions(&self) -> std::collections::hash_map::Iter { - match self { - Attributes::V10(a) => a.iter_extensions(), - } - } } impl AttributesWriter for Attributes { diff --git a/src/event/event.rs b/src/event/event.rs index 5a57afa4..0919ef71 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -49,8 +49,6 @@ impl AttributesReader for Event { fn get_dataschema(&self) -> Option<&Url>; fn get_subject(&self) -> Option<&str>; fn get_time(&self) -> Option<&DateTime>; - fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>; - fn iter_extensions(&self) -> std::collections::hash_map::Iter; } } } diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 83067948..cbd14959 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -114,10 +114,6 @@ mod tests { 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().clone()); From 142c811d641ea78ee0e46e4b0327588ae1c3018a Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 17:34:30 +0530 Subject: [PATCH 40/56] Completed iterator Signed-off-by: Pranav Bhatt --- CONTRIBUTING.md | 129 ++++++++++++++++++++++++++++++++++++ Cargo.lock | 59 +++++++++++++++++ Cargo.toml | 5 ++ README.md | 35 +++++++++- src/event/attributes.rs | 63 ++++++++++-------- src/event/builder.rs | 12 +++- src/event/data.rs | 29 ++++++-- src/event/event.rs | 55 ++++++++++----- src/event/extensions.rs | 20 +----- src/event/mod.rs | 9 +++ src/event/spec_version.rs | 3 - src/event/v10/builder.rs | 18 +++-- src/event/v10/mod.rs | 2 + tests/event.rs | 2 - tests/serde_json.rs | 89 +++++++++++++++++++++++++ tests/version_conversion.rs | 17 +++++ 16 files changed, 467 insertions(+), 80 deletions(-) create mode 100644 CONTRIBUTING.md delete mode 100644 tests/event.rs create mode 100644 tests/serde_json.rs create mode 100644 tests/version_conversion.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a7614ad1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,129 @@ +# Contributing to CloudEvents SDK Rust + +This page contains information about reporting issues, how to suggest changes as +well as the guidelines we follow for how our documents are formatted. + +## Table of Contents + +- [Reporting an Issue](#reporting-an-issue) +- [Preparing the environment](#preparing-the-environment) +- [Suggesting a Change](#suggesting-a-change) + +## Reporting an Issue + +To report an issue, or to suggest an idea for a change that you haven't had time +to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It +is best to check our existing +[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar +one has already been opened and discussed. + +## Preparing the environment + +In order to start developing this project, +you need to install the Rust tooling using [rustup](https://rustup.rs/). + +### Development commands + +To build the project: + +```sh +cargo build --all-features +``` + +To run all tests: + +```sh +cargo test --all-features +``` + +To build and open the documentation: + +```sh +cargo doc --lib --open +``` + +To run the code formatter: + +```sh +cargo fmt +``` + +## Suggesting a change + +To suggest a change to this repository, submit a +[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete +set of changes you'd like to see. See the +[Spec Formatting Conventions](#spec-formatting-conventions) section for the +guidelines we follow for how documents are formatted. + +Each PR must be signed per the following section. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. + +Note: If your git config information is set properly then viewing the `git log` +information for your commit will look something like this: + +``` +Author: Joe Smith +Date: Thu Feb 2 11:41:15 2018 -0800 + + Update README + + Signed-off-by: Joe Smith +``` + +Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will +be rejected by the automated DCO check. diff --git a/Cargo.lock b/Cargo.lock index 6a1b3439..700e3135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,15 +30,27 @@ dependencies = [ "time", ] +[[package]] +name = "claim" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2e893ee68bf12771457cceea72497bc9cb7da404ec8a5311226d354b895ba4" +dependencies = [ + "autocfg", +] + [[package]] name = "cloudevents-sdk" version = "0.0.1" dependencies = [ "base64", "chrono", + "claim", "delegate", "hostname", + "rstest", "serde", + "serde-value", "serde_json", "snafu", "url", @@ -217,12 +229,49 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "rstest" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d5f9396fa6a44e2aa2068340b17208794515e2501c5bf3e680a0c3422a5971" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.104" @@ -232,6 +281,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.104" diff --git a/Cargo.toml b/Cargo.toml index 9a478219..4d19f895 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/cloudevents/sdk-rust" [dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" +serde-value = "^0.6" chrono = { version = "^0.4", features = ["serde"] } delegate = "^0.4" uuid = { version = "^0.8", features = ["serde", "v4"] } @@ -21,5 +22,9 @@ base64 = "^0.12" url = { version = "^2.1", features = ["serde"] } snafu = "^0.6" +[dev-dependencies] +rstest = "0.6" +claim = "0.3.1" + [lib] name = "cloudevents" diff --git a/README.md b/README.md index 23849afd..996af602 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,36 @@ -# CloudEvents Rust SDK +# CloudEvents SDK Rust Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) + +## Spec support + +| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | +| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | +| CloudEvents Core | :x: | :heavy_check_mark: | +| AMQP Protocol Binding | :x: | :x: | +| AVRO Event Format | :x: | :x: | +| HTTP Protocol Binding | :x: | :x: | +| JSON Event Format | :x: | :heavy_check_mark: | +| Kafka Protocol Binding | :x: | :x: | +| MQTT Protocol Binding | :x: | :x: | +| NATS Protocol Binding | :x: | :x: | +| Web hook | :x: | :x: | + +## Development & Contributing + +If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) + +## Community + +- There are bi-weekly calls immediately following the + [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) + at 9am PT (US Pacific). Which means they will typically start at 10am PT, but + if the other call ends early then the SDK call will start early as well. See + the + [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) + to determine which week will have the call. +- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under + [CNCF's Slack workspace](https://slack.cncf.io/). +- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk +- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` + on slack). diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 8855a8d3..1af5b13d 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,5 +1,4 @@ -use super::SpecVersion; -use crate::event::{AttributesV10, ExtensionValue}; +use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; use url::Url; use serde::{Deserialize, Serialize}; @@ -25,11 +24,6 @@ impl fmt::Display for AttributeValue<'_> { } } -pub(crate) trait AttributesConverter { - fn into_v03(self) -> AttributesV03; - fn into_v10(self) -> AttributesV10; -} - /// Trait to get [CloudEvents Context attributes](https://github.com/cloudevents/spec/blob/master/spec.md#context-attributes). pub trait AttributesReader { /// Get the [id](https://github.com/cloudevents/spec/blob/master/spec.md#id). @@ -56,15 +50,11 @@ pub trait AttributesWriter { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ); - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option; +} + +pub(crate) trait AttributesConverter { + fn into_v03(self) -> AttributesV03; + fn into_v10(self) -> AttributesV10; } pub(crate) trait DataAttributesWriter { @@ -74,54 +64,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) -> &Url { 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<&Url> { 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(), } } @@ -130,61 +129,67 @@ 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), } } +} - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ) { +impl DataAttributesWriter for Attributes { + fn set_datacontenttype(&mut self, datacontenttype: Option>) { match self { - Attributes::V10(a) => a.set_extension(extension_name, extension_value), + Attributes::V03(a) => a.set_datacontenttype(datacontenttype), + Attributes::V10(a) => a.set_datacontenttype(datacontenttype), } } fn set_dataschema(&mut self, dataschema: Option>) { match self { - Attributes::V10(a) => a.remove_extension(extension_name), + Attributes::V03(a) => a.set_dataschema(dataschema), + Attributes::V10(a) => a.set_dataschema(dataschema), } } } -impl DataAttributesWriter for Attributes { - fn set_datacontenttype(&mut self, datacontenttype: Option>) { +impl Attributes { + pub fn into_v10(self) -> Self { match self { - Attributes::V10(a) => a.set_datacontenttype(datacontenttype), + Attributes::V03(v03) => Attributes::V10(v03.into_v10()), + _ => self, } } - - fn set_dataschema(&mut self, dataschema: Option>) { + pub fn into_v03(self) -> Self { match self { - Attributes::V10(a) => a.set_dataschema(dataschema), + Attributes::V10(v10) => Attributes::V03(v10.into_v03()), + _ => self, } } } diff --git a/src/event/builder.rs b/src/event/builder.rs index 8c903184..ead19817 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`]: /// ``` @@ -15,8 +15,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 db7caa53..25283c92 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)] -/// Possible data values pub enum Data { - String(String), + /// 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), } @@ -46,6 +49,10 @@ pub(crate) fn is_json_content_type(ct: &str) -> bool { ct == "application/json" || ct == "text/json" || ct.ends_with("+json") } +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) @@ -69,9 +76,21 @@ impl TryFrom for serde_json::Value { fn try_from(value: Data) -> Result { match value { - Data::String(s) => Ok(serde_json::from_str(&s)?), Data::Binary(v) => Ok(serde_json::from_slice(&v)?), Data::Json(v) => Ok(v), + Data::String(s) => Ok(serde_json::from_str(&s)?), + } + } +} + +impl TryFrom for Vec { + type Error = serde_json::Error; + + fn try_from(value: Data) -> Result { + 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()), } } } @@ -81,9 +100,9 @@ impl TryFrom for String { fn try_from(value: Data) -> Result { match value { - Data::String(s) => Ok(s), Data::Binary(v) => Ok(String::from_utf8(v)?), - Data::Json(s) => Ok(s.to_string()), + Data::Json(v) => Ok(v.to_string()), + Data::String(s) => Ok(s), } } } diff --git a/src/event/event.rs b/src/event/event.rs index 0919ef71..188be16b 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -5,6 +5,7 @@ use super::{ use crate::event::attributes::DataAttributesWriter; use chrono::{DateTime, Utc}; use delegate::delegate; +use std::collections::HashMap; use std::convert::TryFrom; use url::Url; @@ -36,6 +37,7 @@ use url::Url; pub struct Event { pub attributes: Attributes, pub data: Option, + pub extensions: HashMap, } impl AttributesReader for Event { @@ -61,15 +63,6 @@ impl AttributesWriter for Event { fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); - fn set_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - extension_value: impl Into, - ); - fn remove_extension<'name, 'event: 'name>( - &'event mut self, - extension_name: &'name str, - ) -> Option; } } } @@ -79,6 +72,7 @@ impl Default for Event { Event { attributes: Attributes::V10(AttributesV10::default()), data: None, + extensions: HashMap::default(), } } } @@ -139,22 +133,49 @@ impl Event { } } - pub fn try_get_data, E: std::error::Error>( - &self, - ) -> Option> { + pub fn try_get_data>(&self) -> Result, T::Error> { match self.data.as_ref() { Some(d) => Some(T::try_from(d.clone())), None => None, } + .transpose() } - pub fn into_data, E: std::error::Error>( - self, - ) -> Option> { + pub fn into_data>(self) -> Result, T::Error> { match self.data { Some(d) => Some(T::try_from(d)), None => None, } + .transpose() + } + + /// Get the [extension](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) named `extension_name` + pub fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> { + self.extensions.get(extension_name) + } + + /// Get all the [extensions](https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes) + pub fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> { + self.extensions + .iter() + .map(|(k, v)| (k.as_str(), v)) + .collect() + } + + pub fn set_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + extension_value: impl Into, + ) { + self.extensions + .insert(extension_name.to_owned(), extension_value.into()); + } + + pub fn remove_extension<'name, 'event: 'name>( + &'event mut self, + extension_name: &'name str, + ) -> Option { + self.extensions.remove(extension_name) } } @@ -196,9 +217,7 @@ mod tests { e.remove_data(); - assert!(e - .try_get_data::() - .is_none()); + assert!(e.try_get_data::().unwrap().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 ac8f0015..3abca5c1 100644 --- a/src/event/extensions.rs +++ b/src/event/extensions.rs @@ -1,7 +1,8 @@ -use serde_json::Value; +use serde::{Deserialize, Serialize}; use std::convert::From; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged)] /// 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. @@ -10,8 +11,6 @@ pub enum ExtensionValue { Boolean(bool), /// Represents an integer [`i64`](i64) value. Integer(i64), - /// Represents a [Json `Value`](serde_json::value::Value). - Json(Value), } impl From for ExtensionValue { @@ -32,12 +31,6 @@ impl From for ExtensionValue { } } -impl From for ExtensionValue { - fn from(s: Value) -> Self { - ExtensionValue::Json(s) - } -} - impl ExtensionValue { pub fn from_string(s: S) -> Self where @@ -59,11 +52,4 @@ impl ExtensionValue { { ExtensionValue::from(s.into()) } - - pub fn from_json_value(s: S) -> Self - where - S: Into, - { - ExtensionValue::from(s.into()) - } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 68b26100..41fb5b91 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -16,7 +16,16 @@ 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; pub use v10::EventBuilder as EventBuilderV10; +pub(crate) use v10::EventDeserializer as EventDeserializerV10; +pub(crate) use v10::EventSerializer as EventSerializerV10; diff --git a/src/event/spec_version.rs b/src/event/spec_version.rs index c66e48b5..663f28a9 100644 --- a/src/event/spec_version.rs +++ b/src/event/spec_version.rs @@ -1,4 +1,3 @@ -use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; @@ -6,9 +5,7 @@ pub(crate) const SPEC_VERSIONS: [&'static str; 2] = ["0.3", "1.0"]; #[derive(PartialEq, Debug, Clone)] pub enum SpecVersion { - #[serde(rename = "0.3")] V03, - #[serde(rename = "1.0")] V10, } diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index cbd14959..265f63b2 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -9,16 +9,22 @@ 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 { event: Event { attributes: Attributes::V10(AttributesV10::default()), data: None, + extensions: HashMap::new(), }, } } @@ -114,6 +120,10 @@ mod tests { 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().clone()); diff --git a/src/event/v10/mod.rs b/src/event/v10/mod.rs index e33a1f0a..1cf2fb2a 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -3,5 +3,7 @@ mod builder; mod message; mod serde; +pub(crate) use crate::event::v10::serde::EventDeserializer; +pub(crate) use crate::event::v10::serde::EventSerializer; pub use attributes::Attributes; pub use builder::EventBuilder; 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 new file mode 100644 index 00000000..14f01619 --- /dev/null +++ b/tests/serde_json.rs @@ -0,0 +1,89 @@ +use claim::*; +use cloudevents::Event; +use rstest::rstest; +use serde_json::Value; + +mod test_data; +use test_data::*; + +/// This test is a parametrized test that uses data from tests/test_data +#[rstest( + 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_should_succeed(in_event: Event, out_json: Value) { + // Event -> serde_json::Value + let serialize_result = serde_json::to_value(in_event.clone()); + assert_ok!(&serialize_result); + let actual_json = serialize_result.unwrap(); + assert_eq!(&actual_json, &out_json); + + // serde_json::Value -> String + let actual_json_serialized = actual_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, 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/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) +} From 3a6ab08d218bd30a5ad646c103cf984c4b9d4220 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 16 Apr 2020 18:23:09 +0530 Subject: [PATCH 41/56] Update attributes.rs Signed-off-by: Pranav Bhatt --- src/event/v10/attributes.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index d68b78b1..c1fde9a2 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -63,6 +63,9 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { _ => return None, }; self.index += 1; + if result.is_none() { + return self.next() + } result } } From cd3a545e30f1beb49398b3037e1f9e9b9f5be827 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Fri, 17 Apr 2020 22:13:14 +0530 Subject: [PATCH 42/56] Optimised Code #1 Resolved: 1. https://github.com/cloudevents/sdk-rust/pull/26/files#r409586754 2. https://github.com/cloudevents/sdk-rust/pull/26/files#r409585595 Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 2 +- src/event/v10/attributes.rs | 24 ++++-------------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 1af5b13d..28b09eb5 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -19,7 +19,7 @@ impl fmt::Display for AttributeValue<'_> { AttributeValue::String(s) => f.write_str(s), AttributeValue::URI(s) => f.write_str(s), AttributeValue::URIRef(s) => f.write_str(s), - AttributeValue::Time(s) => f.write_str(&s.to_rfc2822()), + AttributeValue::Time(s) => f.write_str(&s.to_rfc3339()), } } } diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index c1fde9a2..82b0163f 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -87,22 +87,6 @@ pub struct AttributesIntoIterator<'a> { index: usize, } -fn option_checker_string<'a>(attribute_type: &'a str,input:Option<&'a str>) -> Option<(&'a str,AttributeValue<'a>)> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::String(x))), - None => None, - }; - result -} - -fn option_checker_time<'a>(attribute_type: &'a str,input:Option<&'a DateTime>) -> Option<(&'a str,AttributeValue<'a>)> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::Time(x))), - None => None, - }; - result -} - impl<'a> Iterator for AttributesIntoIterator<'a> { type Item = (&'a str, AttributeValue<'a>); fn next(&mut self) -> Option { @@ -110,10 +94,10 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), 2 => Some(("source", AttributeValue::String(&self.attributes.source))), - 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), - 4 => option_checker_string("dataschema",self.attributes.get_dataschema()), - 5 => option_checker_string("subject",self.attributes.get_subject()), - 6 => option_checker_time("time",self.attributes.get_time()), + 3 => self.attributes.datacontenttype.as_ref().map(|v| ("datacontenttype", AttributeValue::String(v))), + 4 => self.attributes.dataschema.as_ref().map(|v| ("dataschema", AttributeValue::String(v))), + 5 => self.attributes.subject.as_ref().map(|v| ("subject", AttributeValue::String(v))), + 6 => self.attributes.time.as_ref().map(|v| ("time", AttributeValue::Time(v))), _ => return None, }; self.index += 1; From fcda5ed85e4be286fcd5f9efc5821ffc2662a674 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 19:48:18 +0530 Subject: [PATCH 43/56] Added Test in v10/attributes Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 1 + src/event/mod.rs | 1 + src/event/v10/attributes.rs | 22 +++++++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 28b09eb5..983e3e58 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -4,6 +4,7 @@ use url::Url; use serde::{Deserialize, Serialize}; use std::fmt; +#[derive(Debug, PartialEq)] pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), diff --git a/src/event/mod.rs b/src/event/mod.rs index 41fb5b91..53b0d147 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -9,6 +9,7 @@ mod deserializer; mod spec_version; pub use attributes::Attributes; +pub use attributes::AttributeValue as AttributeValue; pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 82b0163f..56ab4c0c 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,6 +1,6 @@ use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc, NaiveDateTime}; use hostname::get_hostname; use url::Url; use uuid::Uuid; @@ -209,3 +209,23 @@ impl AttributesConverter for Attributes { } } } + +#[test] +fn iterator_test(){ + let a = Attributes{ + id: String::from("1"), + ty: String::from("someType"), + source: String::from("Test"), + datacontenttype: None, + dataschema: None, + subject: None, + time: Some(DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc)), + }; + let b = &mut a.into_iter(); + let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + + assert_eq!(("id",AttributeValue::String("1")),b.next().unwrap()); + assert_eq!(("ty",AttributeValue::String("someType")),b.next().unwrap()); + assert_eq!(("source",AttributeValue::String("Test")),b.next().unwrap()); + assert_eq!(("time",AttributeValue::Time(&time)),b.next().unwrap()); +} From ddfb1030dde7f8fd131b55f2d98c873206f966e8 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 20:08:38 +0530 Subject: [PATCH 44/56] Ran cargo fmt Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 2 +- src/event/mod.rs | 2 +- src/event/v10/attributes.rs | 56 +++++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 983e3e58..e2c0ea2d 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -10,7 +10,7 @@ pub enum AttributeValue<'a> { String(&'a str), URI(&'a str), URIRef(&'a str), - Time(&'a DateTime) + Time(&'a DateTime), } impl fmt::Display for AttributeValue<'_> { diff --git a/src/event/mod.rs b/src/event/mod.rs index 53b0d147..140d120d 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -8,8 +8,8 @@ mod serde; mod deserializer; mod spec_version; +pub use attributes::AttributeValue; pub use attributes::Attributes; -pub use attributes::AttributeValue as AttributeValue; pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; pub use data::Data; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 56ab4c0c..24bdae62 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -1,6 +1,6 @@ -use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; +use crate::event::attributes::{AttributeValue, AttributesConverter, DataAttributesWriter}; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; -use chrono::{DateTime, Utc, NaiveDateTime}; +use chrono::{DateTime, NaiveDateTime, Utc}; use hostname::get_hostname; use url::Url; use uuid::Uuid; @@ -92,15 +92,34 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { fn next(&mut self) -> Option { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), - 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), 2 => Some(("source", AttributeValue::String(&self.attributes.source))), - 3 => self.attributes.datacontenttype.as_ref().map(|v| ("datacontenttype", AttributeValue::String(v))), - 4 => self.attributes.dataschema.as_ref().map(|v| ("dataschema", AttributeValue::String(v))), - 5 => self.attributes.subject.as_ref().map(|v| ("subject", AttributeValue::String(v))), - 6 => self.attributes.time.as_ref().map(|v| ("time", AttributeValue::Time(v))), + 3 => self + .attributes + .datacontenttype + .as_ref() + .map(|v| ("datacontenttype", AttributeValue::String(v))), + 4 => self + .attributes + .dataschema + .as_ref() + .map(|v| ("dataschema", AttributeValue::String(v))), + 5 => self + .attributes + .subject + .as_ref() + .map(|v| ("subject", AttributeValue::String(v))), + 6 => self + .attributes + .time + .as_ref() + .map(|v| ("time", AttributeValue::Time(v))), _ => return None, }; self.index += 1; + if result.is_none() { + return self.next(); + } result } } @@ -211,21 +230,30 @@ impl AttributesConverter for Attributes { } #[test] -fn iterator_test(){ - let a = Attributes{ +fn iterator_test() { + let a = Attributes { id: String::from("1"), ty: String::from("someType"), source: String::from("Test"), datacontenttype: None, dataschema: None, subject: None, - time: Some(DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc)), + time: Some(DateTime::::from_utc( + NaiveDateTime::from_timestamp(61, 0), + Utc, + )), }; let b = &mut a.into_iter(); let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); - assert_eq!(("id",AttributeValue::String("1")),b.next().unwrap()); - assert_eq!(("ty",AttributeValue::String("someType")),b.next().unwrap()); - assert_eq!(("source",AttributeValue::String("Test")),b.next().unwrap()); - assert_eq!(("time",AttributeValue::Time(&time)),b.next().unwrap()); + assert_eq!(("id", AttributeValue::String("1")), b.next().unwrap()); + assert_eq!( + ("ty", AttributeValue::String("someType")), + b.next().unwrap() + ); + assert_eq!( + ("source", AttributeValue::String("Test")), + b.next().unwrap() + ); + assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); } From d1bda4a50eb2e5c65ccba1c7b45840fc4960d898 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 21:52:50 +0530 Subject: [PATCH 45/56] Added iterator to AttributeV03 Signed-off-by: Pranav Bhatt --- src/event/mod.rs | 1 - src/event/v03/attributes.rs | 88 ++++++++++++++++++++++++++++++++++++- src/event/v10/attributes.rs | 2 +- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/event/mod.rs b/src/event/mod.rs index 140d120d..41fb5b91 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -8,7 +8,6 @@ mod serde; mod deserializer; mod spec_version; -pub use attributes::AttributeValue; pub use attributes::Attributes; pub use attributes::{AttributesReader, AttributesWriter}; pub use builder::EventBuilder; diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index 09a355e6..47fb1907 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -1,7 +1,7 @@ -use crate::event::attributes::{AttributesConverter, DataAttributesWriter}; +use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; use crate::event::AttributesV10; use crate::event::{AttributesReader, AttributesWriter, SpecVersion}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc, NaiveDateTime}; use hostname::get_hostname; use url::Url; use uuid::Uuid; @@ -17,6 +17,60 @@ pub struct Attributes { pub(crate) time: Option>, } +impl<'a> IntoIterator for &'a Attributes { + type Item = (&'a str, AttributeValue<'a>); + type IntoIter = AttributesIntoIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + AttributesIntoIterator { + attributes: self, + index: 0, + } + } +} + +pub struct AttributesIntoIterator<'a> { + attributes: &'a Attributes, + index: usize, +} + +impl<'a> Iterator for AttributesIntoIterator<'a> { + type Item = (&'a str, AttributeValue<'a>); + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(("id", AttributeValue::String(&self.attributes.id))), + 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 3 => self + .attributes + .datacontenttype + .as_ref() + .map(|v| ("datacontenttype", AttributeValue::String(v))), + 4 => self + .attributes + .schemaurl + .as_ref() + .map(|v| ("dataschema", AttributeValue::String(v))), + 5 => self + .attributes + .subject + .as_ref() + .map(|v| ("subject", AttributeValue::String(v))), + 6 => self + .attributes + .time + .as_ref() + .map(|v| ("time", AttributeValue::Time(v))), + _ => return None, + }; + self.index += 1; + if result.is_none() { + return self.next(); + } + result + } +} + impl AttributesReader for Attributes { fn get_id(&self) -> &str { &self.id @@ -121,3 +175,33 @@ impl AttributesConverter for Attributes { } } } + +#[test] +fn iterator_test_V03() { + let a = Attributes { + id: String::from("1"), + ty: String::from("someType"), + source: String::from("Test"), + datacontenttype: None, + schemaurl: None, + subject: None, + time: Some(DateTime::::from_utc( + NaiveDateTime::from_timestamp(61, 0), + Utc, + )), + }; + let b = &mut a.into_iter(); + let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + + assert_eq!(("id", AttributeValue::String("1")), b.next().unwrap()); + assert_eq!( + ("ty", AttributeValue::String("someType")), + b.next().unwrap() + ); + assert_eq!( + ("source", AttributeValue::String("Test")), + b.next().unwrap() + ); + assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); +} + diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 24bdae62..d470b6c3 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -230,7 +230,7 @@ impl AttributesConverter for Attributes { } #[test] -fn iterator_test() { +fn iterator_test_V10() { let a = Attributes { id: String::from("1"), ty: String::from("someType"), From ff2b6e4045bf75bd3cd0e339a04d60892a081413 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 22:08:28 +0530 Subject: [PATCH 46/56] updated .gitignore for /target Signed-off-by: Pranav Bhatt --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 185ca35e..02a1b1cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target +.target .idea From b8434a633922f92245bf54b54272de9252332a17 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 22:18:34 +0530 Subject: [PATCH 47/56] updated .gitignore Signed-off-by: Pranav Bhatt --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 02a1b1cb..e61a28bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/target +/target/ -.target .idea From 8c5761b58d2a3f02327640de7768f119d474789b Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Wed, 29 Apr 2020 22:54:31 +0530 Subject: [PATCH 48/56] Deleting incorrect EOL files Signed-off-by: Pranav Bhatt --- CONTRIBUTING.md | 129 ------------------------------------------------ README.md | 36 -------------- 2 files changed, 165 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a7614ad1..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,129 +0,0 @@ -# Contributing to CloudEvents SDK Rust - -This page contains information about reporting issues, how to suggest changes as -well as the guidelines we follow for how our documents are formatted. - -## Table of Contents - -- [Reporting an Issue](#reporting-an-issue) -- [Preparing the environment](#preparing-the-environment) -- [Suggesting a Change](#suggesting-a-change) - -## Reporting an Issue - -To report an issue, or to suggest an idea for a change that you haven't had time -to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It -is best to check our existing -[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar -one has already been opened and discussed. - -## Preparing the environment - -In order to start developing this project, -you need to install the Rust tooling using [rustup](https://rustup.rs/). - -### Development commands - -To build the project: - -```sh -cargo build --all-features -``` - -To run all tests: - -```sh -cargo test --all-features -``` - -To build and open the documentation: - -```sh -cargo doc --lib --open -``` - -To run the code formatter: - -```sh -cargo fmt -``` - -## Suggesting a change - -To suggest a change to this repository, submit a -[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete -set of changes you'd like to see. See the -[Spec Formatting Conventions](#spec-formatting-conventions) section for the -guidelines we follow for how documents are formatted. - -Each PR must be signed per the following section. - -### Sign your work - -The sign-off is a simple line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. The rules are pretty simple: if you can certify -the below (from [developercertificate.org](http://developercertificate.org/)): - -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -Then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -Use your real name (sorry, no pseudonyms or anonymous contributions.) - -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. - -Note: If your git config information is set properly then viewing the `git log` -information for your commit will look something like this: - -``` -Author: Joe Smith -Date: Thu Feb 2 11:41:15 2018 -0800 - - Update README - - Signed-off-by: Joe Smith -``` - -Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will -be rejected by the automated DCO check. diff --git a/README.md b/README.md deleted file mode 100644 index 996af602..00000000 --- a/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# CloudEvents SDK Rust - -Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) - -## Spec support - -| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | -| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | -| CloudEvents Core | :x: | :heavy_check_mark: | -| AMQP Protocol Binding | :x: | :x: | -| AVRO Event Format | :x: | :x: | -| HTTP Protocol Binding | :x: | :x: | -| JSON Event Format | :x: | :heavy_check_mark: | -| Kafka Protocol Binding | :x: | :x: | -| MQTT Protocol Binding | :x: | :x: | -| NATS Protocol Binding | :x: | :x: | -| Web hook | :x: | :x: | - -## Development & Contributing - -If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) - -## Community - -- There are bi-weekly calls immediately following the - [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) - at 9am PT (US Pacific). Which means they will typically start at 10am PT, but - if the other call ends early then the SDK call will start early as well. See - the - [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) - to determine which week will have the call. -- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under - [CNCF's Slack workspace](https://slack.cncf.io/). -- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk -- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` - on slack). From 12e4ae4c09e6dba12862f444c6dec05e02b0ce1e Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 16:57:02 +0530 Subject: [PATCH 49/56] Rebased and updated types in iterator Signed-off-by: Pranav Bhatt --- .gitignore | 2 +- src/event/attributes.rs | 2 ++ src/event/data.rs | 4 --- src/event/serde.rs | 65 ------------------------------------- src/event/v03/attributes.rs | 8 ++--- src/event/v10/attributes.rs | 62 +++-------------------------------- src/event/v10/serde.rs | 54 +----------------------------- 7 files changed, 12 insertions(+), 185 deletions(-) diff --git a/.gitignore b/.gitignore index e61a28bf..185ca35e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/target/ +/target .idea diff --git a/src/event/attributes.rs b/src/event/attributes.rs index e2c0ea2d..b9b6a4f3 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -11,6 +11,7 @@ pub enum AttributeValue<'a> { URI(&'a str), URIRef(&'a str), Time(&'a DateTime), + URL(&'a Url), } impl fmt::Display for AttributeValue<'_> { @@ -21,6 +22,7 @@ impl fmt::Display for AttributeValue<'_> { AttributeValue::URI(s) => f.write_str(s), AttributeValue::URIRef(s) => f.write_str(s), AttributeValue::Time(s) => f.write_str(&s.to_rfc3339()), + AttributeValue::URL(s) => f.write_str(&s.as_str()), } } } diff --git a/src/event/data.rs b/src/event/data.rs index 25283c92..4acf4a87 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -49,10 +49,6 @@ pub(crate) fn is_json_content_type(ct: &str) -> bool { ct == "application/json" || ct == "text/json" || ct.ends_with("+json") } -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) diff --git a/src/event/serde.rs b/src/event/serde.rs index 1f8352a6..89d29a48 100644 --- a/src/event/serde.rs +++ b/src/event/serde.rs @@ -91,46 +91,6 @@ macro_rules! parse_data_base64 { }; } -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( map: &mut BTreeMap, @@ -174,15 +134,6 @@ pub(crate) trait EventSerializer { ) -> Result<::Ok, ::Error>; } -pub(crate) trait EventSerializer { - fn serialize( - attributes: &A, - data: &Option, - extensions: &HashMap, - serializer: S, - ) -> Result<::Ok, ::Error>; -} - impl<'de> Deserialize<'de> for Event { fn deserialize(deserializer: D) -> Result>::Error> where @@ -228,22 +179,6 @@ impl Serialize for Event { } } -impl Serialize for Event { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - 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) - } - } - } -} - // This should be provided by the Value package itself pub(crate) fn value_to_unexpected(v: &Value) -> Unexpected { match v { diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index 47fb1907..e45e8cb4 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -40,7 +40,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 2 => Some(("source", AttributeValue::URL(&self.attributes.source))), 3 => self .attributes .datacontenttype @@ -50,7 +50,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { .attributes .schemaurl .as_ref() - .map(|v| ("dataschema", AttributeValue::String(v))), + .map(|v| ("dataschema", AttributeValue::URL(v))), 5 => self .attributes .subject @@ -181,7 +181,7 @@ fn iterator_test_V03() { let a = Attributes { id: String::from("1"), ty: String::from("someType"), - source: String::from("Test"), + source: Url::parse("https://example.net").unwrap(), datacontenttype: None, schemaurl: None, subject: None, @@ -199,7 +199,7 @@ fn iterator_test_V03() { b.next().unwrap() ); assert_eq!( - ("source", AttributeValue::String("Test")), + ("source", AttributeValue::URL(&Url::parse("https://example.net").unwrap())), b.next().unwrap() ); assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index d470b6c3..446e5f7c 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -28,60 +28,6 @@ impl<'a> IntoIterator for &'a Attributes { } } -struct AttributesIntoIterator<'a> { - attributes: &'a Attributes, - index: usize, -} - -fn option_checker_string<'a>(attribute_type: &str,input:Option<&String>) -> Option<&'a str,AttributeValue<'a>> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::String(x))), - None => None, - }; - result -} - -fn option_checker_time<'a>(attribute_type: &str,input:Option<&DateTime>) -> Option<&'a str,AttributeValue<'a>> { - let result = match input { - Some(x) => Some((attribute_type,AttributeValue::Time(x))), - None => None, - }; - result -} - -impl<'a> Iterator for AttributesIntoIterator<'a> { - type Item = (&'a str, AttributeValue<'a>); - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(("id", AttributeValue::String(&self.attributes.id))), - 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::String(&self.attributes.source))), - 3 => option_checker_string("datacontenttype",self.attributes.get_datacontenttype()), - 4 => option_checker_string("dataschema",self.attributes.dataschema.get_dataschema()), - 5 => option_checker_string("subject",self.attributes.subject.get_subject()), - 6 => option_checker_time("time",self.attributes.time.get_time()), - _ => return None, - }; - self.index += 1; - if result.is_none() { - return self.next() - } - result - } -} - -impl<'a> IntoIterator for &'a Attributes { - type Item = (&'a str, AttributeValue<'a>); - type IntoIter = AttributesIntoIterator<'a>; - - fn into_iter(self) -> Self::IntoIter { - AttributesIntoIterator { - attributes: self, - index: 0, - } - } -} - pub struct AttributesIntoIterator<'a> { attributes: &'a Attributes, index: usize, @@ -93,7 +39,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::String(&self.attributes.source))), + 2 => Some(("source", AttributeValue::URL(&self.attributes.source))), 3 => self .attributes .datacontenttype @@ -103,7 +49,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { .attributes .dataschema .as_ref() - .map(|v| ("dataschema", AttributeValue::String(v))), + .map(|v| ("dataschema", AttributeValue::URL(v))), 5 => self .attributes .subject @@ -234,7 +180,7 @@ fn iterator_test_V10() { let a = Attributes { id: String::from("1"), ty: String::from("someType"), - source: String::from("Test"), + source: Url::parse("https://example.net").unwrap(), datacontenttype: None, dataschema: None, subject: None, @@ -252,7 +198,7 @@ fn iterator_test_V10() { b.next().unwrap() ); assert_eq!( - ("source", AttributeValue::String("Test")), + ("source", AttributeValue::URL(&Url::parse("https://example.net").unwrap())), b.next().unwrap() ); assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs index acfd9136..3a138aa3 100644 --- a/src/event/v10/serde.rs +++ b/src/event/v10/serde.rs @@ -99,56 +99,4 @@ impl crate::event::serde::EventSerializer f } state.end() } -} - -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.dataschema.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", "1.0")?; - 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(dataschema) = &attributes.dataschema { - state.serialize_entry("dataschema", dataschema)?; - } - 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", &base64::encode(v))?, - _ => (), - }; - for (k, v) in extensions { - state.serialize_entry(k, v)?; - } - state.end() - } -} +} \ No newline at end of file From dcb2f0025bd33a207f3476aa4f3164468847a9bb Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 17:18:12 +0530 Subject: [PATCH 50/56] minor bug fixes Signed-off-by: Pranav Bhatt --- Cargo.toml | 4 ---- src/event/data.rs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 775c49c5..4d19f895 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,9 +26,5 @@ snafu = "^0.6" rstest = "0.6" claim = "0.3.1" -[dev-dependencies] -rstest = "0.6" -claim = "0.3.1" - [lib] name = "cloudevents" diff --git a/src/event/data.rs b/src/event/data.rs index 25283c92..4acf4a87 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -49,10 +49,6 @@ pub(crate) fn is_json_content_type(ct: &str) -> bool { ct == "application/json" || ct == "text/json" || ct.ends_with("+json") } -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) From 16e3b1010f42d3c3660a1fb85d40bdf7769f6d1e Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 17:36:29 +0530 Subject: [PATCH 51/56] modified URI and URIRef Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 10 ++++------ src/event/v03/attributes.rs | 6 +++--- src/event/v10/attributes.rs | 6 +++--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index b9b6a4f3..708c3a95 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -8,10 +8,9 @@ use std::fmt; pub enum AttributeValue<'a> { SpecVersion(SpecVersion), String(&'a str), - URI(&'a str), - URIRef(&'a str), + URI(&'a Url), + URIRef(&'a Url), Time(&'a DateTime), - URL(&'a Url), } impl fmt::Display for AttributeValue<'_> { @@ -19,10 +18,9 @@ impl fmt::Display for AttributeValue<'_> { match self { AttributeValue::SpecVersion(s) => s.fmt(f), AttributeValue::String(s) => f.write_str(s), - AttributeValue::URI(s) => f.write_str(s), - AttributeValue::URIRef(s) => f.write_str(s), + AttributeValue::URI(s) => f.write_str(&s.as_str()), + AttributeValue::URIRef(s) => f.write_str(&s.as_str()), AttributeValue::Time(s) => f.write_str(&s.to_rfc3339()), - AttributeValue::URL(s) => f.write_str(&s.as_str()), } } } diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index e45e8cb4..09522cce 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -40,7 +40,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::URL(&self.attributes.source))), + 2 => Some(("source", AttributeValue::URI(&self.attributes.source))), 3 => self .attributes .datacontenttype @@ -50,7 +50,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { .attributes .schemaurl .as_ref() - .map(|v| ("dataschema", AttributeValue::URL(v))), + .map(|v| ("dataschema", AttributeValue::URI(v))), 5 => self .attributes .subject @@ -199,7 +199,7 @@ fn iterator_test_V03() { b.next().unwrap() ); assert_eq!( - ("source", AttributeValue::URL(&Url::parse("https://example.net").unwrap())), + ("source", AttributeValue::URI(&Url::parse("https://example.net").unwrap())), b.next().unwrap() ); assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 446e5f7c..18501c2a 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -39,7 +39,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::URL(&self.attributes.source))), + 2 => Some(("source", AttributeValue::URI(&self.attributes.source))), 3 => self .attributes .datacontenttype @@ -49,7 +49,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { .attributes .dataschema .as_ref() - .map(|v| ("dataschema", AttributeValue::URL(v))), + .map(|v| ("dataschema", AttributeValue::URI(v))), 5 => self .attributes .subject @@ -198,7 +198,7 @@ fn iterator_test_V10() { b.next().unwrap() ); assert_eq!( - ("source", AttributeValue::URL(&Url::parse("https://example.net").unwrap())), + ("source", AttributeValue::URI(&Url::parse("https://example.net").unwrap())), b.next().unwrap() ); assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); From efcd54b6ea7c8c5ab8d6807c120b36eaa8a0b042 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 17:42:57 +0530 Subject: [PATCH 52/56] cargo fmt Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 2 +- src/event/v03/attributes.rs | 10 ++++++---- src/event/v10/attributes.rs | 5 ++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 708c3a95..6a9e0efe 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,8 +1,8 @@ use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; -use url::Url; use serde::{Deserialize, Serialize}; use std::fmt; +use url::Url; #[derive(Debug, PartialEq)] pub enum AttributeValue<'a> { diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index 09522cce..f623edcb 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -1,7 +1,7 @@ -use crate::event::attributes::{AttributesConverter, AttributeValue, DataAttributesWriter}; +use crate::event::attributes::{AttributeValue, AttributesConverter, DataAttributesWriter}; use crate::event::AttributesV10; use crate::event::{AttributesReader, AttributesWriter, SpecVersion}; -use chrono::{DateTime, Utc, NaiveDateTime}; +use chrono::{DateTime, NaiveDateTime, Utc}; use hostname::get_hostname; use url::Url; use uuid::Uuid; @@ -199,9 +199,11 @@ fn iterator_test_V03() { b.next().unwrap() ); assert_eq!( - ("source", AttributeValue::URI(&Url::parse("https://example.net").unwrap())), + ( + "source", + AttributeValue::URI(&Url::parse("https://example.net").unwrap()) + ), b.next().unwrap() ); assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); } - diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 18501c2a..7740c476 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -198,7 +198,10 @@ fn iterator_test_V10() { b.next().unwrap() ); assert_eq!( - ("source", AttributeValue::URI(&Url::parse("https://example.net").unwrap())), + ( + "source", + AttributeValue::URI(&Url::parse("https://example.net").unwrap()) + ), b.next().unwrap() ); assert_eq!(("time", AttributeValue::Time(&time)), b.next().unwrap()); From f203e52338cee9d0cb2b2cb0633ffab6afa1d3c9 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 17:58:10 +0530 Subject: [PATCH 53/56] fixed CONTRIBUTING, README and .gitignore Signed-off-by: Pranav Bhatt --- .gitignore | 2 +- CONTRIBUTING.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 36 ++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md create mode 100644 README.md diff --git a/.gitignore b/.gitignore index e61a28bf..185ca35e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/target/ +/target .idea diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a7614ad1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,129 @@ +# Contributing to CloudEvents SDK Rust + +This page contains information about reporting issues, how to suggest changes as +well as the guidelines we follow for how our documents are formatted. + +## Table of Contents + +- [Reporting an Issue](#reporting-an-issue) +- [Preparing the environment](#preparing-the-environment) +- [Suggesting a Change](#suggesting-a-change) + +## Reporting an Issue + +To report an issue, or to suggest an idea for a change that you haven't had time +to write-up yet, open an [issue](https://github.com/cloudevents/sdk-rust/issues). It +is best to check our existing +[issues](https://github.com/cloudevents/sdk-rust/issues) first to see if a similar +one has already been opened and discussed. + +## Preparing the environment + +In order to start developing this project, +you need to install the Rust tooling using [rustup](https://rustup.rs/). + +### Development commands + +To build the project: + +```sh +cargo build --all-features +``` + +To run all tests: + +```sh +cargo test --all-features +``` + +To build and open the documentation: + +```sh +cargo doc --lib --open +``` + +To run the code formatter: + +```sh +cargo fmt +``` + +## Suggesting a change + +To suggest a change to this repository, submit a +[pull request](https://github.com/cloudevents/spec/pulls)(PR) with the complete +set of changes you'd like to see. See the +[Spec Formatting Conventions](#spec-formatting-conventions) section for the +guidelines we follow for how documents are formatted. + +Each PR must be signed per the following section. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. + +Note: If your git config information is set properly then viewing the `git log` +information for your commit will look something like this: + +``` +Author: Joe Smith +Date: Thu Feb 2 11:41:15 2018 -0800 + + Update README + + Signed-off-by: Joe Smith +``` + +Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will +be rejected by the automated DCO check. diff --git a/README.md b/README.md new file mode 100644 index 00000000..996af602 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# CloudEvents SDK Rust + +Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec) + +## Spec support + +| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | +| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | +| CloudEvents Core | :x: | :heavy_check_mark: | +| AMQP Protocol Binding | :x: | :x: | +| AVRO Event Format | :x: | :x: | +| HTTP Protocol Binding | :x: | :x: | +| JSON Event Format | :x: | :heavy_check_mark: | +| Kafka Protocol Binding | :x: | :x: | +| MQTT Protocol Binding | :x: | :x: | +| NATS Protocol Binding | :x: | :x: | +| Web hook | :x: | :x: | + +## Development & Contributing + +If you're interested in contributing to sdk-rust, look at [Contributing documentation](CONTRIBUTING.md) + +## Community + +- There are bi-weekly calls immediately following the + [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) + at 9am PT (US Pacific). Which means they will typically start at 10am PT, but + if the other call ends early then the SDK call will start early as well. See + the + [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) + to determine which week will have the call. +- Slack: #cloudeventssdk (or #cloudevents-sdk-rust) channel under + [CNCF's Slack workspace](https://slack.cncf.io/). +- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk +- Contact for additional information: Fancesco Guardiani (`@slinkydeveloper` + on slack). From e52b2202d01929bf75a22bcbc878d784663de37e Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 18:17:11 +0530 Subject: [PATCH 54/56] URI->URIRef in iterator Signed-off-by: Pranav Bhatt --- src/event/v03/attributes.rs | 6 +++--- src/event/v10/attributes.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index f623edcb..df206da2 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -39,7 +39,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { fn next(&mut self) -> Option { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), - 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), + 1 => Some(("type", AttributeValue::String(&self.attributes.ty))), 2 => Some(("source", AttributeValue::URI(&self.attributes.source))), 3 => self .attributes @@ -50,7 +50,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { .attributes .schemaurl .as_ref() - .map(|v| ("dataschema", AttributeValue::URI(v))), + .map(|v| ("schemaurl", AttributeValue::URIRef(v))), 5 => self .attributes .subject @@ -201,7 +201,7 @@ fn iterator_test_V03() { assert_eq!( ( "source", - AttributeValue::URI(&Url::parse("https://example.net").unwrap()) + AttributeValue::URIRef(&Url::parse("https://example.net").unwrap()) ), b.next().unwrap() ); diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 7740c476..4bc5c943 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -38,8 +38,8 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { fn next(&mut self) -> Option { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), - 1 => Some(("ty", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::URI(&self.attributes.source))), + 1 => Some(("type", AttributeValue::String(&self.attributes.ty))), + 2 => Some(("source", AttributeValue::URIRef(&self.attributes.source))), 3 => self .attributes .datacontenttype @@ -200,7 +200,7 @@ fn iterator_test_V10() { assert_eq!( ( "source", - AttributeValue::URI(&Url::parse("https://example.net").unwrap()) + AttributeValue::URIRef(&Url::parse("https://example.net").unwrap()) ), b.next().unwrap() ); From fd09bb8d7237846dc3d3c25f4e86bb9368c07472 Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 18:18:23 +0530 Subject: [PATCH 55/56] URI->URIRef for iterator 2 Signed-off-by: Pranav Bhatt --- src/event/v03/attributes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index df206da2..1fd18ca8 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -40,7 +40,7 @@ impl<'a> Iterator for AttributesIntoIterator<'a> { let result = match self.index { 0 => Some(("id", AttributeValue::String(&self.attributes.id))), 1 => Some(("type", AttributeValue::String(&self.attributes.ty))), - 2 => Some(("source", AttributeValue::URI(&self.attributes.source))), + 2 => Some(("source", AttributeValue::URIRef(&self.attributes.source))), 3 => self .attributes .datacontenttype From 16ab4a18c028e7efd76f2c0aeacf368b0f4b6f0c Mon Sep 17 00:00:00 2001 From: Pranav Bhatt Date: Thu, 30 Apr 2020 18:28:31 +0530 Subject: [PATCH 56/56] minor bug fixes Signed-off-by: Pranav Bhatt --- src/event/attributes.rs | 1 - src/event/v03/attributes.rs | 2 +- src/event/v10/attributes.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 6a9e0efe..1ab50aec 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,6 +1,5 @@ use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use std::fmt; use url::Url; diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index 1fd18ca8..b0209064 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -195,7 +195,7 @@ fn iterator_test_V03() { assert_eq!(("id", AttributeValue::String("1")), b.next().unwrap()); assert_eq!( - ("ty", AttributeValue::String("someType")), + ("type", AttributeValue::String("someType")), b.next().unwrap() ); assert_eq!( diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 4bc5c943..1cf4f6b8 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -194,7 +194,7 @@ fn iterator_test_V10() { assert_eq!(("id", AttributeValue::String("1")), b.next().unwrap()); assert_eq!( - ("ty", AttributeValue::String("someType")), + ("type", AttributeValue::String("someType")), b.next().unwrap() ); assert_eq!(