use crate::{
MyLanguageTag, add_language,
data::{Canonical, DataError, LanguageMap, Validate, ValidationError},
emit_error, merge_maps,
};
use core::fmt;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct InteractionComponent {
id: String,
description: Option<LanguageMap>,
}
impl InteractionComponent {
pub fn builder() -> InteractionComponentBuilder<'static> {
InteractionComponentBuilder::default()
}
pub fn id(&self) -> &str {
&self.id
}
pub fn description(&self, tag: &MyLanguageTag) -> Option<&str> {
match &self.description {
Some(lm) => lm.get(tag),
None => None,
}
}
pub(crate) fn merge_collections(
dst: &mut Vec<InteractionComponent>,
src: Vec<InteractionComponent>,
) {
for src_ic in src {
match dst.iter().position(|x| x.id == src_ic.id) {
Some(n) => {
let dst_ic = &mut dst[n];
merge_maps!(&mut dst_ic.description, src_ic.description);
}
None => dst.push(src_ic),
}
}
}
}
impl fmt::Display for InteractionComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut vec = vec![];
vec.push(format!("id: \"{}\"", self.id));
if let Some(z_description) = self.description.as_ref() {
vec.push(format!("description: {}", z_description));
}
let res = vec
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
write!(f, "InteractionComponent{{ {res} }}")
}
}
impl Validate for InteractionComponent {
fn validate(&self) -> Vec<ValidationError> {
let mut vec = vec![];
if self.id.is_empty() {
vec.push(ValidationError::Empty("id".into()))
}
vec
}
}
impl Canonical for InteractionComponent {
fn canonicalize(&mut self, tags: &[MyLanguageTag]) {
if let Some(z_description) = self.description.as_mut() {
z_description.canonicalize(tags)
}
}
}
#[derive(Debug, Default)]
pub struct InteractionComponentBuilder<'a> {
_id: Option<&'a str>,
_description: Option<LanguageMap>,
}
impl<'a> InteractionComponentBuilder<'a> {
pub fn id(mut self, val: &'a str) -> Result<Self, DataError> {
let val = val.trim();
if val.is_empty() {
emit_error!(DataError::Validation(ValidationError::Empty("id".into())))
} else {
self._id = Some(val);
Ok(self)
}
}
pub fn description(mut self, tag: &MyLanguageTag, label: &str) -> Result<Self, DataError> {
add_language!(self._description, tag, label);
Ok(self)
}
pub fn build(self) -> Result<InteractionComponent, DataError> {
if let Some(z_id) = self._id {
Ok(InteractionComponent {
id: z_id.to_owned(),
description: self._description,
})
} else {
emit_error!(DataError::Validation(ValidationError::MissingField(
"id".into()
)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use tracing_test::traced_test;
#[test]
fn test_id_len() -> Result<(), DataError> {
let result = InteractionComponent::builder().id("a")?.build();
assert!(result.is_ok());
let result = InteractionComponent::builder().id("");
assert!(result.is_err());
Ok(())
}
#[test]
fn test_description() -> Result<(), DataError> {
let result = InteractionComponent::builder().id("foo")?.build();
assert!(result.is_ok());
let en = MyLanguageTag::from_str("en")?;
let ic = InteractionComponent::builder()
.id("foo")?
.description(&en, "label")?
.build()?;
assert!(ic.description(&en).is_some());
assert_eq!(ic.description(&en).unwrap(), "label");
Ok(())
}
#[traced_test]
#[test]
fn test_serde() -> Result<(), DataError> {
const JSON: &str = r#"{"id":"foo","description":{"en":"hello","it":"ciao"}}"#;
let en = MyLanguageTag::from_str("en")?;
let it = MyLanguageTag::from_str("it")?;
let ic = InteractionComponent::builder()
.id("foo")?
.description(&en, "hello")?
.description(&it, "ciao")?
.build()?;
let se_result = serde_json::to_string(&ic);
assert!(se_result.is_ok());
let json = se_result.unwrap();
assert_eq!(json, JSON);
let de_result = serde_json::from_str::<InteractionComponent>(JSON);
assert!(de_result.is_ok());
let ic2 = de_result.unwrap();
assert_eq!(ic, ic2);
Ok(())
}
#[test]
fn test_merge_disjoint_collections() -> Result<(), DataError> {
let en = MyLanguageTag::from_str("en")?;
let it = MyLanguageTag::from_str("it")?;
let ic1 = InteractionComponent::builder()
.id("foo")?
.description(&en, "hello")?
.build()?;
let mut dst = vec![ic1];
assert_eq!(dst.len(), 1);
let ic2 = InteractionComponent::builder()
.id("bar")?
.description(&it, "ciao")?
.build()?;
let src = vec![ic2];
assert_eq!(src.len(), 1);
InteractionComponent::merge_collections(&mut dst, src);
assert_eq!(dst.len(), 2);
Ok(())
}
#[test]
fn test_merge_collections() -> Result<(), DataError> {
let en = MyLanguageTag::from_str("en")?;
let it = MyLanguageTag::from_str("it")?;
let de = MyLanguageTag::from_str("de")?;
let ic1 = InteractionComponent::builder()
.id("foo")?
.description(&en, "hello")?
.build()?;
let mut dst = vec![ic1];
assert_eq!(dst.len(), 1);
let ic2 = InteractionComponent::builder()
.id("foo")?
.description(&it, "ciao")?
.build()?;
let src = vec![ic2];
assert_eq!(src.len(), 1);
InteractionComponent::merge_collections(&mut dst, src);
assert_eq!(dst.len(), 1);
assert!(dst[0].description.is_some());
assert_eq!(dst[0].description.as_ref().unwrap().len(), 2);
assert_eq!(dst[0].description(&en), Some("hello"));
assert_eq!(dst[0].description(&it), Some("ciao"));
assert_eq!(dst[0].description(&de), None);
Ok(())
}
}