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 self.object_type.is_none() {
95 true
96 } else {
97 self.object_type.as_ref().unwrap() == &ObjectType::Agent
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 self.mbox.is_some() {
199 self.mbox.as_ref().unwrap().fingerprint(state);
200 }
201 self.mbox_sha1sum.hash(state);
202 self.openid.hash(state);
203 if self.account.is_some() {
204 self.account.as_ref().unwrap().fingerprint(state);
205 }
206 }
207}
208
209impl Validate for Agent {
210 fn validate(&self) -> Vec<ValidationError> {
211 let mut vec = vec![];
212
213 if self.object_type.is_some() && !self.check_object_type() {
214 vec.push(ValidationError::WrongObjectType {
215 expected: ObjectType::Agent,
216 found: self.object_type.as_ref().unwrap().to_string().into(),
217 })
218 }
219 if self.name.is_some() && self.name.as_ref().unwrap().is_empty() {
220 vec.push(ValidationError::Empty("name".into()))
221 }
222 let mut count = 0;
225 if self.mbox.is_some() {
226 count += 1;
227 }
229 if self.mbox_sha1sum.is_some() {
230 count += 1;
231 validate_sha1sum(self.mbox_sha1sum.as_ref().unwrap()).unwrap_or_else(|x| vec.push(x))
232 }
233 if self.openid.is_some() {
234 count += 1;
235 }
236 if self.account.is_some() {
237 count += 1;
238 vec.extend(self.account.as_ref().unwrap().validate())
239 }
240 if count != 1 {
241 vec.push(ValidationError::ConstraintViolation(
242 "Exactly 1 IFI is required".into(),
243 ))
244 }
245
246 vec
247 }
248}
249
250#[derive(Debug, Default)]
252pub struct AgentBuilder {
253 _object_type: Option<ObjectType>,
254 _name: Option<CIString>,
255 _mbox: Option<MyEmailAddress>,
256 _sha1sum: Option<String>,
257 _openid: Option<UriString>,
258 _account: Option<Account>,
259}
260
261impl AgentBuilder {
262 pub fn with_object_type(mut self) -> Self {
264 self._object_type = Some(ObjectType::Agent);
265 self
266 }
267
268 pub fn name(mut self, s: &str) -> Result<Self, DataError> {
272 let s = s.trim();
273 if s.is_empty() {
274 emit_error!(DataError::Validation(ValidationError::Empty("name".into())))
275 }
276 self._name = Some(CIString::from(s));
277 Ok(self)
278 }
279
280 pub fn mbox(mut self, s: &str) -> Result<Self, DataError> {
288 set_email!(self, s)
289 }
290
291 pub fn mbox_sha1sum(mut self, s: &str) -> Result<Self, DataError> {
299 let s = s.trim();
300 if s.is_empty() {
301 emit_error!(DataError::Validation(ValidationError::Empty(
302 "mbox_sha1sum".into()
303 )))
304 }
305
306 validate_sha1sum(s)?;
307 self._sha1sum = Some(s.to_owned());
308 self._mbox = None;
309 self._openid = None;
310 self._account = None;
311 Ok(self)
312 }
313
314 pub fn openid(mut self, s: &str) -> Result<Self, DataError> {
322 let s = s.trim();
323 if s.is_empty() {
324 emit_error!(DataError::Validation(ValidationError::Empty(
325 "openid".into()
326 )))
327 }
328
329 let uri = UriString::from_str(s)?;
330 self._openid = Some(uri);
331 self._mbox = None;
332 self._sha1sum = None;
333 self._account = None;
334 Ok(self)
335 }
336
337 pub fn account(mut self, val: Account) -> Result<Self, DataError> {
344 val.check_validity()?;
345 self._account = Some(val);
346 self._mbox = None;
347 self._sha1sum = None;
348 self._openid = None;
349 Ok(self)
350 }
351
352 pub fn build(self) -> Result<Agent, DataError> {
356 if self._mbox.is_none()
357 && self._sha1sum.is_none()
358 && self._openid.is_none()
359 && self._account.is_none()
360 {
361 emit_error!(DataError::Validation(ValidationError::MissingIFI(
362 "Agent".into(),
363 )));
364 }
365
366 Ok(Agent {
367 object_type: self._object_type,
368 name: self._name,
369 mbox: self._mbox,
370 mbox_sha1sum: self._sha1sum,
371 openid: self._openid,
372 account: self._account,
373 })
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use tracing_test::traced_test;
381
382 #[test]
383 fn test_serde() -> Result<(), DataError> {
384 const JSON: &str =
385 r#"{"objectType":"Agent","name":"Z User","mbox":"mailto:zuser@inter.net"}"#;
386
387 let a1 = Agent::builder()
388 .with_object_type()
389 .name("Z User")?
390 .mbox("zuser@inter.net")?
391 .build()?;
392 let se_result = serde_json::to_string(&a1);
393 assert!(se_result.is_ok());
394 let json = se_result.unwrap();
395 assert_eq!(json, JSON);
396
397 let de_result = serde_json::from_str::<Agent>(JSON);
398 assert!(de_result.is_ok());
399 let a2 = de_result.unwrap();
400
401 assert_eq!(a1, a2);
402
403 Ok(())
404 }
405
406 #[test]
407 fn test_camel_and_snake() {
408 const JSON: &str = r#"{
409 "objectType": "Agent",
410 "name": "Ena Hills",
411 "mbox": "mailto:ena.hills@example.com",
412 "mbox_sha1sum": "ebd31e95054c018b10727ccffd2ef2ec3a016ee9",
413 "account": {
414 "homePage": "http://www.example.com",
415 "name": "13936749"
416 },
417 "openid": "http://toby.openid.example.org/"
418 }"#;
419 let de_result = serde_json::from_str::<Agent>(JSON);
420 assert!(de_result.is_ok());
421 let a = de_result.unwrap();
422
423 assert!(a.check_object_type());
424 assert!(a.name().is_some());
425 assert_eq!(a.name().unwrap(), &CIString::from("ena hills"));
426 assert_eq!(a.name_as_str().unwrap(), "Ena Hills");
427 assert!(a.mbox().is_some());
428 assert_eq!(a.mbox().unwrap().to_uri(), "mailto:ena.hills@example.com");
429 assert!(a.mbox_sha1sum().is_some());
430 assert_eq!(
431 a.mbox_sha1sum().unwrap(),
432 "ebd31e95054c018b10727ccffd2ef2ec3a016ee9"
433 );
434 assert!(a.account().is_some());
435 let act = a.account().unwrap();
436 assert_eq!(act.home_page_as_str(), "http://www.example.com");
437 assert_eq!(act.name(), "13936749");
438 assert!(a.openid().is_some());
439 assert_eq!(
440 a.openid().unwrap().to_string(),
441 "http://toby.openid.example.org/"
442 );
443 }
444
445 #[traced_test]
446 #[test]
447 fn test_validate() {
448 const JSON1: &str =
449 r#"{"objectType":"Agent","name":"Z User","openid":"http://résumé.net/zuser"}"#;
450
451 let de_result = serde_json::from_str::<Agent>(JSON1);
452 assert!(de_result.as_ref().is_err_and(|x| x.is_data()));
454 let de_err = de_result.err().unwrap();
455 let (line, col) = (de_err.line(), de_err.column());
456 assert_eq!(line, 1);
457 assert_eq!(col, 74);
458
459 const JSON2: &str =
460 r#"{"objectType":"Activity","name":"Z User","openid":"http://inter.net/zuser"}"#;
461
462 let de_result = serde_json::from_str::<Agent>(JSON2);
463 assert!(de_result.is_ok());
465 let agent = de_result.unwrap();
466 let errors = agent.validate();
467 assert!(!errors.is_empty());
468 assert_eq!(errors.len(), 1);
469 assert!(matches!(
470 &errors[0],
471 ValidationError::WrongObjectType { .. }
472 ));
473
474 const JSON3: &str = r#"{"name":"Rick James","objectType":"Agent"}"#;
475
476 let de_result = serde_json::from_str::<Agent>(JSON3);
477 assert!(de_result.is_ok());
479 let agent = de_result.unwrap();
480 let errors = agent.validate();
481 assert!(!errors.is_empty());
482 assert_eq!(errors.len(), 1);
483 assert!(matches!(
484 &errors[0],
485 ValidationError::ConstraintViolation { .. }
486 ))
487 }
488
489 #[ignore = "Partially Implemented"]
490 #[traced_test]
491 #[test]
492 fn test_null_optional_fields() {
493 const E1: &str = r#"{"objectType":"Agent","name":null}"#;
494 const E2: &str = r#"{"objectType":"Agent","mbox":null}"#;
495 const E3: &str = r#"{"objectType":"Agent","openid":null}"#;
496 const E4: &str = r#"{"objectType":"Agent","account":null}"#;
497
498 const OK1: &str = r#"{"objectType":"Agent","mbox":"foo@bar.org"}"#;
499
500 assert!(serde_json::from_str::<Agent>(E1).is_err());
501 assert!(serde_json::from_str::<Agent>(E2).is_err());
502 assert!(serde_json::from_str::<Agent>(E3).is_err());
503 assert!(serde_json::from_str::<Agent>(E4).is_err());
504
505 assert!(serde_json::from_str::<Agent>(OK1).is_ok());
506 }
507}