cdevents_sdk/
cdevent.rs

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