Skip to main content

xapi_data/
format.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::{DataError, MyLanguageTag, ValidationError, emit_error};
4use core::fmt;
5use serde::{Deserialize, Serialize};
6use std::str::FromStr;
7
8/// Structure that combines a _Statement_ resource **`GET`** request `format`
9/// parameter along w/ the request's **`Accept-Language`**, potentially empty,
10/// list of user-preferred language-tags, in descending order of preference.
11/// This is provided to facilitate reducing types to their _canonical_ form
12/// when required by higher layer APIs.
13///  
14#[doc = include_str!("../doc/Format.md")]
15#[derive(Clone, Debug, Deserialize, Serialize)]
16pub struct Format {
17    format: FormatParam,
18    tags: Vec<MyLanguageTag>,
19}
20
21impl Default for Format {
22    fn default() -> Self {
23        Self {
24            format: FormatParam::Exact,
25            tags: vec![],
26        }
27    }
28}
29
30impl fmt::Display for Format {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        let res = self
33            .tags
34            .iter()
35            .map(|x| x.to_string())
36            .collect::<Vec<_>>()
37            .join(", ");
38        write!(f, "Format{{ {}, [{}] }}", self.format, res)
39    }
40}
41
42impl Format {
43    /// Return a new instance given a _format_ string and a potentially empty
44    /// list of user provided language tags expected to be in descending order
45    /// of preference.
46    pub fn new(s: &str, tags: Vec<MyLanguageTag>) -> Result<Self, DataError> {
47        let format: FormatParam = FormatParam::from_str(s)?;
48
49        Ok(Format { format, tags })
50    }
51
52    /// Return a new instance w/ an _exact_ format and a potentially empty list
53    /// of user provided language tags expected to be in descending order of
54    /// preference.
55    pub fn from(tags: Vec<MyLanguageTag>) -> Self {
56        Format {
57            format: FormatParam::Exact,
58            tags,
59        }
60    }
61
62    /// Return TRUE if the wrapped _format_ is the `ids` variant.
63    pub fn is_ids(&self) -> bool {
64        matches!(self.format, FormatParam::IDs)
65    }
66
67    /// Return TRUE if the wrapped _format_ is the `exact` variant.
68    pub fn is_exact(&self) -> bool {
69        matches!(self.format, FormatParam::Exact)
70    }
71
72    /// Return TRUE if the wrapped _format_ is the `cnonical` variant.
73    pub fn is_canonical(&self) -> bool {
74        matches!(self.format, FormatParam::Canonical)
75    }
76
77    /// Return a reference to this format key when used as a query parameter.
78    pub fn as_param(&self) -> &FormatParam {
79        &self.format
80    }
81
82    /// Return a reference to the list of language tags provided at
83    /// construction time.
84    pub fn tags(&self) -> &[MyLanguageTag] {
85        self.tags.as_slice()
86    }
87}
88
89/// Possible variants for `format` used to represent the [StatementResult][1]
90/// desired response to a **`GET`** _Statement_ resource.
91///
92/// [1]: crate::StatementResult
93#[derive(Clone, Debug, Deserialize, Serialize)]
94pub enum FormatParam {
95    /// Only include minimum information necessary in [Agent][1], [Activity][2],
96    /// [Verb][3] and [Group][4] Objects to identify them. For _Anonymous Groups_
97    /// this means including the minimum information needed to identify each
98    /// member.
99    ///
100    /// [1]: crate::Agent
101    /// [2]: crate::Activity
102    /// [3]: crate::Verb
103    /// [4]: crate::Group
104    IDs,
105    /// Return [Agent][1], [Activity][2], [Verb][3] and [Group][4] populated
106    /// exactly as they were when the [Statement][5] was received.
107    ///
108    /// [1]: crate::Agent
109    /// [2]: crate::Activity
110    /// [3]: crate::Verb
111    /// [4]: crate::Group
112    /// [5]: crate::Statement
113    Exact,
114    /// Return [Activity][2] and [Verb][3]s with canonical definition of
115    /// Activity Objects and Display of the Verbs as determined by the LRS,
116    /// after applying the "Language Filtering Requirements for Canonical
117    /// Format Statements", and return the original [Agent][1] and [Group][4]
118    /// Objects as in "exact" mode.
119    ///
120    /// [1]: crate::Agent
121    /// [2]: crate::Activity
122    /// [3]: crate::Verb
123    /// [4]: crate::Group
124    Canonical,
125}
126
127impl fmt::Display for FormatParam {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            FormatParam::IDs => write!(f, "ids"),
131            FormatParam::Exact => write!(f, "exact"),
132            FormatParam::Canonical => write!(f, "canonical"),
133        }
134    }
135}
136
137impl FromStr for FormatParam {
138    type Err = DataError;
139
140    /// NOTE (rsn) 20240708 - From [4.2.1 Table Guidelines][1]:
141    /// > The LRS shall reject Statements:
142    /// >   ...
143    /// > * where the case of a value restricted to enumerated values does
144    /// >   not match an enumerated value given in this specification exactly.
145    /// >   ...
146    ///
147    /// [1]: https://opensource.ieee.org/xapi/xapi-base-standard-documentation/-/blob/main/9274.1.1%20xAPI%20Base%20Standard%20for%20LRSs.md#421-table-guidelines
148    ///
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        match s {
151            "ids" => Ok(FormatParam::IDs),
152            "exact" => Ok(FormatParam::Exact),
153            "canonical" => Ok(FormatParam::Canonical),
154            x => {
155                emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
156                    format!("Unknown|invalid ({x}) 'format'").into()
157                )))
158            }
159        }
160    }
161}