cloudevents/event/
builder.rs

1use super::Event;
2use snafu::Snafu;
3
4/// Trait to implement a builder for [`Event`]:
5/// ```
6/// use cloudevents::event::{EventBuilderV10, EventBuilder};
7/// use chrono::Utc;
8/// use url::Url;
9///
10/// let event = EventBuilderV10::new()
11///     .id("my_event.my_application")
12///     .source("http://localhost:8080")
13///     .ty("example.demo")
14///     .time(Utc::now())
15///     .build()
16///     .unwrap();
17/// ```
18///
19/// You can create an [`EventBuilder`] starting from an existing [`Event`] using the [`From`] trait.
20/// You can create a default [`EventBuilder`] setting default values for some attributes.
21pub trait EventBuilder
22where
23    Self: Clone + Sized + From<Event> + Default,
24{
25    /// Create a new empty builder
26    fn new() -> Self;
27
28    /// Build [`Event`]
29    fn build(self) -> Result<Event, Error>;
30}
31
32/// Represents an error during build process
33#[derive(Debug, Snafu, Clone)]
34pub enum Error {
35    #[snafu(display("Missing required attribute {}", attribute_name))]
36    MissingRequiredAttribute { attribute_name: &'static str },
37    #[snafu(display(
38        "Error while setting attribute '{}' with timestamp type: {}",
39        attribute_name,
40        source
41    ))]
42    ParseTimeError {
43        attribute_name: &'static str,
44        source: chrono::ParseError,
45    },
46    #[snafu(display(
47        "Error while setting attribute '{}' with uri type: {}",
48        attribute_name,
49        source
50    ))]
51    ParseUrlError {
52        attribute_name: &'static str,
53        source: url::ParseError,
54    },
55    #[snafu(display(
56        "Invalid value setting attribute '{}' with uriref type",
57        attribute_name,
58    ))]
59    InvalidUriRefError { attribute_name: &'static str },
60}
61
62#[cfg(test)]
63mod tests {
64    use crate::test::fixtures;
65    use crate::Event;
66    use crate::EventBuilder;
67    use crate::EventBuilderV03;
68    use crate::EventBuilderV10;
69    use claims::*;
70    use rstest::rstest;
71    use serde_json::{json, Value};
72    use serde_yaml;
73
74    /// Test conversions
75
76    #[test]
77    fn v10_to_v03() {
78        let in_event = fixtures::v10::full_json_data();
79        let out_event = EventBuilderV03::from(in_event).build().unwrap();
80        assert_eq!(fixtures::v03::full_json_data(), out_event)
81    }
82
83    #[test]
84    fn v03_to_v10() {
85        let in_event = fixtures::v03::full_json_data();
86        let out_event = EventBuilderV10::from(in_event).build().unwrap();
87        assert_eq!(fixtures::v10::full_json_data(), out_event)
88    }
89
90    /// Test YAML
91    /// This test checks if the usage of serde_json::Value makes the Deserialize implementation incompatible with
92    /// other Deserializers
93    #[test]
94    fn deserialize_yaml_should_succeed() {
95        let input = r#"
96    id: aaa
97    type: bbb
98    source: http://localhost
99    datacontenttype: application/json
100    data: true
101    specversion: "1.0"
102    "#;
103
104        let expected = EventBuilderV10::new()
105            .id("aaa")
106            .ty("bbb")
107            .source("http://localhost")
108            .data("application/json", serde_json::Value::Bool(true))
109            .build()
110            .unwrap();
111
112        let deserialize_result: Result<Event, serde_yaml::Error> = serde_yaml::from_str(input);
113        assert_ok!(&deserialize_result);
114        let deserialized = deserialize_result.unwrap();
115        assert_eq!(deserialized, expected)
116    }
117
118    /// Test Json
119    /// This test is a parametrized test that uses data from tests/test_data
120    #[rstest(
121        in_event,
122        out_json,
123        case::minimal_v03(fixtures::v03::minimal(), fixtures::v03::minimal_json()),
124        case::full_v03_no_data(fixtures::v03::full_no_data(), fixtures::v03::full_no_data_json()),
125        case::full_v03_with_json_data(
126            fixtures::v03::full_json_data(),
127            fixtures::v03::full_json_data_json()
128        ),
129        case::full_v03_with_xml_string_data(
130            fixtures::v03::full_xml_string_data(),
131            fixtures::v03::full_xml_string_data_json()
132        ),
133        case::full_v03_with_xml_base64_data(
134            fixtures::v03::full_xml_binary_data(),
135            fixtures::v03::full_xml_base64_data_json()
136        ),
137        case::minimal_v10(fixtures::v10::minimal(), fixtures::v10::minimal_json()),
138        case::full_v10_no_data(fixtures::v10::full_no_data(), fixtures::v10::full_no_data_json()),
139        case::full_v10_with_json_data(
140            fixtures::v10::full_json_data(),
141            fixtures::v10::full_json_data_json()
142        ),
143        case::full_v10_with_xml_string_data(
144            fixtures::v10::full_xml_string_data(),
145            fixtures::v10::full_xml_string_data_json()
146        ),
147        case::full_v10_with_xml_base64_data(
148            fixtures::v10::full_xml_binary_data(),
149            fixtures::v10::full_xml_base64_data_json()
150        )
151    )]
152    fn serialize_should_succeed(in_event: Event, out_json: Value) {
153        // Event -> serde_json::Value
154        let serialize_result = serde_json::to_value(in_event.clone());
155        assert_ok!(&serialize_result);
156        let actual_json = serialize_result.unwrap();
157        assert_eq!(&actual_json, &out_json);
158
159        // serde_json::Value -> String
160        let actual_json_serialized = actual_json.to_string();
161        assert_eq!(actual_json_serialized, out_json.to_string());
162
163        // String -> Event
164        let deserialize_result: Result<Event, serde_json::Error> =
165            serde_json::from_str(&actual_json_serialized);
166        assert_ok!(&deserialize_result);
167        let deserialize_json = deserialize_result.unwrap();
168        assert_eq!(deserialize_json, in_event)
169    }
170
171    /// This test is a parametrized test that uses data from tests/test_data
172    #[rstest(
173        in_json,
174        out_event,
175        case::minimal_v03(fixtures::v03::minimal_json(), fixtures::v03::minimal()),
176        case::full_v03_no_data(fixtures::v03::full_no_data_json(), fixtures::v03::full_no_data()),
177        case::full_v03_with_json_data(
178            fixtures::v03::full_json_data_json(),
179            fixtures::v03::full_json_data()
180        ),
181        case::full_v03_with_json_base64_data(
182            fixtures::v03::full_json_base64_data_json(),
183            fixtures::v03::full_json_data()
184        ),
185        case::full_v03_with_xml_string_data(
186            fixtures::v03::full_xml_string_data_json(),
187            fixtures::v03::full_xml_string_data()
188        ),
189        case::full_v03_with_xml_base64_data(
190            fixtures::v03::full_xml_base64_data_json(),
191            fixtures::v03::full_xml_binary_data()
192        ),
193        case::minimal_v10(fixtures::v10::minimal_json(), fixtures::v10::minimal()),
194        case::full_v10_no_data(fixtures::v10::full_no_data_json(), fixtures::v10::full_no_data()),
195        case::full_v10_with_json_data(
196            fixtures::v10::full_json_data_json(),
197            fixtures::v10::full_json_data()
198        ),
199        case::full_v10_with_json_base64_data(
200            fixtures::v10::full_json_base64_data_json(),
201            fixtures::v10::full_json_data()
202        ),
203        case::full_v10_with_non_json_base64_data(
204            fixtures::v10::full_non_json_base64_data(),
205            fixtures::v10::full_non_json_data()
206        ),
207        case::full_v10_with_xml_string_data(
208            fixtures::v10::full_xml_string_data_json(),
209            fixtures::v10::full_xml_string_data()
210        ),
211        case::full_v10_with_xml_base64_data(
212            fixtures::v10::full_xml_base64_data_json(),
213            fixtures::v10::full_xml_binary_data()
214        )
215    )]
216    fn deserialize_json_should_succeed(in_json: Value, out_event: Event) {
217        let deserialize_result: Result<Event, serde_json::Error> = serde_json::from_value(in_json);
218        assert_ok!(&deserialize_result);
219        let deserialize_json = deserialize_result.unwrap();
220        assert_eq!(deserialize_json, out_event)
221    }
222
223    #[test]
224    fn deserialize_with_null_attribute() {
225        let in_json = json!({
226            "specversion" : "1.0",
227            "type" : "com.example.someevent",
228            "source" : "/mycontext",
229            "id" : "A234-1234-1234",
230            "time" : null,
231            "comexampleextension1" : "value",
232            "comexampleothervalue" : 5,
233            "datacontenttype" : "text/xml",
234            "data" : "<much wow=\"xml\"/>"
235        });
236
237        let out_event = EventBuilderV10::new()
238            .ty("com.example.someevent")
239            .source("/mycontext")
240            .id("A234-1234-1234")
241            .data("text/xml", "<much wow=\"xml\"/>")
242            .extension("comexampleextension1", "value")
243            .extension("comexampleothervalue", 5)
244            .build()
245            .unwrap();
246
247        let deserialize_result: Result<Event, serde_json::Error> = serde_json::from_value(in_json);
248        assert_ok!(&deserialize_result);
249        let deserialize_json = deserialize_result.unwrap();
250        assert_eq!(deserialize_json, out_event)
251    }
252
253    #[test]
254    fn deserialize_with_null_ext() {
255        let in_json = json!({
256            "specversion" : "1.0",
257            "type" : "com.example.someevent",
258            "source" : "/mycontext",
259            "id" : "A234-1234-1234",
260            "time" : "2018-04-05T17:31:00Z",
261            "comexampleextension1" : "value",
262            "comexampleothervalue" : 5,
263            "unsetextension": null,
264            "datacontenttype" : "text/xml",
265            "data" : "<much wow=\"xml\"/>"
266        });
267
268        let out_event = EventBuilderV10::new()
269            .ty("com.example.someevent")
270            .source("/mycontext")
271            .id("A234-1234-1234")
272            .time("2018-04-05T17:31:00Z")
273            .data("text/xml", "<much wow=\"xml\"/>")
274            .extension("comexampleextension1", "value")
275            .extension("comexampleothervalue", 5)
276            .build()
277            .unwrap();
278
279        let deserialize_result: Result<Event, serde_json::Error> = serde_json::from_value(in_json);
280        assert_ok!(&deserialize_result);
281        let deserialize_json = deserialize_result.unwrap();
282        assert_eq!(deserialize_json, out_event)
283    }
284}