1use super::Attributes as AttributesV03;
2use crate::event::{
3 Attributes, Data, Event, EventBuilderError, ExtensionValue, TryIntoTime, TryIntoUrl,
4 UriReference,
5};
6use crate::message::MessageAttributeValue;
7use chrono::{DateTime, Utc};
8use std::collections::HashMap;
9use std::convert::TryInto;
10use url::Url;
11
12#[derive(Clone, Debug)]
14pub struct EventBuilder {
15 id: Option<String>,
16 ty: Option<String>,
17 source: Option<UriReference>,
18 datacontenttype: Option<String>,
19 schemaurl: Option<Url>,
20 subject: Option<String>,
21 time: Option<DateTime<Utc>>,
22 data: Option<Data>,
23 extensions: HashMap<String, ExtensionValue>,
24 error: Option<EventBuilderError>,
25}
26
27impl EventBuilder {
28 pub fn id(mut self, id: impl Into<String>) -> Self {
29 self.id = Some(id.into());
30 self
31 }
32
33 pub fn source(mut self, source: impl Into<String>) -> Self {
34 let source = source.into();
35 if source.is_empty() {
36 self.error = Some(EventBuilderError::InvalidUriRefError {
37 attribute_name: "source",
38 });
39 } else {
40 self.source = Some(source);
41 }
42 self
43 }
44
45 pub fn ty(mut self, ty: impl Into<String>) -> Self {
46 self.ty = Some(ty.into());
47 self
48 }
49
50 pub fn subject(mut self, subject: impl Into<String>) -> Self {
51 self.subject = Some(subject.into());
52 self
53 }
54
55 pub fn time(mut self, time: impl TryIntoTime) -> Self {
56 match time.into_time() {
57 Ok(u) => self.time = Some(u),
58 Err(e) => {
59 self.error = Some(EventBuilderError::ParseTimeError {
60 attribute_name: "time",
61 source: e,
62 })
63 }
64 };
65 self
66 }
67
68 pub fn extension(
69 mut self,
70 extension_name: &str,
71 extension_value: impl Into<ExtensionValue>,
72 ) -> Self {
73 self.extensions
74 .insert(extension_name.to_owned(), extension_value.into());
75 self
76 }
77
78 pub(crate) fn data_without_content_type(mut self, data: impl Into<Data>) -> Self {
79 self.data = Some(data.into());
80 self
81 }
82
83 pub fn data(mut self, datacontenttype: impl Into<String>, data: impl Into<Data>) -> Self {
84 self.datacontenttype = Some(datacontenttype.into());
85 self.data = Some(data.into());
86 self
87 }
88
89 pub fn data_with_schema(
90 mut self,
91 datacontenttype: impl Into<String>,
92 schemaurl: impl TryIntoUrl,
93 data: impl Into<Data>,
94 ) -> Self {
95 self.datacontenttype = Some(datacontenttype.into());
96 match schemaurl.into_url() {
97 Ok(u) => self.schemaurl = Some(u),
98 Err(e) => {
99 self.error = Some(EventBuilderError::ParseUrlError {
100 attribute_name: "schemaurl",
101 source: e,
102 })
103 }
104 };
105 self.data = Some(data.into());
106 self
107 }
108}
109
110impl From<Event> for EventBuilder {
111 fn from(event: Event) -> Self {
112 let attributes = match event.attributes.into_v03() {
113 Attributes::V03(attr) => attr,
114 _ => unreachable!(),
117 };
118
119 EventBuilder {
120 id: Some(attributes.id),
121 ty: Some(attributes.ty),
122 source: Some(attributes.source),
123 datacontenttype: attributes.datacontenttype,
124 schemaurl: attributes.schemaurl,
125 subject: attributes.subject,
126 time: attributes.time,
127 data: event.data,
128 extensions: event.extensions,
129 error: None,
130 }
131 }
132}
133
134impl Default for EventBuilder {
135 fn default() -> Self {
136 Self::from(Event::default())
137 }
138}
139
140impl crate::event::builder::EventBuilder for EventBuilder {
141 fn new() -> Self {
142 EventBuilder {
143 id: None,
144 ty: None,
145 source: None,
146 datacontenttype: None,
147 schemaurl: None,
148 subject: None,
149 time: None,
150 data: None,
151 extensions: Default::default(),
152 error: None,
153 }
154 }
155
156 fn build(self) -> Result<Event, EventBuilderError> {
157 match self.error {
158 Some(e) => Err(e),
159 None => Ok(Event {
160 attributes: Attributes::V03(AttributesV03 {
161 id: self.id.ok_or(EventBuilderError::MissingRequiredAttribute {
162 attribute_name: "id",
163 })?,
164 ty: self.ty.ok_or(EventBuilderError::MissingRequiredAttribute {
165 attribute_name: "type",
166 })?,
167 source: self
168 .source
169 .ok_or(EventBuilderError::MissingRequiredAttribute {
170 attribute_name: "source",
171 })?,
172 datacontenttype: self.datacontenttype,
173 schemaurl: self.schemaurl,
174 subject: self.subject,
175 time: self.time,
176 }),
177 data: self.data,
178 extensions: self.extensions,
179 }),
180 }
181 }
182}
183
184impl crate::event::message::AttributesSerializer for EventBuilder {
185 fn serialize_attribute(
186 &mut self,
187 name: &str,
188 value: MessageAttributeValue,
189 ) -> crate::message::Result<()> {
190 match name {
191 "id" => self.id = Some(value.to_string()),
192 "type" => self.ty = Some(value.to_string()),
193 "source" => self.source = Some(value.to_string()),
194 "datacontenttype" => self.datacontenttype = Some(value.to_string()),
195 "schemaurl" => self.schemaurl = Some(value.try_into()?),
196 "subject" => self.subject = Some(value.to_string()),
197 "time" => self.time = Some(value.try_into()?),
198 _ => {
199 return Err(crate::message::Error::UnknownAttribute {
200 name: name.to_string(),
201 })
202 }
203 };
204 Ok(())
205 }
206}
207
208#[cfg(test)]
209mod tests {
210
211 use crate::assert_match_pattern;
212 use chrono::{DateTime, Utc};
213
214 use crate::event::{
215 AttributesReader, EventBuilder, EventBuilderError, ExtensionValue, SpecVersion,
216 };
217 use crate::EventBuilderV03;
218 use std::convert::TryInto;
219 use url::Url;
220
221 #[test]
222 fn build_event() {
223 let id = "aaa";
224 let source = "http://localhost:8080";
225 let ty = "bbb";
226 let subject = "francesco";
227 let time: DateTime<Utc> = Utc::now();
228 let extension_name = "ext";
229 let extension_value = 10i64;
230 let content_type = "application/json";
231 let schema = Url::parse("http://localhost:8080/schema").unwrap();
232 let data = serde_json::json!({
233 "hello": "world"
234 });
235
236 let mut event = EventBuilderV03::new()
237 .id(id)
238 .source(source.to_string())
239 .ty(ty)
240 .subject(subject)
241 .time(time)
242 .extension(extension_name, extension_value)
243 .data_with_schema(content_type, schema.clone(), data.clone())
244 .build()
245 .unwrap();
246
247 assert_eq!(SpecVersion::V03, event.specversion());
248 assert_eq!(id, event.id());
249 assert_eq!(source, event.source().clone());
250 assert_eq!(ty, event.ty());
251 assert_eq!(subject, event.subject().unwrap());
252 assert_eq!(time, event.time().unwrap().clone());
253 assert_eq!(
254 ExtensionValue::from(extension_value),
255 event.extension(extension_name).unwrap().clone()
256 );
257 assert_eq!(content_type, event.datacontenttype().unwrap());
258 assert_eq!(schema, event.dataschema().unwrap().clone());
259
260 let event_data: serde_json::Value = event.take_data().2.unwrap().try_into().unwrap();
261 assert_eq!(data, event_data);
262 }
263
264 #[test]
265 fn source_valid_relative_url() {
266 let res = EventBuilderV03::new()
267 .id("id1")
268 .source("/source") .ty("type")
270 .build();
271 assert_match_pattern!(res, Ok(_));
272 }
273
274 #[test]
275 fn build_missing_id() {
276 let res = EventBuilderV03::new()
277 .source("http://localhost:8080")
278 .build();
279 assert_match_pattern!(
280 res,
281 Err(EventBuilderError::MissingRequiredAttribute {
282 attribute_name: "id"
283 })
284 );
285 }
286
287 #[test]
288 fn source_invalid_url() {
289 let res = EventBuilderV03::new().source("").build();
290 assert_match_pattern!(
291 res,
292 Err(EventBuilderError::InvalidUriRefError {
293 attribute_name: "source",
294 })
295 );
296 }
297
298 #[test]
299 fn default_builds() {
300 let res = EventBuilderV03::default().build();
301 assert_match_pattern!(res, Ok(_));
302 }
303}