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}