Skip to main content

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 let Some(z_more) = self.more.as_ref() {
94            write!(f, "StatementResult{{[ {} ], '{}'}}", statements, z_more)
95        } else {
96            write!(f, "StatementResult{{[ {statements} ]}}")
97        }
98    }
99}
100
101impl StatementResultId {
102    pub(crate) fn from(statements: Vec<StatementId>) -> Self {
103        StatementResultId {
104            statements,
105            more: None,
106        }
107    }
108
109    pub(crate) fn set_more(&mut self, val: &str) -> Result<(), DataError> {
110        let s = val.trim();
111        let iri = IriStr::new(s)?;
112        self.more = Some(iri.to_owned());
113        Ok(())
114    }
115
116    pub(crate) fn is_empty(&self) -> bool {
117        self.statements.is_empty()
118    }
119
120    pub(crate) fn statements(&self) -> &Vec<StatementId> {
121        self.statements.as_ref()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_deserialization() {
131        const SR: &str = r#"{
132"statements":[{
133  "id":"01932d1e-a584-79d2-b83a-6b380546b21c",
134  "actor":{"mbox":"mailto:agent99@adlnet.gov"},
135  "verb":{"id":"http://adlnet.gov/expapi/verbs/attended"},
136  "object":{
137    "objectType":"SubStatement",
138    "actor":{"objectType":"Group","member":[],"mbox":"mailto:groupb@adlnet.gov"},
139    "verb":{"id":"http://adlnet.gov/expapi/verbs/attended"},
140    "object":{"id":"http://www.example.com/unicode/36c47486-83c8-4b4f-872c-67af87e9ad10"},
141    "timestamp":"2024-11-20T00:06:06.838Z"
142  },
143  "timestamp":"2024-11-20T00:06:06.801Z",
144  "stored":"2024-11-20T00:06:06.802+00:00",
145  "authority":{"mbox":"mailto:admin@my.xapi.net"}
146}],
147"more":null}"#;
148
149        let sr: StatementResult = serde_json::from_str(SR).unwrap();
150        assert_eq!(sr.statements().len(), 1);
151    }
152}