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