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 let Some(z_description) = self.description.as_ref() {
73 vec.push(format!("description: {}", z_description));
74 }
75
76 let res = vec
77 .iter()
78 .map(|x| x.to_string())
79 .collect::<Vec<_>>()
80 .join(", ");
81 write!(f, "InteractionComponent{{ {res} }}")
82 }
83}
84
85impl Validate for InteractionComponent {
86 fn validate(&self) -> Vec<ValidationError> {
87 let mut vec = vec![];
88
89 if self.id.is_empty() {
90 vec.push(ValidationError::Empty("id".into()))
91 }
92
93 vec
94 }
95}
96
97impl Canonical for InteractionComponent {
98 fn canonicalize(&mut self, tags: &[MyLanguageTag]) {
99 if let Some(z_description) = self.description.as_mut() {
100 z_description.canonicalize(tags)
101 }
102 }
103}
104
105#[derive(Debug, Default)]
107pub struct InteractionComponentBuilder<'a> {
108 _id: Option<&'a str>,
109 _description: Option<LanguageMap>,
110}
111
112impl<'a> InteractionComponentBuilder<'a> {
113 pub fn id(mut self, val: &'a str) -> Result<Self, DataError> {
117 let val = val.trim();
118 if val.is_empty() {
119 emit_error!(DataError::Validation(ValidationError::Empty("id".into())))
120 } else {
121 self._id = Some(val);
122 Ok(self)
123 }
124 }
125
126 pub fn description(mut self, tag: &MyLanguageTag, label: &str) -> Result<Self, DataError> {
130 add_language!(self._description, tag, label);
131 Ok(self)
132 }
133
134 pub fn build(self) -> Result<InteractionComponent, DataError> {
138 if let Some(z_id) = self._id {
139 Ok(InteractionComponent {
140 id: z_id.to_owned(),
141 description: self._description,
142 })
143 } else {
144 emit_error!(DataError::Validation(ValidationError::MissingField(
145 "id".into()
146 )))
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::str::FromStr;
155 use tracing_test::traced_test;
156
157 #[test]
158 fn test_id_len() -> Result<(), DataError> {
159 let result = InteractionComponent::builder().id("a")?.build();
160 assert!(result.is_ok());
161
162 let result = InteractionComponent::builder().id("");
163 assert!(result.is_err());
164
165 Ok(())
166 }
167
168 #[test]
169 fn test_description() -> Result<(), DataError> {
170 let result = InteractionComponent::builder().id("foo")?.build();
171 assert!(result.is_ok());
172
173 let en = MyLanguageTag::from_str("en")?;
174
175 let ic = InteractionComponent::builder()
176 .id("foo")?
177 .description(&en, "label")?
178 .build()?;
179 assert!(ic.description(&en).is_some());
180 assert_eq!(ic.description(&en).unwrap(), "label");
181
182 Ok(())
183 }
184
185 #[traced_test]
186 #[test]
187 fn test_serde() -> Result<(), DataError> {
188 const JSON: &str = r#"{"id":"foo","description":{"en":"hello","it":"ciao"}}"#;
189
190 let en = MyLanguageTag::from_str("en")?;
191 let it = MyLanguageTag::from_str("it")?;
192
193 let ic = InteractionComponent::builder()
194 .id("foo")?
195 .description(&en, "hello")?
196 .description(&it, "ciao")?
197 .build()?;
198 let se_result = serde_json::to_string(&ic);
199 assert!(se_result.is_ok());
200 let json = se_result.unwrap();
201 assert_eq!(json, JSON);
202
203 let de_result = serde_json::from_str::<InteractionComponent>(JSON);
204 assert!(de_result.is_ok());
205 let ic2 = de_result.unwrap();
206 assert_eq!(ic, ic2);
207
208 Ok(())
209 }
210
211 #[test]
212 fn test_merge_disjoint_collections() -> Result<(), DataError> {
213 let en = MyLanguageTag::from_str("en")?;
214 let it = MyLanguageTag::from_str("it")?;
215
216 let ic1 = InteractionComponent::builder()
217 .id("foo")?
218 .description(&en, "hello")?
219 .build()?;
220 let mut dst = vec![ic1];
221 assert_eq!(dst.len(), 1);
222
223 let ic2 = InteractionComponent::builder()
224 .id("bar")?
225 .description(&it, "ciao")?
226 .build()?;
227 let src = vec![ic2];
228 assert_eq!(src.len(), 1);
229
230 InteractionComponent::merge_collections(&mut dst, src);
231 assert_eq!(dst.len(), 2);
233
234 Ok(())
235 }
236
237 #[test]
238 fn test_merge_collections() -> Result<(), DataError> {
239 let en = MyLanguageTag::from_str("en")?;
240 let it = MyLanguageTag::from_str("it")?;
241 let de = MyLanguageTag::from_str("de")?;
242
243 let ic1 = InteractionComponent::builder()
244 .id("foo")?
245 .description(&en, "hello")?
246 .build()?;
247 let mut dst = vec![ic1];
248 assert_eq!(dst.len(), 1);
249
250 let ic2 = InteractionComponent::builder()
251 .id("foo")?
252 .description(&it, "ciao")?
253 .build()?;
254 let src = vec![ic2];
255 assert_eq!(src.len(), 1);
256
257 InteractionComponent::merge_collections(&mut dst, src);
258 assert_eq!(dst.len(), 1);
260 assert!(dst[0].description.is_some());
262 assert_eq!(dst[0].description.as_ref().unwrap().len(), 2);
263 assert_eq!(dst[0].description(&en), Some("hello"));
264 assert_eq!(dst[0].description(&it), Some("ciao"));
265 assert_eq!(dst[0].description(&de), None);
266
267 Ok(())
268 }
269}