Skip to main content

xapi_rs/data/
sub_statement.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::{
4    data::{
5        Actor, ActorId, Attachment, Context, ContextId, DataError, Fingerprint, MyTimestamp,
6        ObjectType, SubStatementObject, SubStatementObjectId, Validate, ValidationError, Verb,
7        VerbId, XResult, fingerprint_it,
8    },
9    emit_error,
10};
11use chrono::{DateTime, Utc};
12use core::fmt;
13use serde::{Deserialize, Serialize};
14use serde_with::skip_serializing_none;
15use std::{hash::Hasher, str::FromStr};
16
17/// Alternative representation of a [Statement][1] when referenced as the
18/// _object_ of another.
19///
20/// [1]: crate::Statement
21#[skip_serializing_none]
22#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
23#[serde(deny_unknown_fields)]
24pub struct SubStatement {
25    #[serde(rename = "objectType")]
26    object_type: ObjectType,
27    actor: Actor,
28    verb: Verb,
29    object: SubStatementObject,
30    result: Option<XResult>,
31    context: Option<Context>,
32    timestamp: Option<MyTimestamp>,
33    attachments: Option<Vec<Attachment>>,
34}
35
36#[skip_serializing_none]
37#[derive(Debug, Serialize)]
38#[serde(deny_unknown_fields)]
39pub(crate) struct SubStatementId {
40    #[serde(rename = "objectType")]
41    object_type: ObjectType,
42    actor: ActorId,
43    verb: VerbId,
44    object: SubStatementObjectId,
45    result: Option<XResult>,
46    context: Option<ContextId>,
47    timestamp: Option<MyTimestamp>,
48    attachments: Option<Vec<Attachment>>,
49}
50
51impl From<SubStatement> for SubStatementId {
52    fn from(value: SubStatement) -> Self {
53        SubStatementId {
54            object_type: ObjectType::SubStatement,
55            actor: ActorId::from(value.actor),
56            verb: VerbId::from(value.verb),
57            object: SubStatementObjectId::from(value.object),
58            result: value.result,
59            context: value.context.map(ContextId::from),
60            timestamp: value.timestamp,
61            attachments: value.attachments,
62        }
63    }
64}
65
66impl From<Box<SubStatement>> for SubStatementId {
67    fn from(value: Box<SubStatement>) -> Self {
68        SubStatementId {
69            object_type: ObjectType::SubStatement,
70            actor: ActorId::from(value.actor),
71            verb: VerbId::from(value.verb),
72            object: SubStatementObjectId::from(value.object),
73            result: value.result,
74            context: value.context.map(ContextId::from),
75            timestamp: value.timestamp,
76            attachments: value.attachments,
77        }
78    }
79}
80
81impl From<SubStatementId> for SubStatement {
82    fn from(value: SubStatementId) -> Self {
83        SubStatement {
84            object_type: ObjectType::SubStatement,
85            actor: Actor::from(value.actor),
86            verb: Verb::from(value.verb),
87            object: SubStatementObject::from(value.object),
88            result: value.result,
89            context: value.context.map(Context::from),
90            timestamp: value.timestamp,
91            attachments: value.attachments,
92        }
93    }
94}
95
96impl SubStatement {
97    /// Return a [SubStatement] _Builder_.
98    pub fn builder() -> SubStatementBuilder {
99        SubStatementBuilder::default()
100    }
101
102    /// Return TRUE if the `objectType` property is [SubStatement][1]; FALSE
103    /// otherwise.
104    ///
105    /// [1]: ObjectType#variant.SubStatement
106    pub fn check_object_type(&self) -> bool {
107        self.object_type == ObjectType::SubStatement
108    }
109
110    /// Return the [Actor] whom the Sub-Statement is about. The [Actor] is either
111    /// an [Agent][1] or a [Group][2].
112    ///
113    /// [1]: crate::Agent
114    /// [2]: crate::Group
115    pub fn actor(&self) -> &Actor {
116        &self.actor
117    }
118
119    /// Return the _action_ taken by the _actor_.
120    pub fn verb(&self) -> &Verb {
121        &self.verb
122    }
123
124    /// Return an [Activity][1], an [Agent][2], or another [Statement][3] that
125    /// is the _Object_ of this Sub-Statement.
126    ///
127    /// [1]: crate::Activity
128    /// [2]: crate::Agent
129    /// [3]: crate::Statement
130    pub fn object(&self) -> &SubStatementObject {
131        &self.object
132    }
133
134    /// Return the [Result] instance if set; `None` otherwise.
135    pub fn result(&self) -> Option<&XResult> {
136        self.result.as_ref()
137    }
138
139    /// Return the [Context] of this instance if set; `None` otherwise.
140    pub fn context(&self) -> Option<&Context> {
141        self.context.as_ref()
142    }
143
144    /// Return timestamp of when the events described in this [SubStatement]
145    /// occurred if set; `None` otherwise.
146    ///
147    /// It's set by the LRS if not provided.
148    pub fn timestamp(&self) -> Option<&DateTime<Utc>> {
149        if let Some(z_timestamp) = self.timestamp.as_ref() {
150            Some(z_timestamp.inner())
151        } else {
152            None
153        }
154    }
155
156    /// Return [`attachments`][Attachment] if set; `None` otherwise.
157    pub fn attachments(&self) -> Option<&[Attachment]> {
158        self.attachments.as_deref()
159    }
160
161    /// Return fingerprint of this instance.
162    pub fn uid(&self) -> u64 {
163        fingerprint_it(self)
164    }
165
166    /// Return TRUE if this is _Equivalent_ to `that`; FALSE otherwise.
167    pub fn equivalent(&self, that: &SubStatement) -> bool {
168        self.uid() == that.uid()
169    }
170}
171
172impl fmt::Display for SubStatement {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        let mut vec = vec![];
175
176        vec.push(format!("actor: {}", self.actor));
177        vec.push(format!("verb: {}", self.verb));
178        vec.push(format!("object: {}", self.object));
179        if let Some(z_result) = self.result.as_ref() {
180            vec.push(format!("result: {}", z_result));
181        }
182        if let Some(z_context) = self.context.as_ref() {
183            vec.push(format!("context: {}", z_context));
184        }
185        if let Some(z_timestamp) = self.timestamp.as_ref() {
186            vec.push(format!("timestamp: \"{}\"", z_timestamp));
187        }
188        if self.attachments.is_some() {
189            let items = self.attachments.as_deref().unwrap();
190            vec.push(format!(
191                "attachments: [{}]",
192                items
193                    .iter()
194                    .map(|x| x.to_string())
195                    .collect::<Vec<_>>()
196                    .join(", ")
197            ))
198        }
199
200        let res = vec
201            .iter()
202            .map(|x| x.to_string())
203            .collect::<Vec<_>>()
204            .join(", ");
205        write!(f, "SubStatement{{ {res} }}")
206    }
207}
208
209impl Fingerprint for SubStatement {
210    fn fingerprint<H: Hasher>(&self, state: &mut H) {
211        // discard `object_type`
212        self.actor.fingerprint(state);
213        self.verb.fingerprint(state);
214        self.object.fingerprint(state);
215        // self.result.hash(state);
216        // self.context.hash(state);
217        // discard `timestamp`, `stored`, `authority`, `version` and `attachments`
218    }
219}
220
221impl Validate for SubStatement {
222    fn validate(&self) -> Vec<ValidationError> {
223        let mut vec = vec![];
224
225        if !self.check_object_type() {
226            vec.push(ValidationError::WrongObjectType {
227                expected: ObjectType::SubStatement,
228                found: self.object_type.to_string().into(),
229            })
230        }
231        vec.extend(self.actor.validate());
232        vec.extend(self.verb.validate());
233        vec.extend(self.object.validate());
234        if let Some(z_result) = self.result.as_ref() {
235            vec.extend(z_result.validate())
236        }
237        if let Some(z_context) = self.context.as_ref() {
238            vec.extend(z_context.validate());
239            // NOTE (rsn) 20241017 - same as in Statement...
240            if !self.object().is_activity()
241                && (z_context.revision().is_some() || z_context.platform().is_some())
242            {
243                vec.push(ValidationError::ConstraintViolation(
244                    "SubStatement context w/ revision | platform but object != Activity".into(),
245                ))
246            }
247        }
248        if let Some(z_attachments) = self.attachments.as_ref() {
249            for att in z_attachments.iter() {
250                vec.extend(att.validate())
251            }
252        }
253
254        vec
255    }
256}
257
258/// A Type that knows how to construct a [SubStatement].
259#[derive(Debug, Default)]
260pub struct SubStatementBuilder {
261    _actor: Option<Actor>,
262    _verb: Option<Verb>,
263    _object: Option<SubStatementObject>,
264    _result: Option<XResult>,
265    _context: Option<Context>,
266    _timestamp: Option<MyTimestamp>,
267    _attachments: Option<Vec<Attachment>>,
268}
269
270impl SubStatementBuilder {
271    /// Set the `actor` field.
272    ///
273    /// Raise [DataError] if the argument is invalid.
274    pub fn actor(mut self, val: Actor) -> Result<Self, DataError> {
275        val.check_validity()?;
276        self._actor = Some(val);
277        Ok(self)
278    }
279
280    /// Set the `verb` field.
281    ///
282    /// Raise [DataError] if the argument is invalid.
283    pub fn verb(mut self, val: Verb) -> Result<Self, DataError> {
284        val.check_validity()?;
285        self._verb = Some(val);
286        Ok(self)
287    }
288
289    /// Set the `object` field.
290    ///
291    /// Raise [DataError] if the argument is invalid.
292    pub fn object(mut self, val: SubStatementObject) -> Result<Self, DataError> {
293        val.check_validity()?;
294        self._object = Some(val);
295        Ok(self)
296    }
297
298    /// Set the `result` field.
299    ///
300    /// Raise [DataError] if the argument is invalid.
301    pub fn result(mut self, val: XResult) -> Result<Self, DataError> {
302        val.check_validity()?;
303        self._result = Some(val);
304        Ok(self)
305    }
306
307    /// Set the `context` field.
308    ///
309    /// Raise [DataError] if the argument is invalid.
310    pub fn context(mut self, val: Context) -> Result<Self, DataError> {
311        val.check_validity()?;
312        self._context = Some(val);
313        Ok(self)
314    }
315
316    /// Set the `timestamp` field.
317    ///
318    /// Raise [DataError] if the argument is empty or invalid.
319    pub fn timestamp(mut self, val: &str) -> Result<Self, DataError> {
320        let val = val.trim();
321        if val.is_empty() {
322            emit_error!(DataError::Validation(ValidationError::Empty(
323                "timestamp".into()
324            )))
325        }
326        let ts = MyTimestamp::from_str(val)?;
327        self._timestamp = Some(ts);
328        Ok(self)
329    }
330
331    /// Replace the `timestamp` field w/ `val`.
332    pub fn with_timestamp(mut self, val: DateTime<Utc>) -> Self {
333        self._timestamp = Some(MyTimestamp::from(val));
334        self
335    }
336
337    /// Add `att` to `attachments` field if valid; otherwise raise a
338    /// [DataError].
339    pub fn attachment(mut self, att: Attachment) -> Result<Self, DataError> {
340        att.check_validity()?;
341        if self._attachments.is_none() {
342            self._attachments = Some(vec![])
343        }
344        self._attachments.as_mut().unwrap().push(att);
345        Ok(self)
346    }
347
348    /// Create a [SubStatement] from set field values.
349    ///
350    /// Raise [DataError] if an inconsistency is detected.
351    pub fn build(self) -> Result<SubStatement, DataError> {
352        if self._actor.is_none() || self._verb.is_none() || self._object.is_none() {
353            emit_error!(DataError::Validation(ValidationError::MissingField(
354                "actor | verb | object".into()
355            )))
356        }
357        Ok(SubStatement {
358            object_type: ObjectType::SubStatement,
359            actor: self._actor.unwrap(),
360            verb: self._verb.unwrap(),
361            object: self._object.unwrap(),
362            result: self._result,
363            context: self._context,
364            timestamp: self._timestamp,
365            attachments: self._attachments,
366        })
367    }
368}