Skip to main content

xapi_data/
context_group.rs

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