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:
parent
e91622efdb
commit
5d280c4d33
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
]
|
||||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -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"
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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,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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod attributes;
|
||||||
|
|
||||||
|
pub use attributes::Attributes;
|
|
@ -0,0 +1,6 @@
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
pub mod event;
|
||||||
|
|
||||||
|
pub use event::Event;
|
|
@ -0,0 +1,2 @@
|
||||||
|
#[test]
|
||||||
|
fn use_event() {}
|
Loading…
Reference in New Issue