xapi_rs/data/
context_group.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::{
4    GroupId,
5    data::{DataError, Fingerprint, Group, ObjectType, Validate, ValidationError},
6    emit_error,
7};
8use core::fmt;
9use iri_string::types::{IriStr, IriString};
10use serde::{Deserialize, Serialize};
11use serde_with::skip_serializing_none;
12use std::hash::{Hash, Hasher};
13
14/// Similar to [ContextAgent][1] this structure captures a relationship between
15/// a [Statement][1] and one or more [Group][2](s) --besides the [Actor][3]--
16/// in order to properly describe an experience.
17///
18/// [1]: crate::ContextAgent
19/// [2]: crate::Statement
20/// [3]: crate::Group
21/// [4]: crate::Actor
22#[skip_serializing_none]
23#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
24#[serde(deny_unknown_fields)]
25#[serde(rename_all = "camelCase")]
26pub struct ContextGroup {
27    #[serde(default = "default_object_type")]
28    object_type: ObjectType,
29    group: Group,
30    relevant_types: Vec<IriString>,
31}
32
33#[skip_serializing_none]
34#[derive(Debug, Serialize)]
35#[serde(rename_all = "camelCase")]
36pub(crate) struct ContextGroupId {
37    object_type: ObjectType,
38    group: GroupId,
39    relevant_types: Vec<IriString>,
40}
41
42impl From<ContextGroup> for ContextGroupId {
43    fn from(value: ContextGroup) -> Self {
44        ContextGroupId {
45            object_type: ObjectType::ContextGroup,
46            group: GroupId::from(value.group),
47            relevant_types: value.relevant_types,
48        }
49    }
50}
51
52impl From<ContextGroupId> for ContextGroup {
53    fn from(value: ContextGroupId) -> Self {
54        ContextGroup {
55            object_type: ObjectType::ContextGroup,
56            group: Group::from(value.group),
57            relevant_types: value.relevant_types,
58        }
59    }
60}
61
62impl ContextGroup {
63    /// Return a [ContextGroup] _Builder_
64    pub fn builder() -> ContextGroupBuilder {
65        ContextGroupBuilder::default()
66    }
67
68    /// Return TRUE if the `objectType` property is [ContextGroup][1]; FALSE
69    /// otherwise.
70    ///
71    /// [1]: ObjectType#variant.ContextGroup
72    pub fn check_object_type(&self) -> bool {
73        self.object_type == ObjectType::ContextGroup
74    }
75
76    /// Return `group` field.
77    pub fn group(&self) -> &Group {
78        &self.group
79    }
80
81    /// Return `relevant_types` field as an array of IRIs.
82    pub fn relevant_types(&self) -> &[IriString] {
83        self.relevant_types.as_ref()
84    }
85}
86
87impl Fingerprint for ContextGroup {
88    fn fingerprint<H: Hasher>(&self, state: &mut H) {
89        self.group.fingerprint(state);
90        for s in &self.relevant_types {
91            s.hash(state)
92        }
93    }
94}
95
96impl fmt::Display for ContextGroup {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        let mut vec = vec![];
99
100        vec.push(format!("group: {}", self.group));
101        vec.push(format!(
102            "relevantTypes: [{}]",
103            self.relevant_types
104                .iter()
105                .map(|x| x.to_string())
106                .collect::<Vec<_>>()
107                .join(", ")
108        ));
109
110        let res = vec
111            .iter()
112            .map(|x| x.to_string())
113            .collect::<Vec<_>>()
114            .join(", ");
115        write!(f, "ContextAgent{{ {res} }}")
116    }
117}
118
119impl Validate for ContextGroup {
120    fn validate(&self) -> Vec<ValidationError> {
121        let mut vec = vec![];
122
123        if !self.check_object_type() {
124            vec.push(ValidationError::WrongObjectType {
125                expected: ObjectType::ContextGroup,
126                found: self.object_type.to_string().into(),
127            })
128        }
129        vec.extend(self.group.validate());
130        if self.relevant_types.is_empty() {
131            vec.push(ValidationError::Empty("relevant_types".into()))
132        } else {
133            for iri in self.relevant_types.iter() {
134                if iri.is_empty() {
135                    vec.push(ValidationError::InvalidIRI(iri.to_string().into()))
136                }
137            }
138        }
139
140        vec
141    }
142}
143
144/// A Type that knows how to construct a [ContextGroup].
145#[derive(Debug, Default)]
146pub struct ContextGroupBuilder {
147    _group: Option<Group>,
148    _relevant_types: Vec<IriString>,
149}
150
151impl ContextGroupBuilder {
152    /// Set the `group` field.
153    ///
154    /// Raise [DataError] if [Group] argument is invalid.
155    pub fn group(mut self, val: Group) -> Result<Self, DataError> {
156        val.check_validity()?;
157        self._group = Some(val);
158        Ok(self)
159    }
160
161    /// Add IRI string to `relevant_types` collection if it's not empty.
162    ///
163    /// Raise [DataError] if it is.
164    pub fn relevant_type(mut self, val: &str) -> Result<Self, DataError> {
165        if val.is_empty() {
166            emit_error!(DataError::Validation(ValidationError::Empty(
167                "relevant_type IRI".into()
168            )))
169        } else {
170            let iri = IriStr::new(val)?;
171            if self._relevant_types.is_empty() {
172                self._relevant_types = vec![];
173            }
174            self._relevant_types.push(iri.to_owned());
175            Ok(self)
176        }
177    }
178
179    /// Construct a [ContextGroup] instance.
180    ///
181    /// Raise [DataError] if `agent` field is not set.
182    pub fn build(self) -> Result<ContextGroup, DataError> {
183        if self._group.is_none() {
184            emit_error!(DataError::Validation(ValidationError::MissingField(
185                "group".into()
186            )))
187        } else if self._relevant_types.is_empty() {
188            emit_error!(DataError::Validation(ValidationError::Empty(
189                "relevant_types".into()
190            )))
191        } else {
192            let mut relevant_types = vec![];
193            for item in self._relevant_types.iter() {
194                let iri = IriString::try_from(item.as_str())?;
195                relevant_types.push(iri);
196            }
197
198            Ok(ContextGroup {
199                object_type: ObjectType::ContextGroup,
200                group: self._group.unwrap(),
201                relevant_types,
202            })
203        }
204    }
205}
206
207fn default_object_type() -> ObjectType {
208    ObjectType::ContextGroup
209}