Skip to main content

xapi_data/
person.rs

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