Event Data Structure (#11)

* Started sketching the Event data structure
* Implemented 1.0 Attributes
* Fixed data read/write methods
* Added extensions creators
* Fixed doc example
* Fixed github ci
* Fixed Data creation from base64
* Updated meta

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
This commit is contained in:
Francesco Guardiani 2020-03-13 08:24:55 +01:00 committed by GitHub
parent e91622efdb
commit 5d280c4d33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 991 additions and 8 deletions

View File

@ -1,6 +1,12 @@
name: Rust name: Rust
on: [push, pull_request] on:
push:
branches:
- master
pull_request:
branches:
- master
jobs: jobs:
build: build:
@ -10,12 +16,10 @@ jobs:
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --all-features
toolchain: stable toolchain: stable
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --all-features
toolchain: stable toolchain: stable
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu

289
Cargo.lock generated
View File

@ -1,5 +1,290 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]] [[package]]
name = "cloudevents-rust-sdk" name = "autocfg"
version = "0.1.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "base64"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "chrono"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
dependencies = [
"num-integer",
"num-traits",
"serde",
"time",
]
[[package]]
name = "cloudevents"
version = "0.0.1"
dependencies = [
"base64",
"chrono",
"delegate",
"hostname",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "delegate"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29017a50a5f59055b6edd22a8da6a764582edbe24fba9d953abf84be68dc2049"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hostname"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
dependencies = [
"libc",
"winutil",
]
[[package]]
name = "itoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
[[package]]
name = "libc"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
[[package]]
name = "num-integer"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
dependencies = [
"autocfg",
]
[[package]]
name = "ppv-lite86"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
dependencies = [
"libc",
"redox_syscall",
"winapi",
]
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "uuid"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"rand",
"serde",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winutil"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e"
dependencies = [
"winapi",
]

View File

@ -1,9 +1,20 @@
[package] [package]
name = "cloudevents-rust-sdk" name = "cloudevents"
version = "0.1.0" version = "0.0.1"
authors = ["Francesco Guardiani <francescoguard@gmail.com>"] authors = ["Francesco Guardiani <francescoguard@gmail.com>"]
license-file = "LICENSE"
edition = "2018" edition = "2018"
description = "CloudEvents official Rust SDK"
readme = "README.md"
repository = "https://github.com/cloudevents/sdk-rust"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
chrono = { version = "^0.4", features = ["serde"] }
delegate = "^0.4"
uuid = { version = "^0.8", features = ["serde", "v4"] }
hostname = "^0.1"
base64 = "^0.12"

186
src/event/attributes.rs Normal file
View File

@ -0,0 +1,186 @@
use super::SpecVersion;
use crate::event::{AttributesV10, ExtensionValue};
use chrono::{DateTime, FixedOffset};
/// 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).
fn get_id(&self) -> &str;
/// Get the [source](https://github.com/cloudevents/spec/blob/master/spec.md#source-1).
fn get_source(&self) -> &str;
/// Get the [specversion](https://github.com/cloudevents/spec/blob/master/spec.md#specversion).
fn get_specversion(&self) -> SpecVersion;
/// Get the [type](https://github.com/cloudevents/spec/blob/master/spec.md#type).
fn get_type(&self) -> &str;
/// Get the [datacontenttype](https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype).
fn get_datacontenttype(&self) -> Option<&str>;
/// Get the [dataschema](https://github.com/cloudevents/spec/blob/master/spec.md#dataschema).
fn get_dataschema(&self) -> Option<&str>;
/// Get the [subject](https://github.com/cloudevents/spec/blob/master/spec.md#subject).
fn get_subject(&self) -> Option<&str>;
/// Get the [time](https://github.com/cloudevents/spec/blob/master/spec.md#time).
fn get_time(&self) -> Option<&DateTime<FixedOffset>>;
/// 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)>;
}
pub trait AttributesWriter {
fn set_id(&mut self, id: impl Into<String>);
fn set_source(&mut self, source: impl Into<String>);
fn set_type(&mut self, ty: impl Into<String>);
fn set_subject(&mut self, subject: Option<impl Into<String>>);
fn set_time(&mut self, time: Option<impl Into<DateTime<FixedOffset>>>);
fn set_extension<'name, 'event: 'name>(
&'event mut self,
extension_name: &'name str,
extension_value: impl Into<ExtensionValue>,
);
fn remove_extension<'name, 'event: 'name>(
&'event mut self,
extension_name: &'name str,
) -> Option<ExtensionValue>;
}
pub(crate) trait DataAttributesWriter {
fn set_datacontenttype(
&mut self,
datacontenttype: Option<impl Into<String>>,
);
fn set_dataschema(&mut self, dataschema: Option<impl Into<String>>);
}
pub enum Attributes {
V10(AttributesV10),
}
impl AttributesReader for Attributes {
fn get_id(&self) -> &str {
match self {
Attributes::V10(a) => a.get_id(),
}
}
fn get_source(&self) -> &str {
match self {
Attributes::V10(a) => a.get_source(),
}
}
fn get_specversion(&self) -> SpecVersion {
match self {
Attributes::V10(a) => a.get_specversion(),
}
}
fn get_type(&self) -> &str {
match self {
Attributes::V10(a) => a.get_type(),
}
}
fn get_datacontenttype(&self) -> Option<&str> {
match self {
Attributes::V10(a) => a.get_datacontenttype(),
}
}
fn get_dataschema(&self) -> Option<&str> {
match self {
Attributes::V10(a) => a.get_dataschema(),
}
}
fn get_subject(&self) -> Option<&str> {
match self {
Attributes::V10(a) => a.get_subject(),
}
}
fn get_time(&self) -> Option<&DateTime<FixedOffset>> {
match self {
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 get_extensions(&self) -> Vec<(&str, &ExtensionValue)> {
match self {
Attributes::V10(a) => a.get_extensions(),
}
}
}
impl AttributesWriter for Attributes {
fn set_id(&mut self, id: impl Into<String>) {
match self {
Attributes::V10(a) => a.set_id(id),
}
}
fn set_source(&mut self, source: impl Into<String>) {
match self {
Attributes::V10(a) => a.set_source(source),
}
}
fn set_type(&mut self, ty: impl Into<String>) {
match self {
Attributes::V10(a) => a.set_type(ty),
}
}
fn set_subject(&mut self, subject: Option<impl Into<String>>) {
match self {
Attributes::V10(a) => a.set_subject(subject),
}
}
fn set_time(&mut self, time: Option<impl Into<DateTime<FixedOffset>>>) {
match self {
Attributes::V10(a) => a.set_time(time),
}
}
fn set_extension<'name, 'event: 'name>(
&'event mut self,
extension_name: &'name str,
extension_value: impl Into<ExtensionValue>,
) {
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<ExtensionValue> {
match self {
Attributes::V10(a) => a.remove_extension(extension_name),
}
}
}
impl DataAttributesWriter for Attributes {
fn set_datacontenttype(
&mut self,
datacontenttype: Option<impl Into<String>>,
) {
match self {
Attributes::V10(a) => a.set_datacontenttype(datacontenttype),
}
}
fn set_dataschema(&mut self, dataschema: Option<impl Into<String>>) {
match self {
Attributes::V10(a) => a.set_dataschema(dataschema),
}
}
}

74
src/event/data.rs Normal file
View File

@ -0,0 +1,74 @@
use serde::{Deserialize, Serialize};
use std::convert::{Into, TryFrom};
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
/// Possible data values
pub enum Data {
String(String),
Binary(Vec<u8>),
Json(serde_json::Value),
}
impl Data {
/// Create a [`Data`] from a [`Into<Vec<u8>>`].
///
/// # Example
///
/// ```
/// use cloudevents::event::Data;
///
/// let value = Data::from_base64(b"dmFsdWU=").unwrap();
/// assert_eq!(value, Data::Binary(base64::decode("dmFsdWU=").unwrap()));
/// ```
///
/// [`AsRef<[u8]>`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
/// [`Data`]: enum.Data.html
pub fn from_base64<I>(i: I) -> Result<Self, base64::DecodeError>
where
I: AsRef<[u8]>,
{
Ok(base64::decode(&i)?.into())
}
}
impl Into<Data> for serde_json::Value {
fn into(self) -> Data {
Data::Json(self)
}
}
impl Into<Data> for Vec<u8> {
fn into(self) -> Data {
Data::Binary(self)
}
}
impl Into<Data> for String {
fn into(self) -> Data {
Data::String(self)
}
}
impl TryFrom<Data> for serde_json::Value {
type Error = serde_json::Error;
fn try_from(value: Data) -> Result<Self, Self::Error> {
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<Data> for String {
type Error = std::string::FromUtf8Error;
fn try_from(value: Data) -> Result<Self, Self::Error> {
match value {
Data::String(s) => Ok(s),
Data::Binary(v) => Ok(String::from_utf8(v)?),
Data::Json(s) => Ok(s.to_string())
}
}
}

156
src/event/event.rs Normal file
View File

@ -0,0 +1,156 @@
use super::{Attributes, AttributesReader, AttributesWriter, Data, ExtensionValue, SpecVersion, AttributesV10};
use chrono::{DateTime, FixedOffset};
use delegate::delegate;
use std::convert::{TryFrom};
use crate::event::attributes::DataAttributesWriter;
/// Data structure that represents a [CloudEvent](https://github.com/cloudevents/spec/blob/master/spec.md).
/// It provides methods to get the attributes through [`AttributesReader`](cloudevents::event::AttributesReader)
/// and write them through [`AttributesWriter`](cloudevents::event::AttributesWriter).
/// It also provides methods to read and write the [event data](https://github.com/cloudevents/spec/blob/master/spec.md#event-data)
/// ```
/// use cloudevents::Event;
/// use cloudevents::event::AttributesReader;
///
/// // Create an event using the Default trait
/// let mut e = Event::default();
/// e.write_data(
/// "application/json",
/// None,
/// serde_json::json!({"hello": "world"})
/// );
///
/// // Print the event id
/// println!("Event id: {}", e.get_id());
///
/// // Get the event data
/// let data: serde_json::Value = e.try_get_data().unwrap().unwrap();
/// println!("Event data: {}", data)
/// ```
pub struct Event {
pub attributes: Attributes,
pub data: Option<Data>,
}
impl AttributesReader for Event {
delegate! {
to self.attributes {
fn get_id(&self) -> &str;
fn get_source(&self) -> &str;
fn get_specversion(&self) -> SpecVersion;
fn get_type(&self) -> &str;
fn get_datacontenttype(&self) -> Option<&str>;
fn get_dataschema(&self) -> Option<&str>;
fn get_subject(&self) -> Option<&str>;
fn get_time(&self) -> Option<&DateTime<FixedOffset>>;
fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue>;
fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)>;
}
}
}
impl AttributesWriter for Event {
delegate! {
to self.attributes {
fn set_id(&mut self, id: impl Into<String>);
fn set_source(&mut self, source: impl Into<String>);
fn set_type(&mut self, ty: impl Into<String>);
fn set_subject(&mut self, subject: Option<impl Into<String>>);
fn set_time(&mut self, time: Option<impl Into<DateTime<FixedOffset>>>);
fn set_extension<'name, 'event: 'name>(
&'event mut self,
extension_name: &'name str,
extension_value: impl Into<ExtensionValue>,
);
fn remove_extension<'name, 'event: 'name>(
&'event mut self,
extension_name: &'name str,
) -> Option<ExtensionValue>;
}
}
}
impl Default for Event {
fn default() -> Self {
Event {
attributes: Attributes::V10(AttributesV10::default()),
data: None
}
}
}
impl Event {
pub fn remove_data(&mut self) {
self.data = None;
}
/// Write data into the `Event`. You must provide a `content_type` and you can optionally provide a `schema`.
///
/// ```
/// use cloudevents::Event;
/// use serde_json::json;
/// use std::convert::Into;
///
/// let mut e = Event::default();
/// e.write_data("application/json", None, json!({}))
/// ```
pub fn write_data<S: Into<String>, D: Into<Data>>(&mut self, content_type: S, schema: Option<S>, value: D) {
self.attributes.set_datacontenttype(Some(content_type));
self.attributes.set_dataschema(schema);
self.data = Some(value.into());
}
pub fn get_data<T: Sized + From<Data>>(
&self,
) -> Option<T> {
match self.data.as_ref() {
Some(d) => Some(T::from(d.clone())),
None => None,
}
}
pub fn try_get_data<T: Sized + TryFrom<Data, Error = E>, E: std::error::Error>(
&self,
) -> Option<Result<T, E>> {
match self.data.as_ref() {
Some(d) => Some(T::try_from(d.clone())),
None => None,
}
}
pub fn into_data<T: Sized + TryFrom<Data, Error = E>, E: std::error::Error>(
self,
) -> Option<Result<T, E>> {
match self.data {
Some(d) => Some(T::try_from(d)),
None => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_get_data_json() {
let expected_data = serde_json::json!({
"hello": "world"
});
let mut e = Event::default();
e.write_data(
"application/json",
None,
expected_data.clone()
);
let data: serde_json::Value = e.try_get_data().unwrap().unwrap();
assert_eq!(expected_data, data);
assert_eq!("application/json", e.get_datacontenttype().unwrap())
}
}

71
src/event/extensions.rs Normal file
View File

@ -0,0 +1,71 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::convert::From;
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
#[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.
String(String),
/// Represents a [`bool`](bool) value.
Boolean(bool),
/// Represents an integer [`i64`](i64) value.
Integer(i64),
/// Represents a [Json `Value`](serde_json::value::Value).
Json(Value),
}
impl From<String> for ExtensionValue {
fn from(s: String) -> Self {
ExtensionValue::String(s)
}
}
impl From<bool> for ExtensionValue {
fn from(s: bool) -> Self {
ExtensionValue::Boolean(s)
}
}
impl From<i64> for ExtensionValue {
fn from(s: i64) -> Self {
ExtensionValue::Integer(s)
}
}
impl From<Value> for ExtensionValue {
fn from(s: Value) -> Self {
ExtensionValue::Json(s)
}
}
impl ExtensionValue {
pub fn from_string<S>(s: S) -> Self
where
S: Into<String>,
{
ExtensionValue::from(s.into())
}
pub fn from_i64<S>(s: S) -> Self
where
S: Into<i64>,
{
ExtensionValue::from(s.into())
}
pub fn from_bool<S>(s: S) -> Self
where
S: Into<bool>,
{
ExtensionValue::from(s.into())
}
pub fn from_json_value<S>(s: S) -> Self
where
S: Into<serde_json::Value>,
{
ExtensionValue::from(s.into())
}
}

16
src/event/mod.rs Normal file
View File

@ -0,0 +1,16 @@
mod attributes;
mod data;
mod event;
mod extensions;
mod spec_version;
pub use attributes::Attributes;
pub use attributes::{AttributesReader, AttributesWriter};
pub use data::Data;
pub use event::Event;
pub use extensions::ExtensionValue;
pub use spec_version::SpecVersion;
mod v10;
pub use v10::Attributes as AttributesV10;

32
src/event/spec_version.rs Normal file
View File

@ -0,0 +1,32 @@
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum SpecVersion {
#[serde(rename = "0.3")]
V03,
#[serde(rename = "1.0")]
V10,
}
impl fmt::Display for SpecVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpecVersion::V03 => write!(f, "0.3"),
SpecVersion::V10 => write!(f, "1.0"),
}
}
}
impl TryFrom<String> for SpecVersion {
type Error = String;
fn try_from(value: String) -> Result<Self, String> {
match value.as_str() {
"0.3" => Ok(SpecVersion::V03),
"1.0" => Ok(SpecVersion::V10),
_ => Err(format!("Invalid specversion {}", value)),
}
}
}

