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 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 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 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 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 pub fn subject(&self) -> &Subject {
80 &self.subject
81 }
82
83 pub fn ty(&self) -> &str {
86 self.context.ty.as_str()
88 }
89
90 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 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}