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 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 pub fn version(&self) -> &str {
38 self.context.version()
39 }
40
41 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 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 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 pub fn subject(&self) -> &Subject {
78 &self.subject
79 }
80
81 pub fn ty(&self) -> &str {
84 self.context.ty()
86 }
87
88 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 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 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}