cloudevents/event/v10/
builder.rs

1use super::Attributes as AttributesV10;
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/// Builder to create a CloudEvent V1.0
13#[derive(Clone, Debug)]
14pub struct EventBuilder {
15    id: Option<String>,
16    ty: Option<String>,
17    source: Option<UriReference>,
18    datacontenttype: Option<String>,
19    dataschema: 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.dataschema = Some(u),
98            Err(e) => {
99                self.error = Some(EventBuilderError::ParseUrlError {
100                    attribute_name: "dataschema",
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_v10() {
113            Attributes::V10(attr) => attr,
114            // This branch is unreachable because into_v10() returns
115            // always a Attributes::V10
116            _ => 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            dataschema: attributes.dataschema,
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            dataschema: 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::V10(AttributesV10 {
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                    dataschema: self.dataschema,
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            "dataschema" => self.dataschema = 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::EventBuilderV10;
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 = EventBuilderV10::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::V10, 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 = EventBuilderV10::new()
267            .id("id1")
268            .source("/source") // relative URL
269            .ty("type")
270            .build();
271        assert_match_pattern!(res, Ok(_));
272    }
273
274    #[test]
275    fn build_missing_id() {
276        let res = EventBuilderV10::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 = EventBuilderV10::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 = EventBuilderV10::default().build();
301        assert_match_pattern!(res, Ok(_));
302    }
303}