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