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