0
src/event/v03/mod.rs Normal file
View File

137
src/event/v10/attributes.rs Normal file
View File

@ -0,0 +1,137 @@
use crate::event::{AttributesReader, AttributesWriter, ExtensionValue, SpecVersion};
use chrono::{DateTime, FixedOffset};
use std::collections::HashMap;
use crate::event::attributes::DataAttributesWriter;
use uuid::Uuid;
use hostname::get_hostname;
pub struct Attributes {
id: String,
ty: String,
source: String,
datacontenttype: Option<String>,
dataschema: Option<String>,
subject: Option<String>,
time: Option<DateTime<FixedOffset>>,
extensions: HashMap<String, ExtensionValue>,
}
impl AttributesReader for Attributes {
fn get_id(&self) -> &str {
&self.id
}
fn get_source(&self) -> &str {
&self.source
}
fn get_specversion(&self) -> SpecVersion {
SpecVersion::V10
}
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.dataschema.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<FixedOffset>> {
self.time.as_ref()
}
fn get_extension(&self, extension_name: &str) -> Option<&ExtensionValue> {
self.extensions.get(extension_name)
}
fn get_extensions(&self) -> Vec<(&str, &ExtensionValue)> {
self.extensions
.iter()
.map(|(k, v)| (k.as_str(), v))
.collect()
}
}
impl AttributesWriter for Attributes {
fn set_id(&mut self, id: impl Into<String>) {
self.id = id.into()
}
fn set_source(&mut self, source: impl Into<String>) {
self.source = source.into()
}
fn set_type(&mut self, ty: impl Into<String>) {
self.ty = ty.into()
}
fn set_subject(&mut self, subject: Option<impl Into<String>>) {
self.subject = subject.map(Into::into)
}
fn set_time(&mut self, time: Option<impl Into<DateTime<FixedOffset>>>) {
self.time = time.map(Into::into)
}
fn set_extension<'name, 'event: 'name>(
&'event mut self,
extension_name: &'name str,
extension_value: impl Into<ExtensionValue>,
) {
self.extensions
.insert(extension_name.to_owned(), extension_value.into());
}
fn remove_extension<'name, 'event: 'name>(
&'event mut self,
extension_name: &'name str,
) -> Option<ExtensionValue> {
self.extensions.remove(extension_name)
}
}
impl DataAttributesWriter for Attributes {
fn set_datacontenttype(
&mut self,
datacontenttype: Option<impl Into<String>>,
) {
self.datacontenttype = datacontenttype.map(Into::into)
}
fn set_dataschema(&mut self, dataschema: Option<impl Into<String>>) {
self.dataschema = 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,
dataschema: None,
subject: None,
time: None,
extensions: HashMap::new()
}
}
}

3
src/event/v10/mod.rs Normal file
View File

@ -0,0 +1,3 @@
mod attributes;
pub use attributes::Attributes;

View File

@ -0,0 +1,6 @@
extern crate serde;
extern crate serde_json;
pub mod event;
pub use event::Event;

2
tests/event.rs Normal file
View File

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