1use crate::{
4 Account, CIString, DataError, Fingerprint, MyEmailAddress, ObjectType, Validate,
5 ValidationError, check_for_nulls, emit_error, fingerprint_it, set_email, validate_sha1sum,
6};
7use core::fmt;
8use iri_string::types::{UriStr, UriString};
9use serde::{Deserialize, Serialize};
10use serde_json::{Map, Value};
11use serde_with::skip_serializing_none;
12use std::{
13 cmp::Ordering,
14 hash::{Hash, Hasher},
15 str::FromStr,
16};
17
18#[skip_serializing_none]
21#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
22#[serde(deny_unknown_fields)]
23pub struct Agent {
24 #[serde(rename = "objectType")]
25 object_type: Option<ObjectType>,
26 name: Option<CIString>,
27 mbox: Option<MyEmailAddress>,
28 mbox_sha1sum: Option<String>,
29 openid: Option<UriString>,
30 account: Option<Account>,
31}
32
33#[skip_serializing_none]
34#[derive(Debug, Serialize)]
35pub(crate) struct AgentId {
36 mbox: Option<MyEmailAddress>,
37 mbox_sha1sum: Option<String>,
38 openid: Option<UriString>,
39 account: Option<Account>,
40}
41
42impl From<Agent> for AgentId {
43 fn from(value: Agent) -> Self {
44 AgentId {
45 mbox: value.mbox,
46 mbox_sha1sum: value.mbox_sha1sum,
47 openid: value.openid,
48 account: value.account,
49 }
50 }
51}
52
53impl From<AgentId> for Agent {
54 fn from(value: AgentId) -> Self {
55 Agent {
56 object_type: None,
57 name: None,
58 mbox: value.mbox,
59 mbox_sha1sum: value.mbox_sha1sum,
60 openid: value.openid,
61 account: value.account,
62 }
63 }
64}
65
66impl Agent {
67 pub fn from_json_obj(map: Map<String, Value>) -> Result<Self, DataError> {
69 for (k, v) in &map {
70 if v.is_null() {
71 emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
72 format!("Key '{k}' is null").into()
73 )))
74 } else {
75 check_for_nulls(v)?
76 }
77 }
78 let agent: Agent = serde_json::from_value(Value::Object(map))?;
80 agent.check_validity()?;
81 Ok(agent)
82 }
83
84 pub fn builder() -> AgentBuilder {
86 AgentBuilder::default()
87 }
88
89 pub fn check_object_type(&self) -> bool {
91 if let Some(z_object_type) = self.object_type.as_ref() {
92 z_object_type == &ObjectType::Agent
93 } else {
94 true
95 }
96 }
97
98 pub fn name(&self) -> Option<&CIString> {
100 self.name.as_ref()
101 }
102
103 pub fn name_as_str(&self) -> Option<&str> {
105 self.name.as_deref()
106 }
107
108 pub fn mbox(&self) -> Option<&MyEmailAddress> {
110 self.mbox.as_ref()
111 }
112
113 pub fn mbox_sha1sum(&self) -> Option<&str> {
116 self.mbox_sha1sum.as_deref()
117 }
118
119 pub fn openid(&self) -> Option<&UriStr> {
121 self.openid.as_deref()
122 }
123
124 pub fn account(&self) -> Option<&Account> {
127 self.account.as_ref()
128 }
129
130 pub fn uid(&self) -> u64 {
132 fingerprint_it(self)
133 }
134
135 pub fn equivalent(&self, that: &Agent) -> bool {
137 self.uid() == that.uid()
138 }
139}
140
141impl Ord for Agent {
142 fn cmp(&self, other: &Self) -> Ordering {
143 fingerprint_it(self).cmp(&fingerprint_it(other))
144 }
145}
146
147impl PartialOrd for Agent {
148 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
149 Some(self.cmp(other))
150 }
151}
152
153impl FromStr for Agent {
154 type Err = DataError;
155
156 fn from_str(s: &str) -> Result<Self, Self::Err> {
157 let map: Map<String, Value> = serde_json::from_str(s)?;
158 Self::from_json_obj(map)
159 }
160}
161
162impl fmt::Display for Agent {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 let mut vec = vec![];
165 if self.name.is_some() {
166 vec.push(format!("name: \"{}\"", self.name().unwrap()));
167 }
168 if self.mbox.is_some() {
169 vec.push(format!("mbox: \"{}\"", self.mbox().unwrap()));
170 }
171 if self.mbox_sha1sum.is_some() {
172 vec.push(format!(
173 "mbox_sha1sum: \"{}\"",
174 self.mbox_sha1sum().unwrap()
175 ));
176 }
177 if self.account.is_some() {
178 vec.push(format!("account: {}", self.account().unwrap()));
179 }
180 if self.openid.is_some() {
181 vec.push(format!("openid: \"{}\"", self.openid().unwrap()));
182 }
183 let res = vec
184 .iter()
185 .map(|x| x.to_string())
186 .collect::<Vec<_>>()
187 .join(", ");
188 write!(f, "Agent{{ {res} }}")
189 }
190}
191
192impl Fingerprint for Agent {
193 fn fingerprint<H: Hasher>(&self, state: &mut H) {
194 if let Some(z_mbox) = self.mbox.as_ref() {
196 z_mbox.fingerprint(state);
197 }
198 self.mbox_sha1sum.hash(state);
199 self.openid.hash(state);
200 if let Some(z_account) = self.account.as_ref() {
201 z_account.fingerprint(state);
202 }
203 }
204}
205
206impl Validate for Agent {
207 fn validate(&self) -> Vec<ValidationError> {
208 let mut vec = vec![];
209
210 if let Some(z_object_type) = self.object_type.as_ref()
211 && z_object_type != &ObjectType::Agent
212 {
213 vec.push(ValidationError::WrongObjectType {
214 expected: ObjectType::Agent,
215 found: z_object_type.to_string().into(),
216 })
217 }
218 if self.name.is_some() && self.name.as_ref().unwrap().is_empty() {
219 vec.push(ValidationError::Empty("name".into()))
220 }
221 let mut count = 0;
224 if self.mbox.is_some() {
225 count += 1;
226 }
228 if let Some(z_mbox_sha1sum) = self.mbox_sha1sum.as_ref() {
229 count += 1;
230 validate_sha1sum(z_mbox_sha1sum).unwrap_or_else(|x| vec.push(x))
231 }
232 if self.openid.is_some() {
233 count += 1;
234 }
235 if let Some(z_account) = self.account.as_ref() {
236 count += 1;
237 vec.extend(z_account.validate())
238 }
239 if count != 1 {
240 vec.push(ValidationError::ConstraintViolation(
241 "Exactly 1 IFI is required".into(),
242 ))
243 }
244
245 vec
246 }
247}
248
249#[derive(Debug, Default)]
251pub struct AgentBuilder {
252 _object_type: Option<ObjectType>,
253 _name: Option<CIString>,
254 _mbox: Option<MyEmailAddress>,
255 _sha1sum: Option<String>,
256 _openid: Option<UriString>,
257 _account: Option<Account>,
258}
259
260impl AgentBuilder {
261 pub fn with_object_type(mut self) -> Self {
263 self._object_type = Some(ObjectType::Agent);
264 self
265 }
266
267 pub fn name(mut self, s: &str) -> Result<Self, DataError> {
271 let s = s.trim();
272 if s.is_empty() {
273 emit_error!(DataError::Validation(ValidationError::Empty("name".into())))
274 }
275 self._name = Some(CIString::from(s));
276 Ok(self)
277 }
278
279 pub fn mbox(mut self, s: &str) -> Result<Self, DataError> {
287 set_email!(self, s)
288 }
289
290 pub fn mbox_sha1sum(mut self, s: &str) -> Result<Self, DataError> {
298 let s = s.trim();
299 if s.is_empty() {
300 emit_error!(DataError::Validation(ValidationError::Empty(
301 "mbox_sha1sum".into()
302 )))
303 }
304
305 validate_sha1sum(s)?;
306 self._sha1sum = Some(s.to_owned());
307 self._mbox = None;
308 self._openid = None;
309 self._account = None;
310 Ok(self)
311 }
312
313 pub fn openid(mut self, s: &str) -> Result<Self, DataError> {
321 let s = s.trim();
322 if s.is_empty() {
323 emit_error!(DataError::Validation(ValidationError::Empty(
324 "openid".into()
325 )))
326 }
327
328 let uri = UriString::from_str(s)?;
329 self._openid = Some(uri);
330 self._mbox = None;
331 self._sha1sum = None;
332 self._account = None;
333 Ok(self)
334 }
335
336 pub fn account(mut self, val: Account) -> Result<Self, DataError> {
343 val.check_validity()?;
344 self._account = Some(val);
345 self._mbox = None;
346 self._sha1sum = None;
347 self._openid = None;
348 Ok(self)
349 }
350
351 pub fn build(self) -> Result<Agent, DataError> {
355 if self._mbox.is_none()
356 && self._sha1sum.is_none()
357 && self._openid.is_none()
358 && self._account.is_none()
359 {
360 emit_error!(DataError::Validation(ValidationError::MissingIFI(
361 "Agent".into(),
362 )));
363 }
364
365 Ok(Agent {
366 object_type: self._object_type,
367 name: self._name,
368 mbox: self._mbox,
369 mbox_sha1sum: self._sha1sum,
370 openid: self._openid,
371 account: self._account,
372 })
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use tracing_test::traced_test;
380
381 #[test]
382 fn test_serde() -> Result<(), DataError> {
383 const JSON: &str =
384 r#"{"objectType":"Agent","name":"Z User","mbox":"mailto:zuser@inter.net"}"#;
385
386 let a1 = Agent::builder()
387 .with_object_type()
388 .name("Z User")?
389 .mbox("zuser@inter.net")?
390 .build()?;
391 let se_result = serde_json::to_string(&a1);
392 assert!(se_result.is_ok());
393 let json = se_result.unwrap();
394 assert_eq!(json, JSON);
395
396 let de_result = serde_json::from_str::<Agent>(JSON);
397 assert!(de_result.is_ok());
398 let a2 = de_result.unwrap();
399
400 assert_eq!(a1, a2);
401
402 Ok(())
403 }
404
405 #[test]
406 fn test_camel_and_snake() {
407 const JSON: &str = r#"{
408 "objectType": "Agent",
409 "name": "Ena Hills",
410 "mbox": "mailto:ena.hills@example.com",
411 "mbox_sha1sum": "ebd31e95054c018b10727ccffd2ef2ec3a016ee9",
412 "account": {
413 "homePage": "http://www.example.com",
414 "name": "13936749"
415 },
416 "openid": "http://toby.openid.example.org/"
417 }"#;
418 let de_result = serde_json::from_str::<Agent>(JSON);
419 assert!(de_result.is_ok());
420 let a = de_result.unwrap();
421
422 assert!(a.check_object_type());
423 assert!(a.name().is_some());
424 assert_eq!(a.name().unwrap(), &CIString::from("ena hills"));
425 assert_eq!(a.name_as_str().unwrap(), "Ena Hills");
426 assert!(a.mbox().is_some());
427 assert_eq!(a.mbox().unwrap().to_uri(), "mailto:ena.hills@example.com");
428 assert!(a.mbox_sha1sum().is_some());
429 assert_eq!(
430 a.mbox_sha1sum().unwrap(),
431 "ebd31e95054c018b10727ccffd2ef2ec3a016ee9"
432 );
433 assert!(a.account().is_some());
434 let act = a.account().unwrap();
435 assert_eq!(act.home_page_as_str(), "http://www.example.com");
436 assert_eq!(act.name(), "13936749");
437 assert!(a.openid().is_some());
438 assert_eq!(
439 a.openid().unwrap().to_string(),
440 "http://toby.openid.example.org/"
441 );
442 }
443
444 #[traced_test]
445 #[test]
446 fn test_validate() {
447 const JSON1: &str =
448 r#"{"objectType":"Agent","name":"Z User","openid":"http://résumé.net/zuser"}"#;
449
450 let de_result = serde_json::from_str::<Agent>(JSON1);
451 assert!(de_result.as_ref().is_err_and(|x| x.is_data()));
453 let de_err = de_result.err().unwrap();
454 let (line, col) = (de_err.line(), de_err.column());
455 assert_eq!(line, 1);
456 assert_eq!(col, 74);
457
458 const JSON2: &str =
459 r#"{"objectType":"Activity","name":"Z User","openid":"http://inter.net/zuser"}"#;
460
461 let de_result = serde_json::from_str::<Agent>(JSON2);
462 assert!(de_result.is_ok());
464 let agent = de_result.unwrap();
465 let errors = agent.validate();
466 assert!(!errors.is_empty());
467 assert_eq!(errors.len(), 1);
468 assert!(matches!(
469 &errors[0],
470 ValidationError::WrongObjectType { .. }
471 ));
472
473 const JSON3: &str = r#"{"name":"Rick James","objectType":"Agent"}"#;
474
475 let de_result = serde_json::from_str::<Agent>(JSON3);
476 assert!(de_result.is_ok());
478 let agent = de_result.unwrap();
479 let errors = agent.validate();
480 assert!(!errors.is_empty());
481 assert_eq!(errors.len(), 1);
482 assert!(matches!(
483 &errors[0],
484 ValidationError::ConstraintViolation { .. }
485 ))
486 }
487
488 #[ignore = "Partially Implemented"]
489 #[traced_test]
490 #[test]
491 fn test_null_optional_fields() {
492 const E1: &str = r#"{"objectType":"Agent","name":null}"#;
493 const E2: &str = r#"{"objectType":"Agent","mbox":null}"#;
494 const E3: &str = r#"{"objectType":"Agent","openid":null}"#;
495 const E4: &str = r#"{"objectType":"Agent","account":null}"#;
496
497 const OK1: &str = r#"{"objectType":"Agent","mbox":"foo@bar.org"}"#;
498
499 assert!(serde_json::from_str::<Agent>(E1).is_err());
500 assert!(serde_json::from_str::<Agent>(E2).is_err());
501 assert!(serde_json::from_str::<Agent>(E3).is_err());
502 assert!(serde_json::from_str::<Agent>(E4).is_err());
503
504 assert!(serde_json::from_str::<Agent>(OK1).is_ok());
505 }
506}