xapi_rs/data/
statement_result.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::data::{DataError, Statement, StatementId, validate_irl};
4use core::fmt;
5use iri_string::types::{IriStr, IriString};
6use serde::{Deserialize, Serialize};
7use serde_with::skip_serializing_none;
8use tracing::warn;
9
10/// Structure that contains zero, one, or more [Statement]s.
11///
12/// The `statements` field will contain the result of a **`GET`** _Statement_
13/// Resource. If it is incomplete (due for example to pagination), the rest can
14/// be accessed at the IRL provided by the `more` property.
15///
16#[skip_serializing_none]
17#[derive(Debug, Default, Deserialize, Serialize)]
18pub struct StatementResult {
19    statements: Vec<Statement>,
20    more: Option<IriString>,
21}
22
23#[skip_serializing_none]
24#[derive(Debug, Serialize)]
25pub(crate) struct StatementResultId {
26    statements: Vec<StatementId>,
27    more: Option<IriString>,
28}
29
30impl From<StatementResult> for StatementResultId {
31    fn from(value: StatementResult) -> Self {
32        StatementResultId {
33            statements: value
34                .statements
35                .into_iter()
36                .map(StatementId::from)
37                .collect(),
38            more: value.more,
39        }
40    }
41}
42
43impl StatementResult {
44    /// Construct a new instance from a given collection of [Statement]s.
45    pub fn from(statements: Vec<Statement>) -> Self {
46        StatementResult {
47            statements,
48            more: None,
49        }
50    }
51
52    /// Return a reference to this instance's statements collection.
53    pub fn statements(&self) -> &Vec<Statement> {
54        self.statements.as_ref()
55    }
56
57    /// Return TRUE if the `statements` collection is empty.
58    pub fn is_empty(&self) -> bool {
59        self.statements.is_empty()
60    }
61
62    /// Return the `more` field of this instance if set; `None` otherwise.
63    pub fn more(&self) -> Option<&IriStr> {
64        self.more.as_deref()
65    }
66
67    /// Set the `more` field.
68    ///
69    /// Raise [DataError] if the argument is empty, cannot be parsed as
70    /// an IRI, or the resulting IRI is not a valid URL.
71    pub fn set_more(&mut self, val: &str) -> Result<(), DataError> {
72        let s = val.trim();
73        if s.is_empty() {
74            warn!("Input value is empty. Unset URL");
75            self.more = None;
76        } else {
77            let iri = IriStr::new(s)?;
78            validate_irl(iri)?;
79            self.more = Some(iri.to_owned());
80        }
81        Ok(())
82    }
83}
84
85impl fmt::Display for StatementResult {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        let statements = self
88            .statements
89            .iter()
90            .map(|x| x.to_string())
91            .collect::<Vec<_>>()
92            .join(", ");
93        if self.more.is_some() {
94            write!(
95                f,
96                "StatementResult{{[ {} ], '{}'}}",
97                statements,
98                self.more.as_ref().unwrap()
99            )
100        } else {
101            write!(f, "StatementResult{{[ {statements} ]}}")
102        }
103    }
104}
105
106impl StatementResultId {
107    pub(crate) fn from(statements: Vec<StatementId>) -> Self {
108        StatementResultId {
109            statements,
110            more: None,
111        }
112    }
113
114    pub(crate) fn set_more(&mut self, val: &str) -> Result<(), DataError> {
115        let s = val.trim();
116        let iri = IriStr::new(s)?;
117        self.more = Some(iri.to_owned());
118        Ok(())
119    }
120
121    pub(crate) fn is_empty(&self) -> bool {
122        self.statements.is_empty()
123    }
124
125    pub(crate) fn statements(&self) -> &Vec<StatementId> {
126        self.statements.as_ref()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_deserialization() {
136        const SR: &str = r#"{
137"statements":[{
138  "id":"01932d1e-a584-79d2-b83a-6b380546b21c",
139  "actor":{"mbox":"mailto:agent99@adlnet.gov"},
140  "verb":{"id":"http://adlnet.gov/expapi/verbs/attended"},
141  "object":{
142    "objectType":"SubStatement",
143    "actor":{"objectType":"Group","member":[],"mbox":"mailto:groupb@adlnet.gov"},
144    "verb":{"id":"http://adlnet.gov/expapi/verbs/attended"},
145    "object":{"id":"http://www.example.com/unicode/36c47486-83c8-4b4f-872c-67af87e9ad10"},
146    "timestamp":"2024-11-20T00:06:06.838Z"
147  },
148  "timestamp":"2024-11-20T00:06:06.801Z",
149  "stored":"2024-11-20T00:06:06.802+00:00",
150  "authority":{"mbox":"mailto:admin@my.xapi.net"}
151}],
152"more":null}"#;
153
154        let sr: StatementResult = serde_json::from_str(SR).unwrap();
155        assert_eq!(sr.statements().len(), 1);
156    }
157}