xapi_rs/data/
format.rs

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