xapi_rs/data/
context_activities.rs1use crate::{
4 ActivityId, DataError,
5 data::{Activity, Fingerprint, Validate, ValidationError},
6 emit_error,
7};
8use core::fmt;
9use serde::{Deserialize, Serialize};
10use serde_with::{OneOrMany, serde_as, skip_serializing_none};
11use std::hash::Hasher;
12
13#[serde_as]
14#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
15struct Activities(
16 #[serde_as(deserialize_as = "OneOrMany<_>", serialize_as = "Vec<_>")] Vec<Activity>,
17);
18
19#[serde_as]
20#[derive(Debug, Serialize)]
21struct ActivitiesId(#[serde_as(serialize_as = "Vec<_>")] Vec<ActivityId>);
22
23impl From<Activities> for ActivitiesId {
24 fn from(value: Activities) -> Self {
25 ActivitiesId(value.0.into_iter().map(ActivityId::from).collect())
26 }
27}
28
29impl From<ActivitiesId> for Activities {
30 fn from(value: ActivitiesId) -> Self {
31 Activities(value.0.into_iter().map(Activity::from).collect())
32 }
33}
34
35#[serde_as]
45#[skip_serializing_none]
46#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
47#[serde(deny_unknown_fields)]
48pub struct ContextActivities {
49 parent: Option<Activities>,
50 grouping: Option<Activities>,
51 category: Option<Activities>,
52 other: Option<Activities>,
53}
54
55#[skip_serializing_none]
56#[derive(Debug, Serialize)]
57pub(crate) struct ContextActivitiesId {
58 parent: Option<ActivitiesId>,
59 grouping: Option<ActivitiesId>,
60 category: Option<ActivitiesId>,
61 other: Option<ActivitiesId>,
62}
63
64impl From<ContextActivities> for ContextActivitiesId {
65 fn from(value: ContextActivities) -> Self {
66 ContextActivitiesId {
67 parent: value.parent.map(|x| x.into()),
68 grouping: value.grouping.map(|x| x.into()),
69 category: value.category.map(|x| x.into()),
70 other: value.other.map(|x| x.into()),
71 }
72 }
73}
74
75impl From<ContextActivitiesId> for ContextActivities {
76 fn from(value: ContextActivitiesId) -> Self {
77 ContextActivities {
78 parent: value.parent.map(Activities::from),
79 grouping: value.grouping.map(Activities::from),
80 category: value.category.map(Activities::from),
81 other: value.other.map(Activities::from),
82 }
83 }
84}
85
86impl ContextActivities {
87 pub fn builder() -> ContextActivitiesBuilder {
89 ContextActivitiesBuilder::default()
90 }
91
92 pub fn parent(&self) -> &[Activity] {
94 if self.parent.is_none() {
95 &[]
96 } else {
97 self.parent.as_ref().unwrap().0.as_slice()
98 }
99 }
100
101 pub fn grouping(&self) -> &[Activity] {
103 if self.grouping.is_none() {
104 &[]
105 } else {
106 self.grouping.as_ref().unwrap().0.as_slice()
107 }
108 }
109
110 pub fn category(&self) -> &[Activity] {
112 if self.category.is_none() {
113 &[]
114 } else {
115 self.category.as_ref().unwrap().0.as_slice()
116 }
117 }
118
119 pub fn other(&self) -> &[Activity] {
121 if self.other.is_none() {
122 &[]
123 } else {
124 self.other.as_ref().unwrap().0.as_slice()
125 }
126 }
127}
128
129impl Fingerprint for ContextActivities {
130 fn fingerprint<H: Hasher>(&self, state: &mut H) {
131 if self.parent.is_some() {
132 Fingerprint::fingerprint_slice(self.parent(), state)
133 }
134 if self.grouping.is_some() {
135 Fingerprint::fingerprint_slice(self.grouping(), state)
136 }
137 if self.category.is_some() {
138 Fingerprint::fingerprint_slice(self.category(), state)
139 }
140 if self.other.is_some() {
141 Fingerprint::fingerprint_slice(self.other(), state)
142 }
143 }
144}
145
146impl fmt::Display for ContextActivities {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 let mut vec = vec![];
149
150 if self.parent.is_some() {
151 vec.push(format!(
152 "parent: [{}]",
153 self.parent()
154 .iter()
155 .map(|x| x.to_string())
156 .collect::<Vec<_>>()
157 .join(", ")
158 ))
159 }
160 if self.grouping.is_some() {
161 vec.push(format!(
162 "grouping: [{}]",
163 self.grouping()
164 .iter()
165 .map(|x| x.to_string())
166 .collect::<Vec<_>>()
167 .join(", ")
168 ))
169 }
170 if self.category.is_some() {
171 vec.push(format!(
172 "category: [{}]",
173 self.category()
174 .iter()
175 .map(|x| x.to_string())
176 .collect::<Vec<_>>()
177 .join(", ")
178 ))
179 }
180 if self.other.is_some() {
181 vec.push(format!(
182 "other: [{}]",
183 self.other()
184 .iter()
185 .map(|x| x.to_string())
186 .collect::<Vec<_>>()
187 .join(", ")
188 ))
189 }
190
191 let res = vec
192 .iter()
193 .map(|x| x.to_string())
194 .collect::<Vec<_>>()
195 .join(", ");
196 write!(f, "{{ {res} }}")
197 }
198}
199
200impl Validate for ContextActivities {
201 fn validate(&self) -> Vec<ValidationError> {
202 let mut vec = vec![];
203
204 if self.parent.is_some() {
205 self.parent().iter().for_each(|x| vec.extend(x.validate()));
206 }
207 if self.grouping.is_some() {
208 self.grouping()
209 .iter()
210 .for_each(|x| vec.extend(x.validate()));
211 }
212 if self.category.is_some() {
213 self.category()
214 .iter()
215 .for_each(|x| vec.extend(x.validate()));
216 }
217 if self.other.is_some() {
218 self.other().iter().for_each(|x| vec.extend(x.validate()));
219 }
220
221 vec
222 }
223}
224
225#[derive(Debug, Default)]
227pub struct ContextActivitiesBuilder {
228 _parent: Vec<Activity>,
229 _grouping: Vec<Activity>,
230 _category: Vec<Activity>,
231 _other: Vec<Activity>,
232}
233
234impl ContextActivitiesBuilder {
235 pub fn parent(mut self, val: Activity) -> Result<Self, DataError> {
239 val.check_validity()?;
240 self._parent.push(val);
241 Ok(self)
242 }
243
244 pub fn grouping(mut self, val: Activity) -> Result<Self, DataError> {
248 val.check_validity()?;
249 self._grouping.push(val);
250 Ok(self)
251 }
252
253 pub fn category(mut self, val: Activity) -> Result<Self, DataError> {
257 val.check_validity()?;
258 self._category.push(val);
259 Ok(self)
260 }
261
262 pub fn other(mut self, val: Activity) -> Result<Self, DataError> {
266 val.check_validity()?;
267 self._other.push(val);
268 Ok(self)
269 }
270
271 pub fn build(self) -> Result<ContextActivities, DataError> {
275 if self._parent.is_empty()
276 && self._grouping.is_empty()
277 && self._category.is_empty()
278 && self._other.is_empty()
279 {
280 emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
281 "At least one of the keys must be set".into()
282 )))
283 } else {
284 Ok(ContextActivities {
285 parent: if self._parent.is_empty() {
286 None
287 } else {
288 Some(Activities(self._parent))
289 },
290 grouping: if self._grouping.is_empty() {
291 None
292 } else {
293 Some(Activities(self._grouping))
294 },
295 category: if self._category.is_empty() {
296 None
297 } else {
298 Some(Activities(self._category))
299 },
300 other: if self._other.is_empty() {
301 None
302 } else {
303 Some(Activities(self._other))
304 },
305 })
306 }
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn test_missing_keys() -> Result<(), DataError> {
316 const CA: &str = r#"{}"#;
317
318 let ca = serde_json::from_str::<ContextActivities>(CA).map_err(|x| DataError::JSON(x))?;
319 assert_eq!(ca.parent, None);
320 assert!(ca.parent().is_empty());
321 assert_eq!(ca.grouping, None);
322 assert!(ca.grouping().is_empty());
323 assert_eq!(ca.category, None);
324 assert!(ca.category().is_empty());
325 assert_eq!(ca.other, None);
326 assert!(ca.other().is_empty());
327
328 Ok(())
329 }
330
331 #[test]
332 fn test_one_or_many() -> Result<(), DataError> {
333 const CA1: &str = r#"{"parent":{"id":"http://xapi.acticity/1"}}"#;
334 const CA2: &str =
335 r#"{"other":[{"id":"http://xapi.activity/1"},{"id":"http://xapi.activity/2"}]}"#;
336
337 let one = serde_json::from_str::<ContextActivities>(CA1).map_err(|x| DataError::JSON(x))?;
338 assert!(one.parent.is_some());
339 assert_eq!(one.parent().len(), 1);
340
341 let many =
342 serde_json::from_str::<ContextActivities>(CA2).map_err(|x| DataError::JSON(x))?;
343 assert!(many.other.is_some());
344 assert_eq!(many.other().len(), 2);
345
346 Ok(())
347 }
348
349 #[test]
350 fn test_serialize_as_array() {
351 const CA: &str = r#"{"parent":{"id":"http://xapi.acticity/1"}}"#;
352 const EXPECTED: &str = r#"{"parent":[{"id":"http://xapi.acticity/1"}]}"#;
353
354 let ca = serde_json::from_str::<ContextActivities>(CA).unwrap();
355 let actual = serde_json::to_string(&ca).unwrap();
356 assert_eq!(EXPECTED, actual);
357 }
358}