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}