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