Skip to main content

cdevents_sdk/
cdevent.rs

1use crate::{Context, ContextEnum, Id, Subject, UriReference};
2use serde::{
3    de::{self, Deserializer, MapAccess, Visitor},
4    Deserialize, Serialize,
5};
6use std::fmt;
7
8#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
9#[serde(deny_unknown_fields)]
10pub struct CDEvent {
11    context: ContextEnum,
12    subject: Subject,
13    #[serde(rename = "customData", skip_serializing_if = "Option::is_none")]
14    custom_data: Option<serde_json::Value>,
15    #[serde(
16        rename = "customDataContentType",
17        skip_serializing_if = "Option::is_none"
18    )]
19    custom_data_content_type: Option<String>,
20}
21
22impl From<Subject> for CDEvent {
23    fn from(subject: Subject) -> Self {
24        // TODO select context from subject version
25        let context = crate::new_context(subject.content().ty());
26        Self {
27            context,
28            subject,
29            custom_data: None,
30            custom_data_content_type: None,
31        }
32    }
33}
34
35impl CDEvent {
36    /// see <https://github.com/cdevents/spec/blob/main/spec.md#version>
37    pub fn version(&self) -> &str {
38        self.context.version()
39    }
40
41    // pub fn with_version<T>(mut self, v: T) -> Self where T: Into<String> {
42    //     self.context.with_version(v);
43    //     self
44    // }
45
46    /// see <https://github.com/cdevents/spec/blob/main/spec.md#id-context>
47    pub fn id(&self) -> &Id {
48        self.context.id()
49    }
50
51    pub fn with_id(mut self, v: Id) -> Self {
52        self.context = self.context.with_id(v);
53        self
54    }
55
56    /// see <https://github.com/cdevents/spec/blob/main/spec.md#source-context>
57    pub fn source(&self) -> &UriReference {
58        self.context.source()
59    }
60
61    pub fn with_source(mut self, v: UriReference) -> Self {
62        self.context = self.context.with_source(v);
63        self
64    }
65
66    /// see <https://github.com/cdevents/spec/blob/main/spec.md#timestamp>
67    pub fn timestamp(&self) -> &time::OffsetDateTime {
68        self.context.timestamp()
69    }
70
71    pub fn with_timestamp(mut self, v: time::OffsetDateTime) -> Self {
72        self.context = self.context.with_timestamp(v);
73        self
74    }
75
76    /// see <https://github.com/cdevents/spec/blob/main/spec.md#cdevent-subject>
77    pub fn subject(&self) -> &Subject {
78        &self.subject
79    }
80
81    /// see <https://github.com/cdevents/spec/blob/main/spec.md#type-context>
82    /// derived from subject.content
83    pub fn ty(&self) -> &str {
84        //self.subject.content().ty()
85        self.context.ty()
86    }
87
88    /// see <https://github.com/cdevents/spec/blob/main/spec.md#customdata>
89    pub fn custom_data(&self) -> &Option<serde_json::Value> {
90        &self.custom_data
91    }
92
93    pub fn with_custom_data(mut self, custom_data: serde_json::Value) -> Self {
94        self.custom_data = Some(custom_data);
95        self
96    }
97
98    /// see <https://github.com/cdevents/spec/blob/main/spec.md#customdatacontenttype>
99    pub fn custom_data_content_type(&self) -> &Option<String> {
100        &self.custom_data_content_type
101    }
102
103    pub fn with_custom_data_content_type(
104        mut self,
105        custom_data_content_type: String,
106    ) -> Self {
107        self.custom_data_content_type = Some(custom_data_content_type);
108        self
109    }
110}
111
112impl<'de> Deserialize<'de> for CDEvent {
113    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114    where
115        D: Deserializer<'de>,
116    {
117        #[derive(Deserialize)]
118        #[serde(field_identifier, rename_all = "camelCase")]
119        enum Field {
120            Context,
121            Subject,
122            CustomData,
123            CustomDataContentType,
124        }
125
126        struct CDEventVisitor;
127
128        // TODO remove dependencie to serde_json
129        impl<'de> Visitor<'de> for CDEventVisitor {
130            type Value = CDEvent;
131
132            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
133                formatter.write_str("struct CDEvent")
134            }
135
136            fn visit_map<V>(self, mut map: V) -> Result<CDEvent, V::Error>
137            where
138                V: MapAccess<'de>,
139            {
140                let mut context_json: Option<serde_json::value::Value> = None;
141                let mut subject_json: Option<serde_json::value::Value> = None;
142                let mut custom_data = None;
143                let mut custom_data_content_type = None;
144                while let Some(key) = map.next_key()? {
145                    match key {
146                        Field::Context => {
147                            if context_json.is_some() {
148                                return Err(de::Error::duplicate_field("context"));
149                            }
150                            context_json = Some(map.next_value()?);
151                        }
152                        Field::Subject => {
153                            if subject_json.is_some() {
154                                return Err(de::Error::duplicate_field("subject"));
155                            }
156                            subject_json = Some(map.next_value()?);
157                        }
158                        Field::CustomData => {
159                            if custom_data.is_some() {
160                                return Err(de::Error::duplicate_field("customData"));
161                            }
162                            custom_data = Some(map.next_value()?);
163                        }
164                        Field::CustomDataContentType => {
165                            if custom_data_content_type.is_some() {
166                                return Err(de::Error::duplicate_field("customDataContentType"));
167                            }
168                            custom_data_content_type = Some(map.next_value()?);
169                        }
170                    }
171                }
172                let context = {
173                    let context_json = context_json.ok_or_else(|| de::Error::missing_field("context"))?;
174                    ContextEnum::from_json(context_json).map_err(de::Error::custom)?
175                };
176                let subject = {
177                    let subject_json = subject_json.ok_or_else(|| de::Error::missing_field("subject"))?;
178                    Subject::from_json(context.ty(), subject_json).map_err(de::Error::custom)?
179                };
180
181                Ok(CDEvent {
182                    context,
183                    subject,
184                    custom_data,
185                    custom_data_content_type,
186                })
187            }
188        }
189
190        const FIELDS: &[&str] = &["context", "subject"];
191        deserializer.deserialize_struct("CDEvent", FIELDS, CDEventVisitor)
192    }
193}
194
195#[cfg(feature = "testkit")]
196impl<> proptest::arbitrary::Arbitrary for CDEvent {
197    type Parameters = ();
198    type Strategy = proptest::strategy::BoxedStrategy<Self>;
199
200    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
201        use proptest::prelude::*;
202        (
203            any::<Subject>(),
204            any::<Id>(),
205            any::<UriReference>(),
206        ).prop_map(|(subject, id, source)| {
207            CDEvent::from(subject).with_id(id).with_source(source)
208        }).boxed()
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use crate::CDEvent;
215    use proptest::prelude::*;
216
217    proptest! {
218        #[test]
219        #[cfg(feature = "testkit")]
220        fn arbitraries_are_json_valid(s in any::<CDEvent>()) {
221            let json_str = serde_json::to_string(&s).unwrap();
222            let actual = serde_json::from_str::<CDEvent>(&json_str).unwrap();
223            assert_eq!(s, actual);
224        }
225    }
226}