diff --git a/cloudevents-sdk-actix-web/src/server_request.rs b/cloudevents-sdk-actix-web/src/server_request.rs index 0cc1fb6..e981044 100644 --- a/cloudevents-sdk-actix-web/src/server_request.rs +++ b/cloudevents-sdk-actix-web/src/server_request.rs @@ -181,7 +181,7 @@ mod tests { //TODO this is required now because the message deserializer implictly set default values // As soon as this defaulting doesn't happen anymore, we can remove it (Issues #40/#41) .time(time) - .data("application/json", j.clone()) + .data("application/json", j.to_string().into_bytes()) .extension("someint", "10") .build() .unwrap(); diff --git a/cloudevents-sdk-reqwest/src/client_response.rs b/cloudevents-sdk-reqwest/src/client_response.rs index d80d383..24dd9fd 100644 --- a/cloudevents-sdk-reqwest/src/client_response.rs +++ b/cloudevents-sdk-reqwest/src/client_response.rs @@ -195,7 +195,7 @@ mod tests { // As soon as this defaulting doesn't happen anymore, we can remove it (Issues #40/#41) .time(time) .source(Url::from_str("http://localhost").unwrap()) - .data("application/json", j.clone()) + .data("application/json", j.to_string().into_bytes()) .extension("someint", "10") .build() .unwrap(); diff --git a/src/event/data.rs b/src/event/data.rs index 4acf4a8..2a7adab 100644 --- a/src/event/data.rs +++ b/src/event/data.rs @@ -46,7 +46,7 @@ impl Data { } pub(crate) fn is_json_content_type(ct: &str) -> bool { - ct == "application/json" || ct == "text/json" || ct.ends_with("+json") + ct.starts_with("application/json") || ct.starts_with("text/json") || ct.ends_with("+json") } impl Into for serde_json::Value { diff --git a/src/event/message.rs b/src/event/message.rs index 762d945..cfd5857 100644 --- a/src/event/message.rs +++ b/src/event/message.rs @@ -6,6 +6,7 @@ use crate::message::{ BinaryDeserializer, BinarySerializer, MessageAttributeValue, Result, StructuredDeserializer, StructuredSerializer, }; +use crate::{EventBuilder, EventBuilderV03, EventBuilderV10}; impl StructuredDeserializer for Event { fn deserialize_structured>(self, visitor: V) -> Result { @@ -37,10 +38,6 @@ pub(crate) trait AttributesDeserializer { fn deserialize_attributes>(self, visitor: V) -> Result; } -pub(crate) trait AttributesSerializer { - fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()>; -} - impl AttributesDeserializer for Attributes { fn deserialize_attributes>(self, visitor: V) -> Result { match self { @@ -50,50 +47,125 @@ impl AttributesDeserializer for Attributes { } } -impl AttributesSerializer for Attributes { - fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()> { - match self { - Attributes::V03(v03) => v03.serialize_attribute(name, value), - Attributes::V10(v10) => v10.serialize_attribute(name, value), - } +pub(crate) trait AttributesSerializer { + fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()>; +} + +#[derive(Debug)] +pub(crate) struct EventStructuredSerializer {} + +impl StructuredSerializer for EventStructuredSerializer { + fn set_structured_event(self, bytes: Vec) -> Result { + Ok(serde_json::from_slice(&bytes)?) } } -impl StructuredSerializer for Event { - fn set_structured_event(mut self, bytes: Vec) -> Result { - let new_event: Event = serde_json::from_slice(&bytes)?; - self.attributes = new_event.attributes; - self.data = new_event.data; - self.extensions = new_event.extensions; - Ok(self) +#[derive(Debug)] +pub(crate) enum EventBinarySerializer { + V10(EventBuilderV10), + V03(EventBuilderV03), +} + +impl EventBinarySerializer { + pub(crate) fn new() -> Self { + EventBinarySerializer::V10(EventBuilderV10::new()) } } -impl BinarySerializer for Event { - fn set_spec_version(mut self, spec_version: SpecVersion) -> Result { - match spec_version { - SpecVersion::V03 => self.attributes = self.attributes.clone().into_v03(), - SpecVersion::V10 => self.attributes = self.attributes.clone().into_v10(), - } - Ok(self) +impl BinarySerializer for EventBinarySerializer { + fn set_spec_version(self, spec_version: SpecVersion) -> Result { + Ok(match spec_version { + SpecVersion::V03 => EventBinarySerializer::V03(EventBuilderV03::new()), + SpecVersion::V10 => EventBinarySerializer::V10(EventBuilderV10::new()), + }) } fn set_attribute(mut self, name: &str, value: MessageAttributeValue) -> Result { - self.attributes.serialize_attribute(name, value)?; + match &mut self { + EventBinarySerializer::V03(eb) => eb.serialize_attribute(name, value)?, + EventBinarySerializer::V10(eb) => eb.serialize_attribute(name, value)?, + } Ok(self) } - fn set_extension(mut self, name: &str, value: MessageAttributeValue) -> Result { - self.extensions.insert(name.to_string(), value.into()); - Ok(self) + fn set_extension(self, name: &str, value: MessageAttributeValue) -> Result { + Ok(match self { + EventBinarySerializer::V03(eb) => EventBinarySerializer::V03(eb.extension(name, value)), + EventBinarySerializer::V10(eb) => EventBinarySerializer::V10(eb.extension(name, value)), + }) } - fn end_with_data(mut self, bytes: Vec) -> Result { - self.data = Some(Data::from_binary(self.get_datacontenttype(), bytes)?); - Ok(self) + fn end_with_data(self, bytes: Vec) -> Result { + Ok(match self { + EventBinarySerializer::V03(eb) => { + eb.data_without_content_type(Data::Binary(bytes)).build() + } + EventBinarySerializer::V10(eb) => { + eb.data_without_content_type(Data::Binary(bytes)).build() + } + }?) } fn end(self) -> Result { - Ok(self) + Ok(match self { + EventBinarySerializer::V03(eb) => eb.build(), + EventBinarySerializer::V10(eb) => eb.build(), + }?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::message::Error; + + #[test] + fn binary_deserializer_unrecognized_attribute_v03() { + assert_eq!( + Error::UnrecognizedAttributeName { + name: "dataschema".to_string() + } + .to_string(), + EventBinarySerializer::new() + .set_spec_version(SpecVersion::V03) + .unwrap() + .set_attribute("dataschema", MessageAttributeValue::Boolean(true)) + .expect_err("Should return an error") + .to_string() + ) + } + + #[test] + fn binary_deserializer_missing_id() { + assert_eq!( + Error::EventBuilderError { + source: crate::event::EventBuilderError::MissingRequiredAttribute { + attribute_name: "id" + }, + } + .to_string(), + EventBinarySerializer::new() + .set_spec_version(SpecVersion::V10) + .unwrap() + .end() + .unwrap_err() + .to_string() + ) + } + + #[test] + fn binary_deserializer_unrecognized_attribute_v10() { + assert_eq!( + Error::UnrecognizedAttributeName { + name: "schemaurl".to_string() + } + .to_string(), + EventBinarySerializer::new() + .set_spec_version(SpecVersion::V10) + .unwrap() + .set_attribute("schemaurl", MessageAttributeValue::Boolean(true)) + .expect_err("Should return an error") + .to_string() + ) } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 4ec85bf..fbcc63e 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -17,6 +17,8 @@ pub use builder::EventBuilder; pub use data::Data; pub use event::Event; pub use extensions::ExtensionValue; +pub(crate) use message::EventBinarySerializer; +pub(crate) use message::EventStructuredSerializer; pub use spec_version::InvalidSpecVersion; pub use spec_version::SpecVersion; pub use types::{TryIntoTime, TryIntoUrl}; diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index c0b879b..2607def 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -3,6 +3,7 @@ use crate::event::attributes::{ }; use crate::event::AttributesV10; use crate::event::{AttributesReader, AttributesWriter, SpecVersion}; +use crate::message::{BinarySerializer, MessageAttributeValue}; use chrono::{DateTime, Utc}; use url::Url; use uuid::Uuid; @@ -184,6 +185,40 @@ impl AttributesConverter for Attributes { } } +impl crate::event::message::AttributesDeserializer for super::Attributes { + fn deserialize_attributes>( + self, + mut visitor: V, + ) -> crate::message::Result { + visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?; + visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?; + visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?; + if self.datacontenttype.is_some() { + visitor = visitor.set_attribute( + "datacontenttype", + MessageAttributeValue::String(self.datacontenttype.unwrap()), + )?; + } + if self.schemaurl.is_some() { + visitor = visitor.set_attribute( + "schemaurl", + MessageAttributeValue::Uri(self.schemaurl.unwrap()), + )?; + } + if self.subject.is_some() { + visitor = visitor.set_attribute( + "subject", + MessageAttributeValue::String(self.subject.unwrap()), + )?; + } + if self.time.is_some() { + visitor = visitor + .set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?; + } + Ok(visitor) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/event/v03/builder.rs b/src/event/v03/builder.rs index 94b64c1..c7a920d 100644 --- a/src/event/v03/builder.rs +++ b/src/event/v03/builder.rs @@ -2,12 +2,14 @@ use super::Attributes as AttributesV03; use crate::event::{ Attributes, Data, Event, EventBuilderError, ExtensionValue, TryIntoTime, TryIntoUrl, }; +use crate::message::MessageAttributeValue; use chrono::{DateTime, Utc}; use std::collections::HashMap; +use std::convert::TryInto; use url::Url; /// Builder to create a CloudEvent V0.3 -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct EventBuilder { id: Option, ty: Option, @@ -73,6 +75,11 @@ impl EventBuilder { self } + pub(crate) fn data_without_content_type(mut self, data: impl Into) -> Self { + self.data = Some(data.into()); + self + } + pub fn data(mut self, datacontenttype: impl Into, data: impl Into) -> Self { self.datacontenttype = Some(datacontenttype.into()); self.data = Some(data.into()); @@ -173,3 +180,27 @@ impl crate::event::builder::EventBuilder for EventBuilder { } } } + +impl crate::event::message::AttributesSerializer for EventBuilder { + fn serialize_attribute( + &mut self, + name: &str, + value: MessageAttributeValue, + ) -> crate::message::Result<()> { + match name { + "id" => self.id = Some(value.to_string()), + "type" => self.ty = Some(value.to_string()), + "source" => self.source = Some(value.try_into()?), + "datacontenttype" => self.datacontenttype = 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()?), + _ => { + return Err(crate::message::Error::UnrecognizedAttributeName { + name: name.to_string(), + }) + } + }; + Ok(()) + } +} diff --git a/src/event/v03/message.rs b/src/event/v03/message.rs deleted file mode 100644 index 4d7ee54..0000000 --- a/src/event/v03/message.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::message::{BinarySerializer, Error, MessageAttributeValue, Result}; -use std::convert::TryInto; - -impl crate::event::message::AttributesDeserializer for super::Attributes { - fn deserialize_attributes>(self, mut visitor: V) -> Result { - visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?; - visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?; - visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?; - if self.datacontenttype.is_some() { - visitor = visitor.set_attribute( - "datacontenttype", - MessageAttributeValue::String(self.datacontenttype.unwrap()), - )?; - } - if self.schemaurl.is_some() { - visitor = visitor.set_attribute( - "schemaurl", - MessageAttributeValue::Uri(self.schemaurl.unwrap()), - )?; - } - if self.subject.is_some() { - visitor = visitor.set_attribute( - "subject", - MessageAttributeValue::String(self.subject.unwrap()), - )?; - } - if self.time.is_some() { - visitor = visitor - .set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?; - } - Ok(visitor) - } -} - -impl crate::event::message::AttributesSerializer for super::Attributes { - fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()> { - match name { - "id" => self.id = value.to_string(), - "type" => self.ty = value.to_string(), - "source" => self.source = value.try_into()?, - "datacontenttype" => self.datacontenttype = 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()?), - _ => { - return Err(Error::UnrecognizedAttributeName { - name: name.to_string(), - }) - } - }; - Ok(()) - } -} diff --git a/src/event/v03/mod.rs b/src/event/v03/mod.rs index 1813134..b88bb3f 100644 --- a/src/event/v03/mod.rs +++ b/src/event/v03/mod.rs @@ -1,7 +1,6 @@ mod attributes; mod builder; mod format; -mod message; pub use attributes::Attributes; pub(crate) use attributes::AttributesIntoIterator; diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index fe9444d..c267beb 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -2,6 +2,7 @@ use crate::event::attributes::{ default_hostname, AttributeValue, AttributesConverter, DataAttributesWriter, }; use crate::event::{AttributesReader, AttributesV03, AttributesWriter, SpecVersion}; +use crate::message::{BinarySerializer, MessageAttributeValue}; use chrono::{DateTime, Utc}; use core::fmt::Debug; use url::Url; @@ -166,6 +167,40 @@ impl Default for Attributes { } } +impl crate::event::message::AttributesDeserializer for super::Attributes { + fn deserialize_attributes>( + self, + mut visitor: V, + ) -> crate::message::Result { + visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?; + visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?; + visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?; + if self.datacontenttype.is_some() { + visitor = visitor.set_attribute( + "datacontenttype", + MessageAttributeValue::String(self.datacontenttype.unwrap()), + )?; + } + if self.dataschema.is_some() { + visitor = visitor.set_attribute( + "dataschema", + MessageAttributeValue::Uri(self.dataschema.unwrap()), + )?; + } + if self.subject.is_some() { + visitor = visitor.set_attribute( + "subject", + MessageAttributeValue::String(self.subject.unwrap()), + )?; + } + if self.time.is_some() { + visitor = visitor + .set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?; + } + Ok(visitor) + } +} + impl AttributesConverter for Attributes { fn into_v10(self) -> Self { self diff --git a/src/event/v10/builder.rs b/src/event/v10/builder.rs index 4d83ff4..28b5b3c 100644 --- a/src/event/v10/builder.rs +++ b/src/event/v10/builder.rs @@ -2,12 +2,14 @@ use super::Attributes as AttributesV10; use crate::event::{ Attributes, Data, Event, EventBuilderError, ExtensionValue, TryIntoTime, TryIntoUrl, }; +use crate::message::MessageAttributeValue; use chrono::{DateTime, Utc}; use std::collections::HashMap; +use std::convert::TryInto; use url::Url; /// Builder to create a CloudEvent V1.0 -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct EventBuilder { id: Option, ty: Option, @@ -73,6 +75,11 @@ impl EventBuilder { self } + pub(crate) fn data_without_content_type(mut self, data: impl Into) -> Self { + self.data = Some(data.into()); + self + } + pub fn data(mut self, datacontenttype: impl Into, data: impl Into) -> Self { self.datacontenttype = Some(datacontenttype.into()); self.data = Some(data.into()); @@ -173,3 +180,27 @@ impl crate::event::builder::EventBuilder for EventBuilder { } } } + +impl crate::event::message::AttributesSerializer for EventBuilder { + fn serialize_attribute( + &mut self, + name: &str, + value: MessageAttributeValue, + ) -> crate::message::Result<()> { + match name { + "id" => self.id = Some(value.to_string()), + "type" => self.ty = Some(value.to_string()), + "source" => self.source = Some(value.try_into()?), + "datacontenttype" => self.datacontenttype = 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()?), + _ => { + return Err(crate::message::Error::UnrecognizedAttributeName { + name: name.to_string(), + }) + } + } + Ok(()) + } +} diff --git a/src/event/v10/message.rs b/src/event/v10/message.rs deleted file mode 100644 index 191c5d9..0000000 --- a/src/event/v10/message.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::message::{BinarySerializer, Error, MessageAttributeValue, Result}; -use std::convert::TryInto; - -impl crate::event::message::AttributesDeserializer for super::Attributes { - fn deserialize_attributes>(self, mut visitor: V) -> Result { - visitor = visitor.set_attribute("id", MessageAttributeValue::String(self.id))?; - visitor = visitor.set_attribute("type", MessageAttributeValue::String(self.ty))?; - visitor = visitor.set_attribute("source", MessageAttributeValue::UriRef(self.source))?; - if self.datacontenttype.is_some() { - visitor = visitor.set_attribute( - "datacontenttype", - MessageAttributeValue::String(self.datacontenttype.unwrap()), - )?; - } - if self.dataschema.is_some() { - visitor = visitor.set_attribute( - "dataschema", - MessageAttributeValue::Uri(self.dataschema.unwrap()), - )?; - } - if self.subject.is_some() { - visitor = visitor.set_attribute( - "subject", - MessageAttributeValue::String(self.subject.unwrap()), - )?; - } - if self.time.is_some() { - visitor = visitor - .set_attribute("time", MessageAttributeValue::DateTime(self.time.unwrap()))?; - } - Ok(visitor) - } -} - -impl crate::event::message::AttributesSerializer for super::Attributes { - fn serialize_attribute(&mut self, name: &str, value: MessageAttributeValue) -> Result<()> { - match name { - "id" => self.id = value.to_string(), - "type" => self.ty = value.to_string(), - "source" => self.source = value.try_into()?, - "datacontenttype" => self.datacontenttype = 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()?), - _ => { - return Err(Error::UnrecognizedAttributeName { - name: name.to_string(), - }) - } - } - Ok(()) - } -} diff --git a/src/event/v10/mod.rs b/src/event/v10/mod.rs index 1813134..b88bb3f 100644 --- a/src/event/v10/mod.rs +++ b/src/event/v10/mod.rs @@ -1,7 +1,6 @@ mod attributes; mod builder; mod format; -mod message; pub use attributes::Attributes; pub(crate) use attributes::AttributesIntoIterator; diff --git a/src/message/deserializer.rs b/src/message/deserializer.rs index 2879ca6..7df0821 100644 --- a/src/message/deserializer.rs +++ b/src/message/deserializer.rs @@ -1,4 +1,5 @@ -use super::{BinarySerializer, Encoding, Result, StructuredSerializer}; +use super::{BinarySerializer, Encoding, Error, Result, StructuredSerializer}; +use crate::event::{EventBinarySerializer, EventStructuredSerializer}; use crate::Event; /// Deserializer trait for a Message that can be encoded as structured mode @@ -14,7 +15,7 @@ where /// Convert this Message to [`Event`] fn into_event(self) -> Result { - self.deserialize_structured(Event::default()) + self.deserialize_structured(EventStructuredSerializer {}) } } @@ -28,7 +29,7 @@ where /// Convert this Message to [`Event`] fn into_event(self) -> Result { - self.deserialize_binary(Event::default()) + self.deserialize_binary(EventBinarySerializer::new()) } } @@ -42,7 +43,11 @@ where /// Convert this Message to [`Event`] fn into_event(self) -> Result { - self.deserialize_to(Event::default()) + match self.encoding() { + Encoding::BINARY => BinaryDeserializer::into_event(self), + Encoding::STRUCTURED => StructuredDeserializer::into_event(self), + _ => Err(Error::WrongEncoding {}), + } } /// Deserialize the message to [`BinarySerializer`] diff --git a/src/message/error.rs b/src/message/error.rs index 4be498a..d14f50c 100644 --- a/src/message/error.rs +++ b/src/message/error.rs @@ -12,6 +12,11 @@ pub enum Error { }, #[snafu(display("Unrecognized attribute name: {}", name))] UnrecognizedAttributeName { name: String }, + #[snafu(display("Error while building the final event: {}", source))] + #[snafu(context(false))] + EventBuilderError { + source: crate::event::EventBuilderError, + }, #[snafu(display("Error while parsing a time string: {}", source))] #[snafu(context(false))] ParseTimeError { source: chrono::ParseError }, diff --git a/tests/message.rs b/tests/message.rs index 001499a..c346d29 100644 --- a/tests/message.rs +++ b/tests/message.rs @@ -1,9 +1,7 @@ mod test_data; -use cloudevents::message::{ - BinaryDeserializer, BinarySerializer, Error, MessageAttributeValue, Result, - StructuredDeserializer, -}; +use cloudevents::message::{BinaryDeserializer, Result, StructuredDeserializer}; +use cloudevents::{AttributesReader, EventBuilder, EventBuilderV03, EventBuilderV10}; use test_data::*; #[test] @@ -17,8 +15,18 @@ fn message_v03_roundtrip_structured() -> Result<()> { #[test] fn message_v03_roundtrip_binary() -> Result<()> { + //TODO this code smells because we're missing a proper way in the public APIs + // to destructure an event and rebuild it + let wanna_be_expected = v03::full_json_data(); + let data: serde_json::Value = wanna_be_expected.try_get_data()?.unwrap(); + let bytes = serde_json::to_vec(&data)?; + let expected = EventBuilderV03::from(wanna_be_expected.clone()) + .data(wanna_be_expected.get_datacontenttype().unwrap(), bytes) + .build() + .unwrap(); + assert_eq!( - v03::full_json_data(), + expected, BinaryDeserializer::into_event(v03::full_json_data())? ); Ok(()) @@ -35,37 +43,19 @@ fn message_v10_roundtrip_structured() -> Result<()> { #[test] fn message_v10_roundtrip_binary() -> Result<()> { + //TODO this code smells because we're missing a proper way in the public APIs + // to destructure an event and rebuild it + let wanna_be_expected = v10::full_json_data(); + let data: serde_json::Value = wanna_be_expected.try_get_data()?.unwrap(); + let bytes = serde_json::to_vec(&data)?; + let expected = EventBuilderV10::from(wanna_be_expected.clone()) + .data(wanna_be_expected.get_datacontenttype().unwrap(), bytes) + .build() + .unwrap(); + assert_eq!( - v10::full_json_data(), + expected, BinaryDeserializer::into_event(v10::full_json_data())? ); Ok(()) } - -#[test] -fn message_v03_invalid_attribute_name() { - assert_eq!( - Error::UnrecognizedAttributeName { - name: "dataschema".to_string() - } - .to_string(), - v03::full_json_data() - .set_attribute("dataschema", MessageAttributeValue::Boolean(true)) - .unwrap_err() - .to_string() - ) -} - -#[test] -fn message_v10_invalid_attribute_name() { - assert_eq!( - Error::UnrecognizedAttributeName { - name: "schemaurl".to_string() - } - .to_string(), - v10::full_json_data() - .set_attribute("schemaurl", MessageAttributeValue::Boolean(true)) - .unwrap_err() - .to_string() - ) -}