xapi_rs/data/
person.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::{
4    data::{
5        Account, DataError, MyEmailAddress, ObjectType, Validate, ValidationError, validate_sha1sum,
6    },
7    emit_error,
8};
9use core::fmt;
10use email_address::EmailAddress;
11use iri_string::types::UriString;
12use serde::{Deserialize, Serialize};
13use serde_with::skip_serializing_none;
14use std::{collections::HashSet, str::FromStr};
15
16/// Structure used in response to a **`GET`**` _Agents Resource_ request. It
17/// provides aggregated information about one [Agent][1].
18///
19/// Also called a _"Person Object"_ it's very similar to an [Agent][1], but
20/// instead of each attribute being a single value, this one has a list of
21/// them. In addition contrary to n [Agent][1] a [Person] may have more than
22/// of those IFI (Inverse Functional Identifier) fields populated.
23///
24/// [Person] is expected to be used, by an LRS, to associate a person-centric
25/// (aggregated) data, while an [Agent][1] only refers to one _persona_ (one
26/// person in one context).
27///
28/// [1]: crate::Agent
29#[skip_serializing_none]
30#[derive(Debug, Deserialize, PartialEq, Serialize)]
31pub struct Person {
32    #[serde(rename = "objectType")]
33    #[serde(default = "default_object_type")]
34    object_type: ObjectType,
35    name: Vec<String>,
36    mbox: Vec<MyEmailAddress>,
37    mbox_sha1sum: Vec<String>,
38    openid: Vec<UriString>,
39    account: Vec<Account>,
40}
41
42impl Person {
43    /// Return a [Person] _Builder_.
44    pub fn builder() -> PersonBuilder {
45        PersonBuilder::default()
46    }
47
48    /// Return TRUE if the `objectType` property is as expected; FALSE otherwise.
49    pub fn check_object_type(&self) -> bool {
50        self.object_type == ObjectType::Person
51    }
52
53    /// Return the name(s) of this [Person] or `None` if not set.
54    pub fn names(&self) -> &[String] {
55        self.name.as_slice()
56    }
57
58    /// Return the email address(es) of this [Person] or `None` if not set.
59    pub fn mboxes(&self) -> &[MyEmailAddress] {
60        self.mbox.as_slice()
61    }
62
63    /// Return the email hash-sum(s) of this [Person] or `None` if not set.
64    pub fn mbox_sha1sums(&self) -> &[String] {
65        self.mbox_sha1sum.as_slice()
66    }
67
68    /// Return the OpenID(s) of this [Person] or `None` if not set.
69    pub fn openids(&self) -> &[UriString] {
70        self.openid.as_slice()
71    }
72
73    /// Return the account(s) of this [Person] or `None` if not set.
74    pub fn accounts(&self) -> &[Account] {
75        self.account.as_slice()
76    }
77
78    /// Return a representation of an unknown Person; i.e. one w/ no IFIs.
79    pub fn unknown() -> Self {
80        Person {
81            object_type: ObjectType::Person,
82            name: vec![],
83            mbox: vec![],
84            mbox_sha1sum: vec![],
85            openid: vec![],
86            account: vec![],
87        }
88    }
89}
90
91impl fmt::Display for Person {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        let mut vec = vec![];
94
95        let y: Vec<_> = self.name.iter().map(|x| x.to_string()).collect();
96        if !y.is_empty() {
97            vec.push(format!("\"name\": [{}]", y.join(", ")))
98        }
99        let y: Vec<_> = self.mbox.iter().map(|x| x.as_ref().to_string()).collect();
100        if !y.is_empty() {
101            vec.push(format!("\"mbox\": [{}]", y.join(", ")))
102        }
103        let y: Vec<_> = self.mbox_sha1sum.iter().map(|x| x.to_string()).collect();
104        if !y.is_empty() {
105            vec.push(format!("\"mbox_sha1sum\": [{}]", y.join(", ")))
106        }
107        let y: Vec<_> = self.openid.iter().map(|x| x.to_string()).collect();
108        if !y.is_empty() {
109            vec.push(format!("\"openid\": [{}]", y.join(", ")))
110        }
111        let y: Vec<_> = self.account.iter().map(|x| x.to_string()).collect();
112        if !y.is_empty() {
113            vec.push(format!("\"account\": [{}]", y.join(", ")))
114        }
115
116        let res = vec
117            .iter()
118            .map(|x| x.to_string())
119            .collect::<Vec<_>>()
120            .join(", ");
121        write!(f, "Person{{ {res} }}")
122    }
123}
124
125impl Validate for Person {
126    fn validate(&self) -> Vec<ValidationError> {
127        let mut vec = vec![];
128
129        if !self.check_object_type() {
130            vec.push(ValidationError::WrongObjectType {
131                expected: ObjectType::Agent,
132                found: self.object_type.to_string().into(),
133            })
134        }
135        self.name.iter().for_each(|x| {
136            if x.trim().is_empty() {
137                vec.push(ValidationError::Empty("name".into()))
138            }
139        });
140        self.mbox_sha1sum.iter().for_each(|x| {
141            if x.trim().is_empty() {
142                vec.push(ValidationError::Empty("mbox_sha1sum".into()))
143            } else {
144                validate_sha1sum(x).unwrap_or_else(|x| vec.push(x))
145            }
146        });
147        self.account
148            .iter()
149            .for_each(|x| x.check_validity().unwrap_or_else(|x| vec.push(x)));
150
151        vec
152    }
153}
154
155/// A Type that knows how to construct a [Person].
156#[derive(Default, Debug)]
157pub struct PersonBuilder {
158    _name: HashSet<String>,
159    _mbox: HashSet<MyEmailAddress>,
160    _mbox_sha1sum: HashSet<String>,
161    _openid: HashSet<UriString>,
162    _account: HashSet<Account>,
163}
164
165impl PersonBuilder {
166    /// Add another name/id to this [Person].
167    ///
168    /// Raise [DataError] if the argument is empty.
169    pub fn name(mut self, val: &str) -> Result<Self, DataError> {
170        let val = val.trim();
171        if val.is_empty() {
172            emit_error!(DataError::Validation(ValidationError::Empty("name".into())))
173        }
174        self._name.insert(val.to_owned());
175        Ok(self)
176    }
177
178    /// Add another email address (optionally w/ a `mailto` scheme) to
179    /// this [Person].
180    ///
181    /// Raise [DataError] if the argument is invalid.
182    pub fn mbox(mut self, val: &str) -> Result<Self, DataError> {
183        let val = val.trim();
184        if val.is_empty() {
185            emit_error!(DataError::Validation(ValidationError::Empty("mbox".into())))
186        }
187        // is it valid?
188        let email = if let Some(x) = val.strip_prefix("mailto:") {
189            EmailAddress::from_str(x)?
190        } else {
191            EmailAddress::from_str(val)?
192        };
193        self._mbox.insert(MyEmailAddress::from(email));
194        Ok(self)
195    }
196
197    /// Add another email address mailto URI hash to this [Person].
198    ///
199    /// Raise [DataError] if the argument is invalid.
200    pub fn mbox_sha1sum(mut self, val: &str) -> Result<Self, DataError> {
201        let val = val.trim();
202        if val.is_empty() {
203            emit_error!(DataError::Validation(ValidationError::Empty(
204                "mbox_sha1sum".into()
205            )))
206        }
207        // is it valid?
208        validate_sha1sum(val)?;
209        self._mbox_sha1sum.insert(val.to_owned());
210        Ok(self)
211    }
212
213    /// Add another OpenID to this [Person].
214    ///
215    /// Raise [DataError] if the argument is invalid.
216    pub fn openid(mut self, val: &str) -> Result<Self, DataError> {
217        let val = val.trim();
218        if val.is_empty() {
219            emit_error!(DataError::Validation(ValidationError::Empty(
220                "openid".into()
221            )))
222        }
223        let uri = UriString::from_str(val)?;
224        self._openid.insert(uri);
225        Ok(self)
226    }
227
228    /// Add another [Account] to this [Person].
229    ///
230    /// Raise [DataError] if the argument is invalid.
231    pub fn account(mut self, val: Account) -> Result<Self, DataError> {
232        val.check_validity()?;
233        self._account.insert(val);
234        Ok(self)
235    }
236
237    /// Create a [Person] instance.
238    ///
239    /// Raise [DataError] if an inconsistency is discovered.
240    pub fn build(self) -> Result<Person, DataError> {
241        Ok(Person {
242            object_type: ObjectType::Person,
243            name: self._name.into_iter().collect(),
244            mbox: self._mbox.into_iter().collect(),
245            mbox_sha1sum: self._mbox_sha1sum.into_iter().collect(),
246            openid: self._openid.into_iter().collect(),
247            account: self._account.into_iter().collect(),
248        })
249    }
250}
251
252fn default_object_type() -> ObjectType {
253    ObjectType::Person
254}