xapi_rs/data/
interaction_component.rs1use crate::{
4 MyLanguageTag, add_language,
5 data::{Canonical, DataError, LanguageMap, Validate, ValidationError},
6 emit_error, merge_maps,
7};
8use core::fmt;
9use serde::{Deserialize, Serialize};
10use serde_with::skip_serializing_none;
11
12#[skip_serializing_none]
19#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
20pub struct InteractionComponent {
21 id: String,
22 description: Option<LanguageMap>,
23}
24
25impl InteractionComponent {
26 pub fn builder() -> InteractionComponentBuilder<'static> {
28 InteractionComponentBuilder::default()
29 }
30
31 pub fn id(&self) -> &str {
33 &self.id
34 }
35
36 pub fn description(&self, tag: &MyLanguageTag) -> Option<&str> {
40 match &self.description {
41 Some(lm) => lm.get(tag),
42 None => None,
43 }
44 }
45
46 pub(crate) fn merge_collections(
52 dst: &mut Vec<InteractionComponent>,
53 src: Vec<InteractionComponent>,
54 ) {
55 for src_ic in src {
56 match dst.iter().position(|x| x.id == src_ic.id) {
57 Some(n) => {
58 let dst_ic = &mut dst[n];
59 merge_maps!(&mut dst_ic.description, src_ic.description);
60 }
61 None => dst.push(src_ic),
62 }
63 }
64 }
65}
66
67impl fmt::Display for InteractionComponent {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 let mut vec = vec![];
70
71 vec.push(format!("id: \"{}\"", self.id));
72 if self.description.is_some() {
73 vec.push(format!(
74 "description: {}",
75 self.description.as_ref().unwrap()
76 ));
77 }
78
79 let res = vec
80 .iter()
81 .map(|x| x.to_string())
82 .collect::<Vec<_>>()
83 .join(", ");
84 write!(f, "InteractionComponent{{ {res} }}")
85 }
86}
87
88impl Validate for InteractionComponent {
89 fn validate(&self) -> Vec<ValidationError> {
90 let mut vec = vec![];
91
92 if self.id.is_empty() {
93 vec.push(ValidationError::Empty("id".into()))
94 }
95
96 vec
97 }
98}
99
100impl Canonical for InteractionComponent {
101 fn canonicalize(&mut self, tags: &[MyLanguageTag]) {
102 if self.description.is_some() {
103 self.description.as_mut().unwrap().canonicalize(tags)
104 }
105 }
106}
107
108#[derive(Debug, Default)]
110pub struct InteractionComponentBuilder<'a> {
111 _id: Option<&'a str>,
112 _description: Option<LanguageMap>,
113}
114
115impl<'a> InteractionComponentBuilder<'a> {
116 pub fn id(mut self, val: &'a str) -> Result<Self, DataError> {
120 let val = val.trim();
121 if val.is_empty() {
122 emit_error!(DataError::Validation(ValidationError::Empty("id".into())))
123 } else {
124 self._id = Some(val);
125 Ok(self)
126 }
127 }
128
129 pub fn description(mut self, tag: &MyLanguageTag, label: &str) -> Result<Self, DataError> {
133 add_language!(self._description, tag, label);
134 Ok(self)
135 }
136
137 pub fn build(self) -> Result<InteractionComponent, DataError> {
141 if self._id.is_none() {
142 emit_error!(DataError::Validation(ValidationError::MissingField(
143 "id".into()
144 )))
145 } else {
146 Ok(InteractionComponent {
147 id: self._id.unwrap().to_owned(),
148 description: self._description,
149 })
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use std::str::FromStr;
158 use tracing_test::traced_test;
159
160 #[test]
161 fn test_id_len() -> Result<(), DataError> {
162 let result = InteractionComponent::builder().id("a")?.build();
163 assert!(result.is_ok());
164
165 let result = InteractionComponent::builder().id("");
166 assert!(result.is_err());
167
168 Ok(())
169 }
170
171 #[test]
172 fn test_description() -> Result<(), DataError> {
173 let result = InteractionComponent::builder().id("foo")?.build();
174 assert!(result.is_ok());
175
176 let en = MyLanguageTag::from_str("en")?;
177
178 let ic = InteractionComponent::builder()
179 .id("foo")?
180 .description(&en, "label")?
181 .build()?;
182 assert!(ic.description(&en).is_some());
183 assert_eq!(ic.description(&en).unwrap(), "label");
184
185 Ok(())
186 }
187
188 #[traced_test]
189 #[test]
190 fn test_serde() -> Result<(), DataError> {
191 const JSON: &str = r#"{"id":"foo","description":{"en":"hello","it":"ciao"}}"#;
192
193 let en = MyLanguageTag::from_str("en")?;
194 let it = MyLanguageTag::from_str("it")?;
195
196 let ic = InteractionComponent::builder()
197 .id("foo")?
198 .description(&en, "hello")?
199 .description(&it, "ciao")?
200 .build()?;
201 let se_result = serde_json::to_string(&ic);
202 assert!(se_result.is_ok());
203 let json = se_result.unwrap();
204 assert_eq!(json, JSON);
205
206 let de_result = serde_json::from_str::<InteractionComponent>(JSON);
207 assert!(de_result.is_ok());
208 let ic2 = de_result.unwrap();
209 assert_eq!(ic, ic2);
210
211 Ok(())
212 }
213
214 #[test]
215 fn test_merge_disjoint_collections() -> Result<(), DataError> {
216 let en = MyLanguageTag::from_str("en")?;
217 let it = MyLanguageTag::from_str("it")?;
218
219 let ic1 = InteractionComponent::builder()
220 .id("foo")?
221 .description(&en, "hello")?
222 .build()?;
223 let mut dst = vec![ic1];
224 assert_eq!(dst.len(), 1);
225
226 let ic2 = InteractionComponent::builder()
227 .id("bar")?
228 .description(&it, "ciao")?
229 .build()?;
230 let src = vec![ic2];
231 assert_eq!(src.len(), 1);
232
233 InteractionComponent::merge_collections(&mut dst, src);
234 assert_eq!(dst.len(), 2);
236
237 Ok(())
238 }
239
240 #[test]
241 fn test_merge_collections() -> Result<(), DataError> {
242 let en = MyLanguageTag::from_str("en")?;
243 let it = MyLanguageTag::from_str("it")?;
244 let de = MyLanguageTag::from_str("de")?;
245
246 let ic1 = InteractionComponent::builder()
247 .id("foo")?
248 .description(&en, "hello")?
249 .build()?;
250 let mut dst = vec![ic1];
251 assert_eq!(dst.len(), 1);
252
253 let ic2 = InteractionComponent::builder()
254 .id("foo")?
255 .description(&it, "ciao")?
256 .build()?;
257 let src = vec![ic2];
258 assert_eq!(src.len(), 1);
259
260 InteractionComponent::merge_collections(&mut dst, src);
261 assert_eq!(dst.len(), 1);
263 assert!(dst[0].description.is_some());
265 assert_eq!(dst[0].description.as_ref().unwrap().len(), 2);
266 assert_eq!(dst[0].description(&en), Some("hello"));
267 assert_eq!(dst[0].description(&it), Some("ciao"));
268 assert_eq!(dst[0].description(&de), None);
269
270 Ok(())
271 }
272}