Skip to main content

xapi_data/
sub_statement.rs

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