From 9b2f26223f1e531b950b7be6b29ef2519f75f587 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Wed, 29 Apr 2020 21:40:37 +0200 Subject: [PATCH] Introduced URL support (#27) * Introduced url library Signed-off-by: Francesco Guardiani * Fixed lock Signed-off-by: Francesco Guardiani * Integrated changes with url library Signed-off-by: Francesco Guardiani * Cargo fmt Signed-off-by: Francesco Guardiani --- Cargo.lock | 60 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/event/attributes.rs | 17 ++++++----- src/event/builder.rs | 3 +- src/event/event.rs | 27 +++++++++++------ src/event/serde.rs | 22 ++++++++++++++ src/event/v03/attributes.rs | 37 +++++++++++------------ src/event/v03/builder.rs | 17 ++++++----- src/event/v03/message.rs | 4 +-- src/event/v03/serde.rs | 20 +++++-------- src/event/v10/attributes.rs | 37 +++++++++++------------ src/event/v10/builder.rs | 17 ++++++----- src/event/v10/message.rs | 4 +-- src/event/v10/serde.rs | 20 +++++-------- src/message/deserializer.rs | 5 +++- src/message/types.rs | 21 ++++++++++--- tests/test_data/data.rs | 2 +- tests/test_data/v03.rs | 18 +++++++---- tests/test_data/v10.rs | 17 +++++++---- 19 files changed, 231 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d90759..700e313 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,7 @@ dependencies = [ "serde-value", "serde_json", "snafu", + "url", "uuid", ] @@ -94,6 +95,17 @@ dependencies = [ "winutil", ] +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "0.4.5" @@ -106,6 +118,12 @@ version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "num-integer" version = "0.1.42" @@ -134,6 +152,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "ppv-lite86" version = "0.2.6" @@ -289,6 +313,12 @@ dependencies = [ "serde", ] +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + [[package]] name = "snafu" version = "0.6.6" @@ -332,12 +362,42 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +dependencies = [ + "smallvec", +] + [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +dependencies = [ + "idna", + "matches", + "percent-encoding", + "serde", +] + [[package]] name = "uuid" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 02d6620..4d19f89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ delegate = "^0.4" uuid = { version = "^0.8", features = ["serde", "v4"] } hostname = "^0.1" base64 = "^0.12" +url = { version = "^2.1", features = ["serde"] } snafu = "^0.6" [dev-dependencies] diff --git a/src/event/attributes.rs b/src/event/attributes.rs index 5cfa5be..6470126 100644 --- a/src/event/attributes.rs +++ b/src/event/attributes.rs @@ -1,12 +1,13 @@ use super::{AttributesV03, AttributesV10, SpecVersion}; use chrono::{DateTime, Utc}; +use url::Url; /// 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; + fn get_source(&self) -> &Url; /// 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). @@ -14,7 +15,7 @@ pub trait AttributesReader { /// 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>; + fn get_dataschema(&self) -> Option<&Url>; /// 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). @@ -23,7 +24,7 @@ pub trait AttributesReader { pub trait AttributesWriter { fn set_id(&mut self, id: impl Into); - fn set_source(&mut self, source: impl Into); + fn set_source(&mut self, source: impl Into); fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); @@ -36,7 +37,7 @@ pub(crate) trait AttributesConverter { pub(crate) trait DataAttributesWriter { fn set_datacontenttype(&mut self, datacontenttype: Option>); - fn set_dataschema(&mut self, dataschema: Option>); + fn set_dataschema(&mut self, dataschema: Option>); } #[derive(PartialEq, Debug, Clone)] @@ -53,7 +54,7 @@ impl AttributesReader for Attributes { } } - fn get_source(&self) -> &str { + fn get_source(&self) -> &Url { match self { Attributes::V03(a) => a.get_source(), Attributes::V10(a) => a.get_source(), @@ -81,7 +82,7 @@ impl AttributesReader for Attributes { } } - fn get_dataschema(&self) -> Option<&str> { + fn get_dataschema(&self) -> Option<&Url> { match self { Attributes::V03(a) => a.get_dataschema(), Attributes::V10(a) => a.get_dataschema(), @@ -111,7 +112,7 @@ impl AttributesWriter for Attributes { } } - fn set_source(&mut self, source: impl Into) { + fn set_source(&mut self, source: impl Into) { match self { Attributes::V03(a) => a.set_source(source), Attributes::V10(a) => a.set_source(source), @@ -148,7 +149,7 @@ impl DataAttributesWriter for Attributes { } } - fn set_dataschema(&mut self, dataschema: Option>) { + fn set_dataschema(&mut self, dataschema: Option>) { match self { Attributes::V03(a) => a.set_dataschema(dataschema), Attributes::V10(a) => a.set_dataschema(dataschema), diff --git a/src/event/builder.rs b/src/event/builder.rs index 5dbfef3..ead1981 100644 --- a/src/event/builder.rs +++ b/src/event/builder.rs @@ -4,10 +4,11 @@ use super::{EventBuilderV03, EventBuilderV10}; /// ``` /// use cloudevents::EventBuilder; /// use chrono::Utc; +/// use url::Url; /// /// let event = EventBuilder::v10() /// .id("my_event.my_application") -/// .source("http://localhost:8080") +/// .source(Url::parse("http://localhost:8080").unwrap()) /// .time(Utc::now()) /// .build(); /// ``` diff --git a/src/event/event.rs b/src/event/event.rs index e1c138a..188be16 100644 --- a/src/event/event.rs +++ b/src/event/event.rs @@ -7,6 +7,7 @@ use chrono::{DateTime, Utc}; use delegate::delegate; use std::collections::HashMap; use std::convert::TryFrom; +use url::Url; /// Data structure that represents a [CloudEvent](https://github.com/cloudevents/spec/blob/master/spec.md). /// It provides methods to get the attributes through [`AttributesReader`] @@ -43,11 +44,11 @@ impl AttributesReader for Event { delegate! { to self.attributes { fn get_id(&self) -> &str; - fn get_source(&self) -> &str; + fn get_source(&self) -> &Url; fn get_specversion(&self) -> SpecVersion; fn get_type(&self) -> &str; fn get_datacontenttype(&self) -> Option<&str>; - fn get_dataschema(&self) -> Option<&str>; + fn get_dataschema(&self) -> Option<&Url>; fn get_subject(&self) -> Option<&str>; fn get_time(&self) -> Option<&DateTime>; } @@ -58,7 +59,7 @@ impl AttributesWriter for Event { delegate! { to self.attributes { fn set_id(&mut self, id: impl Into); - fn set_source(&mut self, source: impl Into); + fn set_source(&mut self, source: impl Into); fn set_type(&mut self, ty: impl Into); fn set_subject(&mut self, subject: Option>); fn set_time(&mut self, time: Option>>); @@ -79,7 +80,7 @@ impl Default for Event { impl Event { pub fn remove_data(&mut self) { self.data = None; - self.attributes.set_dataschema(None as Option); + self.attributes.set_dataschema(None as Option); self.attributes.set_datacontenttype(None as Option); } @@ -95,7 +96,7 @@ impl Event { /// ``` pub fn write_data(&mut self, datacontenttype: impl Into, data: impl Into) { self.attributes.set_datacontenttype(Some(datacontenttype)); - self.attributes.set_dataschema(None as Option<&str>); + self.attributes.set_dataschema(None as Option); self.data = Some(data.into()); } @@ -105,14 +106,19 @@ impl Event { /// use cloudevents::Event; /// use serde_json::json; /// use std::convert::Into; + /// use url::Url; /// /// let mut e = Event::default(); - /// e.write_data_with_schema("application/json", "http://myapplication.com/schema", json!({})) + /// e.write_data_with_schema( + /// "application/json", + /// Url::parse("http://myapplication.com/schema").unwrap(), + /// json!({}) + /// ) /// ``` pub fn write_data_with_schema( &mut self, datacontenttype: impl Into, - dataschema: impl Into, + dataschema: impl Into, data: impl Into, ) { self.attributes.set_datacontenttype(Some(datacontenttype)); @@ -186,14 +192,17 @@ mod tests { let mut e = Event::default(); e.write_data_with_schema( "application/json", - "http://localhost:8080/schema", + Url::parse("http://localhost:8080/schema").unwrap(), expected_data.clone(), ); let data: serde_json::Value = e.try_get_data().unwrap().unwrap(); assert_eq!(expected_data, data); assert_eq!("application/json", e.get_datacontenttype().unwrap()); - assert_eq!("http://localhost:8080/schema", e.get_dataschema().unwrap()) + assert_eq!( + &Url::parse("http://localhost:8080/schema").unwrap(), + e.get_dataschema().unwrap() + ) } #[test] diff --git a/src/event/serde.rs b/src/event/serde.rs index a3ab1b1..89d29a4 100644 --- a/src/event/serde.rs +++ b/src/event/serde.rs @@ -20,6 +20,23 @@ macro_rules! parse_optional_field { }) .transpose() }; + + ($map:ident, $name:literal, $value_variant:ident, $error:ty, $mapper:expr) => { + $map.remove($name) + .map(|val| match val { + Value::$value_variant(v) => $mapper(&v).map_err(|e| { + <$error>::invalid_value( + crate::event::serde::value_to_unexpected(&Value::$value_variant(v)), + &e.to_string().as_str(), + ) + }), + other => Err(<$error>::invalid_type( + crate::event::serde::value_to_unexpected(&other), + &stringify!($value_variant), + )), + }) + .transpose() + }; } macro_rules! parse_field { @@ -27,6 +44,11 @@ macro_rules! parse_field { parse_optional_field!($map, $name, $value_variant, $error)? .ok_or_else(|| <$error>::missing_field($name)) }; + + ($map:ident, $name:literal, $value_variant:ident, $error:ty, $mapper:expr) => { + parse_optional_field!($map, $name, $value_variant, $error, $mapper)? + .ok_or_else(|| <$error>::missing_field($name)) + }; } macro_rules! parse_data_json { diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index 1e75629..09a355e 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -3,15 +3,16 @@ use crate::event::AttributesV10; use crate::event::{AttributesReader, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; +use url::Url; use uuid::Uuid; #[derive(PartialEq, Debug, Clone)] pub struct Attributes { pub(crate) id: String, pub(crate) ty: String, - pub(crate) source: String, + pub(crate) source: Url, pub(crate) datacontenttype: Option, - pub(crate) schemaurl: Option, + pub(crate) schemaurl: Option, pub(crate) subject: Option, pub(crate) time: Option>, } @@ -21,7 +22,7 @@ impl AttributesReader for Attributes { &self.id } - fn get_source(&self) -> &str { + fn get_source(&self) -> &Url { &self.source } @@ -34,24 +35,15 @@ impl AttributesReader for Attributes { } fn get_datacontenttype(&self) -> Option<&str> { - match self.datacontenttype.as_ref() { - Some(s) => Some(&s), - None => None, - } + self.datacontenttype.as_deref() } - fn get_dataschema(&self) -> Option<&str> { - match self.schemaurl.as_ref() { - Some(s) => Some(&s), - None => None, - } + fn get_dataschema(&self) -> Option<&Url> { + self.schemaurl.as_ref() } fn get_subject(&self) -> Option<&str> { - match self.subject.as_ref() { - Some(s) => Some(&s), - None => None, - } + self.subject.as_deref() } fn get_time(&self) -> Option<&DateTime> { @@ -64,7 +56,7 @@ impl AttributesWriter for Attributes { self.id = id.into() } - fn set_source(&mut self, source: impl Into) { + fn set_source(&mut self, source: impl Into) { self.source = source.into() } @@ -86,7 +78,7 @@ impl DataAttributesWriter for Attributes { self.datacontenttype = datacontenttype.map(Into::into) } - fn set_dataschema(&mut self, dataschema: Option>) { + fn set_dataschema(&mut self, dataschema: Option>) { self.schemaurl = dataschema.map(Into::into) } } @@ -96,7 +88,14 @@ impl Default for Attributes { Attributes { id: Uuid::new_v4().to_string(), ty: "type".to_string(), - source: get_hostname().unwrap_or("http://localhost/".to_string()), + source: Url::parse( + format!( + "http://{}", + get_hostname().unwrap_or("localhost".to_string()) + ) + .as_ref(), + ) + .unwrap(), datacontenttype: None, schemaurl: None, subject: None, diff --git a/src/event/v03/builder.rs b/src/event/v03/builder.rs index 41aca7c..9982fb0 100644 --- a/src/event/v03/builder.rs +++ b/src/event/v03/builder.rs @@ -2,6 +2,7 @@ use super::Attributes as AttributesV03; use crate::event::{Attributes, AttributesWriter, Data, Event, ExtensionValue}; use chrono::{DateTime, Utc}; use std::collections::HashMap; +use url::Url; pub struct EventBuilder { event: Event, @@ -33,7 +34,7 @@ impl EventBuilder { return self; } - pub fn source(mut self, source: impl Into) -> Self { + pub fn source(mut self, source: impl Into) -> Self { self.event.set_source(source); return self; } @@ -70,7 +71,7 @@ impl EventBuilder { pub fn data_with_schema( mut self, datacontenttype: impl Into, - schemaurl: impl Into, + schemaurl: impl Into, data: impl Into, ) -> Self { self.event @@ -91,31 +92,31 @@ mod tests { #[test] fn build_event() { let id = "aaa"; - let source = "http://localhost:8080"; + let source = Url::parse("http://localhost:8080").unwrap(); 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 schema = Url::parse("http://localhost:8080/schema").unwrap(); let data = serde_json::json!({ "hello": "world" }); let event = EventBuilder::new() .id(id) - .source(source) + .source(source.clone()) .ty(ty) .subject(subject) .time(time) .extension(extension_name, extension_value) - .data_with_schema(content_type, schema, data.clone()) + .data_with_schema(content_type, schema.clone(), data.clone()) .build(); assert_eq!(SpecVersion::V03, event.get_specversion()); assert_eq!(id, event.get_id()); - assert_eq!(source, event.get_source()); + assert_eq!(source, event.get_source().clone()); assert_eq!(ty, event.get_type()); assert_eq!(subject, event.get_subject().unwrap()); assert_eq!(time, event.get_time().unwrap().clone()); @@ -124,7 +125,7 @@ mod tests { event.get_extension(extension_name).unwrap().clone() ); assert_eq!(content_type, event.get_datacontenttype().unwrap()); - assert_eq!(schema, event.get_dataschema().unwrap()); + assert_eq!(schema, event.get_dataschema().unwrap().clone()); let event_data: serde_json::Value = event.try_get_data().unwrap().unwrap(); assert_eq!(data, event_data); diff --git a/src/event/v03/message.rs b/src/event/v03/message.rs index 1c22a90..e2fd11f 100644 --- a/src/event/v03/message.rs +++ b/src/event/v03/message.rs @@ -42,9 +42,9 @@ impl crate::event::deserializer::AttributesSerializer for super::Attributes { match name { "id" => self.id = value.to_string(), "type" => self.ty = value.to_string(), - "source" => self.source = value.to_string(), + "source" => self.source = value.try_into()?, "datacontenttype" => self.datacontenttype = Some(value.to_string()), - "schemaurl" => self.schemaurl = Some(value.to_string()), + "schemaurl" => self.schemaurl = Some(value.try_into()?), "subject" => self.subject = Some(value.to_string()), "time" => self.time = Some(value.try_into()?), _ => { diff --git a/src/event/v03/serde.rs b/src/event/v03/serde.rs index 58f0f65..b813485 100644 --- a/src/event/v03/serde.rs +++ b/src/event/v03/serde.rs @@ -2,11 +2,12 @@ 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::de::IntoDeserializer; use serde::ser::SerializeMap; use serde::{Deserialize, Serializer}; use serde_value::Value; use std::collections::{BTreeMap, HashMap}; +use url::Url; pub(crate) struct EventDeserializer {} @@ -17,19 +18,14 @@ impl crate::event::serde::EventDeserializer for EventDeserializer { 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)?, + source: parse_field!(map, "source", String, E, Url::parse)?, datacontenttype: parse_optional_field!(map, "datacontenttype", String, E)?, - schemaurl: parse_optional_field!(map, "schemaurl", String, E)?, + schemaurl: parse_optional_field!(map, "schemaurl", String, E, Url::parse)?, 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()?, + time: parse_optional_field!(map, "time", String, E, |s| DateTime::parse_from_rfc3339( + s + ) + .map(DateTime::::from))?, })) } diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index cd3d92b..f7a429c 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -2,15 +2,16 @@ use crate::event::attributes::{AttributesConverter, DataAttributesWriter}; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; use chrono::{DateTime, Utc}; use hostname::get_hostname; +use url::Url; use uuid::Uuid; #[derive(PartialEq, Debug, Clone)] pub struct Attributes { pub(crate) id: String, pub(crate) ty: String, - pub(crate) source: String, + pub(crate) source: Url, pub(crate) datacontenttype: Option, - pub(crate) dataschema: Option, + pub(crate) dataschema: Option, pub(crate) subject: Option, pub(crate) time: Option>, } @@ -20,7 +21,7 @@ impl AttributesReader for Attributes { &self.id } - fn get_source(&self) -> &str { + fn get_source(&self) -> &Url { &self.source } @@ -33,24 +34,15 @@ impl AttributesReader for Attributes { } fn get_datacontenttype(&self) -> Option<&str> { - match self.datacontenttype.as_ref() { - Some(s) => Some(&s), - None => None, - } + self.datacontenttype.as_deref() } - fn get_dataschema(&self) -> Option<&str> { - match self.dataschema.as_ref() { - Some(s) => Some(&s), - None => None, - } + fn get_dataschema(&self) -> Option<&Url> { + self.dataschema.as_ref() } fn get_subject(&self) -> Option<&str> { - match self.subject.as_ref() { - Some(s) => Some(&s), - None => None, - } + self.subject.as_deref() } fn get_time(&self) -> Option<&DateTime> { @@ -63,7 +55,7 @@ impl AttributesWriter for Attributes { self.id = id.into() } - fn set_source(&mut self, source: impl Into) { + fn set_source(&mut self, source: impl Into) { self.source = source.into() } @@ -85,7 +77,7 @@ impl DataAttributesWriter for Attributes { self.datacontenttype = datacontenttype.map(Into::into) } - fn set_dataschema(&mut self, dataschema: Option>) { + fn set_dataschema(&mut self, dataschema: Option>) { self.dataschema = dataschema.map(Into::into) } } @@ -95,7 +87,14 @@ impl Default for Attributes { Attributes { id: Uuid::new_v4().to_string(), ty: "type".to_string(), - source: get_hostname().unwrap_or("http://localhost/".to_string()), + source: Url::parse( + format!( + "http://{}", + get_hostname().unwrap_or("localhost".to_string()) + ) + .as_ref(), + ) + .unwrap(), datacontenttype: None, dataschema: None, subject: None, diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 9b7f888..265f63b 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -2,6 +2,7 @@ use super::Attributes as AttributesV10; use crate::event::{Attributes, AttributesWriter, Data, Event, ExtensionValue}; use chrono::{DateTime, Utc}; use std::collections::HashMap; +use url::Url; pub struct EventBuilder { event: Event, @@ -33,7 +34,7 @@ impl EventBuilder { return self; } - pub fn source(mut self, source: impl Into) -> Self { + pub fn source(mut self, source: impl Into) -> Self { self.event.set_source(source); return self; } @@ -70,7 +71,7 @@ impl EventBuilder { pub fn data_with_schema( mut self, datacontenttype: impl Into, - dataschema: impl Into, + dataschema: impl Into, data: impl Into, ) -> Self { self.event @@ -91,31 +92,31 @@ mod tests { #[test] fn build_event() { let id = "aaa"; - let source = "http://localhost:8080"; + let source = Url::parse("http://localhost:8080").unwrap(); 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 schema = Url::parse("http://localhost:8080/schema").unwrap(); let data = serde_json::json!({ "hello": "world" }); let event = EventBuilder::new() .id(id) - .source(source) + .source(source.clone()) .ty(ty) .subject(subject) .time(time) .extension(extension_name, extension_value) - .data_with_schema(content_type, schema, data.clone()) + .data_with_schema(content_type, schema.clone(), data.clone()) .build(); assert_eq!(SpecVersion::V10, event.get_specversion()); assert_eq!(id, event.get_id()); - assert_eq!(source, event.get_source()); + assert_eq!(source, event.get_source().clone()); assert_eq!(ty, event.get_type()); assert_eq!(subject, event.get_subject().unwrap()); assert_eq!(time, event.get_time().unwrap().clone()); @@ -124,7 +125,7 @@ mod tests { event.get_extension(extension_name).unwrap().clone() ); assert_eq!(content_type, event.get_datacontenttype().unwrap()); - assert_eq!(schema, event.get_dataschema().unwrap()); + assert_eq!(schema, event.get_dataschema().unwrap().clone()); let event_data: serde_json::Value = event.try_get_data().unwrap().unwrap(); assert_eq!(data, event_data); diff --git a/src/event/v10/message.rs b/src/event/v10/message.rs index d0bc09c..0983891 100644 --- a/src/event/v10/message.rs +++ b/src/event/v10/message.rs @@ -42,9 +42,9 @@ impl crate::event::deserializer::AttributesSerializer for super::Attributes { match name { "id" => self.id = value.to_string(), "type" => self.ty = value.to_string(), - "source" => self.source = value.to_string(), + "source" => self.source = value.try_into()?, "datacontenttype" => self.datacontenttype = Some(value.to_string()), - "dataschema" => self.dataschema = Some(value.to_string()), + "dataschema" => self.dataschema = Some(value.try_into()?), "subject" => self.subject = Some(value.to_string()), "time" => self.time = Some(value.try_into()?), _ => { diff --git a/src/event/v10/serde.rs b/src/event/v10/serde.rs index fa21954..2b518eb 100644 --- a/src/event/v10/serde.rs +++ b/src/event/v10/serde.rs @@ -2,11 +2,12 @@ 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::de::IntoDeserializer; use serde::ser::SerializeMap; use serde::{Deserialize, Serializer}; use serde_value::Value; use std::collections::{BTreeMap, HashMap}; +use url::Url; pub(crate) struct EventDeserializer {} @@ -17,19 +18,14 @@ impl crate::event::serde::EventDeserializer for EventDeserializer { 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)?, + source: parse_field!(map, "source", String, E, Url::parse)?, datacontenttype: parse_optional_field!(map, "datacontenttype", String, E)?, - dataschema: parse_optional_field!(map, "dataschema", String, E)?, + dataschema: parse_optional_field!(map, "dataschema", String, E, Url::parse)?, 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()?, + time: parse_optional_field!(map, "time", String, E, |s| DateTime::parse_from_rfc3339( + s + ) + .map(DateTime::::from))?, })) } diff --git a/src/message/deserializer.rs b/src/message/deserializer.rs index 7362173..60e4a7a 100644 --- a/src/message/deserializer.rs +++ b/src/message/deserializer.rs @@ -8,9 +8,12 @@ use std::io::Read; pub enum Error { #[snafu(display("Unrecognized attribute name: {}", name))] UnrecognizedAttributeName { name: String }, - #[snafu(display("Error while decoding base64: {}", source))] + #[snafu(display("Error while parsing a time string: {}", source))] #[snafu(context(false))] ParseTimeError { source: chrono::ParseError }, + #[snafu(display("Error while parsing a url: {}", source))] + #[snafu(context(false))] + ParseUrlError { source: url::ParseError }, #[snafu(display("Error while decoding base64: {}", source))] #[snafu(context(false))] Base64DecodingError { source: base64::DecodeError }, diff --git a/src/message/types.rs b/src/message/types.rs index 9ee7e41..e09acea 100644 --- a/src/message/types.rs +++ b/src/message/types.rs @@ -1,14 +1,15 @@ use crate::event::ExtensionValue; use chrono::{DateTime, Utc}; use std::convert::TryInto; +use url::Url; pub enum MessageAttributeValue { Boolean(bool), Integer(i64), String(String), Binary(Vec), - Uri(String), - UriRef(String), + Uri(Url), + UriRef(Url), DateTime(DateTime), } @@ -25,6 +26,18 @@ impl TryInto> for MessageAttributeValue { } } +impl TryInto for MessageAttributeValue { + type Error = super::Error; + + fn try_into(self) -> Result { + match self { + MessageAttributeValue::Uri(u) => Ok(u), + MessageAttributeValue::UriRef(u) => Ok(u), + v => Ok(Url::parse(v.to_string().as_ref())?), + } + } +} + impl ToString for MessageAttributeValue { fn to_string(&self) -> String { match self { @@ -32,8 +45,8 @@ impl ToString for MessageAttributeValue { MessageAttributeValue::Integer(i) => i.to_string(), MessageAttributeValue::String(s) => s.clone(), MessageAttributeValue::Binary(v) => base64::encode(v), - MessageAttributeValue::Uri(s) => s.clone(), - MessageAttributeValue::UriRef(s) => s.clone(), + MessageAttributeValue::Uri(u) => u.to_string(), + MessageAttributeValue::UriRef(u) => u.to_string(), MessageAttributeValue::DateTime(d) => d.to_rfc3339(), } } diff --git a/tests/test_data/data.rs b/tests/test_data/data.rs index 087b20d..afd16ae 100644 --- a/tests/test_data/data.rs +++ b/tests/test_data/data.rs @@ -10,7 +10,7 @@ pub fn ty() -> String { } pub fn source() -> String { - "http://localhost".to_string() + "http://localhost/".to_string() } pub fn json_datacontenttype() -> String { diff --git a/tests/test_data/v03.rs b/tests/test_data/v03.rs index 00e5347..b1ff7a7 100644 --- a/tests/test_data/v03.rs +++ b/tests/test_data/v03.rs @@ -1,11 +1,13 @@ use super::*; use cloudevents::{Event, EventBuilder}; use serde_json::{json, Value}; +use std::convert::TryInto; +use url::Url; pub fn minimal() -> Event { EventBuilder::v03() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .build() } @@ -26,7 +28,7 @@ pub fn full_no_data() -> Event { EventBuilder::v03() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .subject(subject()) .time(time()) @@ -61,14 +63,18 @@ pub fn full_json_data() -> Event { EventBuilder::v03() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .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()) + .data_with_schema( + json_datacontenttype(), + Url::parse(dataschema().as_ref()).unwrap(), + json_data(), + ) .build() } @@ -122,7 +128,7 @@ pub fn full_xml_string_data() -> Event { EventBuilder::v03() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .subject(subject()) .time(time()) @@ -140,7 +146,7 @@ pub fn full_xml_binary_data() -> Event { EventBuilder::v03() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .subject(subject()) .time(time()) diff --git a/tests/test_data/v10.rs b/tests/test_data/v10.rs index f564c4d..d7b4646 100644 --- a/tests/test_data/v10.rs +++ b/tests/test_data/v10.rs @@ -1,11 +1,12 @@ use super::*; use cloudevents::{Event, EventBuilder}; use serde_json::{json, Value}; +use url::Url; pub fn minimal() -> Event { EventBuilder::v10() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .build() } @@ -26,7 +27,7 @@ pub fn full_no_data() -> Event { EventBuilder::v10() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .subject(subject()) .time(time()) @@ -61,14 +62,18 @@ pub fn full_json_data() -> Event { EventBuilder::v10() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .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()) + .data_with_schema( + json_datacontenttype(), + Url::parse(dataschema().as_ref()).unwrap(), + json_data(), + ) .build() } @@ -121,7 +126,7 @@ pub fn full_xml_string_data() -> Event { EventBuilder::v10() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .subject(subject()) .time(time()) @@ -139,7 +144,7 @@ pub fn full_xml_binary_data() -> Event { EventBuilder::v10() .id(id()) - .source(source()) + .source(Url::parse(source().as_ref()).unwrap()) .ty(ty()) .subject(subject()) .time(time())