1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[cfg(feature = "python")]
5use pyo3::{
6 prelude::*,
7 types::{PyDateTime, PyDict},
8};
9
10use crate::native::deserializers::{
11 default_datetime_none, default_string_none, deserialize_empty_string_as_none,
12 deserialize_empty_string_as_none_datetime,
13};
14
15#[cfg(feature = "python")]
16use crate::native::deserializers::{to_py_datetime, to_py_datetime_option};
17
18#[cfg(not(feature = "python"))]
19#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
20pub struct Value {
21 #[serde(rename = "by")]
22 #[serde(alias = "@by")]
23 #[serde(alias = "by")]
24 pub by: String,
25
26 #[serde(rename = "byUniqueId")]
27 #[serde(alias = "@byUniqueId")]
28 #[serde(alias = "byUniqueId")]
29 #[serde(
30 default = "default_string_none",
31 deserialize_with = "deserialize_empty_string_as_none"
32 )]
33 pub by_unique_id: Option<String>,
34 #[serde(rename = "role")]
35 #[serde(alias = "@role")]
36 #[serde(alias = "role")]
37 pub role: String,
38 #[serde(rename = "when")]
39 #[serde(alias = "@when")]
40 #[serde(alias = "when")]
41 pub when: Option<DateTime<Utc>>,
42
43 #[serde(rename = "value")]
44 #[serde(alias = "$text")]
45 #[serde(alias = "#text")]
46 #[serde(alias = "value")]
47 #[serde(default)]
48 pub value: String,
49}
50
51#[cfg(feature = "python")]
52#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
53#[pyclass]
54pub struct Value {
55 #[serde(rename = "by")]
56 #[serde(alias = "@by")]
57 #[serde(alias = "by")]
58 pub by: String,
59
60 #[serde(rename = "byUniqueId")]
61 #[serde(alias = "@byUniqueId")]
62 #[serde(alias = "byUniqueId")]
63 #[serde(
64 default = "default_string_none",
65 deserialize_with = "deserialize_empty_string_as_none"
66 )]
67 pub by_unique_id: Option<String>,
68 #[serde(rename = "role")]
69 #[serde(alias = "@role")]
70 #[serde(alias = "role")]
71 pub role: String,
72 #[serde(rename = "when")]
73 #[serde(alias = "@when")]
74 #[serde(alias = "when")]
75 pub when: Option<DateTime<Utc>>,
76
77 #[serde(rename = "value")]
78 #[serde(alias = "$text")]
79 #[serde(alias = "#text")]
80 #[serde(alias = "value")]
81 #[serde(default)]
82 pub value: String,
83}
84
85#[cfg(feature = "python")]
86#[pymethods]
87impl Value {
88 #[getter]
89 fn by(&self) -> PyResult<String> {
90 Ok(self.by.clone())
91 }
92
93 #[getter]
94 fn by_unique_id(&self) -> PyResult<Option<String>> {
95 Ok(self.by_unique_id.clone())
96 }
97
98 #[getter]
99 fn role(&self) -> PyResult<String> {
100 Ok(self.role.clone())
101 }
102
103 #[getter]
104 fn when<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
105 to_py_datetime_option(py, &self.when)
106 }
107
108 #[getter]
109 fn value(&self) -> PyResult<String> {
110 Ok(self.value.clone())
111 }
112
113 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
114 let dict = PyDict::new(py);
115 dict.set_item("by", &self.by)?;
116 dict.set_item("by_unique_id", &self.by_unique_id)?;
117 dict.set_item("role", &self.role)?;
118 dict.set_item("when", to_py_datetime_option(py, &self.when)?)?;
119 dict.set_item("value", &self.value)?;
120
121 Ok(dict)
122 }
123}
124
125#[cfg(not(feature = "python"))]
126#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
127pub struct Reason {
128 #[serde(rename = "by")]
129 #[serde(alias = "@by")]
130 #[serde(alias = "by")]
131 pub by: String,
132
133 #[serde(rename = "byUniqueId")]
134 #[serde(alias = "@byUniqueId")]
135 #[serde(alias = "byUniqueId")]
136 #[serde(
137 default = "default_string_none",
138 deserialize_with = "deserialize_empty_string_as_none"
139 )]
140 pub by_unique_id: Option<String>,
141
142 #[serde(rename = "role")]
143 #[serde(alias = "@role")]
144 #[serde(alias = "role")]
145 pub role: String,
146 #[serde(rename = "when")]
147 #[serde(alias = "@when")]
148 #[serde(alias = "when")]
149 pub when: Option<DateTime<Utc>>,
150
151 #[serde(rename = "value")]
152 #[serde(alias = "$text")]
153 #[serde(alias = "#text")]
154 #[serde(alias = "value")]
155 #[serde(default)]
156 pub value: String,
157}
158
159#[cfg(feature = "python")]
160#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
161#[pyclass]
162pub struct Reason {
163 #[serde(rename = "by")]
164 #[serde(alias = "@by")]
165 #[serde(alias = "by")]
166 pub by: String,
167
168 #[serde(rename = "byUniqueId")]
169 #[serde(alias = "@byUniqueId")]
170 #[serde(alias = "byUniqueId")]
171 #[serde(
172 default = "default_string_none",
173 deserialize_with = "deserialize_empty_string_as_none"
174 )]
175 pub by_unique_id: Option<String>,
176
177 #[serde(rename = "role")]
178 #[serde(alias = "@role")]
179 #[serde(alias = "role")]
180 pub role: String,
181 #[serde(rename = "when")]
182 #[serde(alias = "@when")]
183 #[serde(alias = "when")]
184 pub when: Option<DateTime<Utc>>,
185
186 #[serde(rename = "value")]
187 #[serde(alias = "$text")]
188 #[serde(alias = "#text")]
189 #[serde(alias = "value")]
190 #[serde(default)]
191 pub value: String,
192}
193
194#[cfg(feature = "python")]
195#[pymethods]
196impl Reason {
197 #[getter]
198 fn by(&self) -> PyResult<String> {
199 Ok(self.by.clone())
200 }
201
202 #[getter]
203 fn by_unique_id(&self) -> PyResult<Option<String>> {
204 Ok(self.by_unique_id.clone())
205 }
206
207 #[getter]
208 fn role(&self) -> PyResult<String> {
209 Ok(self.role.clone())
210 }
211
212 #[getter]
213 fn when<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
214 to_py_datetime_option(py, &self.when)
215 }
216
217 #[getter]
218 fn value(&self) -> PyResult<String> {
219 Ok(self.value.clone())
220 }
221
222 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
223 let dict = PyDict::new(py);
224 dict.set_item("by", &self.by)?;
225 dict.set_item("by_unique_id", &self.by_unique_id)?;
226 dict.set_item("role", &self.role)?;
227 dict.set_item("when", to_py_datetime_option(py, &self.when)?)?;
228 dict.set_item("value", &self.value)?;
229
230 Ok(dict)
231 }
232}
233
234#[cfg(not(feature = "python"))]
235#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
236pub struct Entry {
237 #[serde(rename = "entryId")]
238 #[serde(alias = "@id")]
239 #[serde(alias = "entryId")]
240 pub entry_id: String,
241
242 #[serde(rename = "reviewedBy")]
243 #[serde(alias = "@reviewedBy")]
244 #[serde(alias = "reviewedBy")]
245 #[serde(
246 default = "default_string_none",
247 deserialize_with = "deserialize_empty_string_as_none"
248 )]
249 pub reviewed_by: Option<String>,
250
251 #[serde(rename = "reviewedByUniqueId")]
252 #[serde(alias = "@reviewedByUniqueId")]
253 #[serde(alias = "reviewedByUniqueId")]
254 #[serde(
255 default = "default_string_none",
256 deserialize_with = "deserialize_empty_string_as_none"
257 )]
258 pub reviewed_by_unique_id: Option<String>,
259
260 #[serde(rename = "reviewedByWhen")]
261 #[serde(alias = "@reviewedByWhen")]
262 #[serde(alias = "reviewedByWhen")]
263 #[serde(
264 default = "default_datetime_none",
265 deserialize_with = "deserialize_empty_string_as_none_datetime"
266 )]
267 pub reviewed_by_when: Option<DateTime<Utc>>,
268
269 pub value: Option<Value>,
270 pub reason: Option<Reason>,
271}
272
273#[cfg(feature = "python")]
274#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
275#[pyclass]
276pub struct Entry {
277 #[serde(rename = "entryId")]
278 #[serde(alias = "@id")]
279 #[serde(alias = "entryId")]
280 pub entry_id: String,
281
282 #[serde(rename = "reviewedBy")]
283 #[serde(alias = "@reviewedBy")]
284 #[serde(alias = "reviewedBy")]
285 #[serde(
286 default = "default_string_none",
287 deserialize_with = "deserialize_empty_string_as_none"
288 )]
289 pub reviewed_by: Option<String>,
290
291 #[serde(rename = "reviewedByUniqueId")]
292 #[serde(alias = "@reviewedByUniqueId")]
293 #[serde(alias = "reviewedByUniqueId")]
294 #[serde(
295 default = "default_string_none",
296 deserialize_with = "deserialize_empty_string_as_none"
297 )]
298 pub reviewed_by_unique_id: Option<String>,
299
300 #[serde(rename = "reviewedByWhen")]
301 #[serde(alias = "@reviewedByWhen")]
302 #[serde(alias = "reviewedByWhen")]
303 #[serde(
304 default = "default_datetime_none",
305 deserialize_with = "deserialize_empty_string_as_none_datetime"
306 )]
307 pub reviewed_by_when: Option<DateTime<Utc>>,
308
309 pub value: Option<Value>,
310 pub reason: Option<Reason>,
311}
312
313#[cfg(feature = "python")]
314#[pymethods]
315impl Entry {
316 #[getter]
317 fn entry_id(&self) -> PyResult<String> {
318 Ok(self.entry_id.clone())
319 }
320
321 #[getter]
322 fn reviewed_by(&self) -> PyResult<Option<String>> {
323 Ok(self.reviewed_by.clone())
324 }
325
326 #[getter]
327 fn reviewed_by_unique_id(&self) -> PyResult<Option<String>> {
328 Ok(self.reviewed_by_unique_id.clone())
329 }
330
331 #[getter]
332 fn reviewed_by_when<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
333 to_py_datetime_option(py, &self.reviewed_by_when)
334 }
335
336 #[getter]
337 fn value(&self) -> PyResult<Option<Value>> {
338 Ok(self.value.clone())
339 }
340
341 #[getter]
342 fn reason(&self) -> PyResult<Option<Reason>> {
343 Ok(self.reason.clone())
344 }
345
346 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
347 let dict = PyDict::new(py);
348 dict.set_item("entry_id", &self.entry_id)?;
349 dict.set_item("reviewed_by", &self.reviewed_by)?;
350 dict.set_item("reviewed_by_unique_id", &self.reviewed_by_unique_id)?;
351 dict.set_item(
352 "reviewed_by_when",
353 to_py_datetime_option(py, &self.reviewed_by_when)?,
354 )?;
355 if let Some(value) = &self.value {
356 dict.set_item("value", value.to_dict(py)?)?;
357 } else {
358 dict.set_item("value", py.None())?;
359 }
360 if let Some(reason) = &self.reason {
361 dict.set_item("reason", reason.to_dict(py)?)?;
362 } else {
363 dict.set_item("reason", py.None())?;
364 }
365
366 Ok(dict)
367 }
368}
369
370#[cfg(not(feature = "python"))]
371#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
372pub struct Comment {
373 #[serde(rename = "commentId")]
374 #[serde(alias = "@id")]
375 #[serde(alias = "commentId")]
376 pub comment_id: String,
377 pub value: Option<Value>,
378}
379
380#[cfg(feature = "python")]
381#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
382#[pyclass(get_all)]
383pub struct Comment {
384 #[serde(rename = "commentId")]
385 #[serde(alias = "@id")]
386 #[serde(alias = "commentId")]
387 pub comment_id: String,
388 pub value: Option<Value>,
389}
390
391#[cfg(feature = "python")]
392#[pymethods]
393impl Comment {
394 #[getter]
395 fn comment_id(&self) -> PyResult<String> {
396 Ok(self.comment_id.clone())
397 }
398
399 #[getter]
400 fn value(&self) -> PyResult<Option<Value>> {
401 Ok(self.value.clone())
402 }
403
404 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
405 let dict = PyDict::new(py);
406 dict.set_item("comment_id", &self.comment_id)?;
407 if let Some(value) = &self.value {
408 dict.set_item("value", value.to_dict(py)?)?;
409 } else {
410 dict.set_item("value", py.None())?;
411 }
412
413 Ok(dict)
414 }
415}
416
417#[cfg(not(feature = "python"))]
418#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
419pub struct Field {
420 #[serde(rename = "name")]
421 #[serde(alias = "@name")]
422 #[serde(alias = "name")]
423 pub name: String,
424
425 #[serde(rename = "fieldType")]
426 #[serde(alias = "@type")]
427 #[serde(alias = "fieldType")]
428 pub field_type: String,
429
430 #[serde(rename = "dataType")]
431 #[serde(alias = "@dataType")]
432 #[serde(alias = "dataType")]
433 #[serde(
434 default = "default_string_none",
435 deserialize_with = "deserialize_empty_string_as_none"
436 )]
437 pub data_type: Option<String>,
438 #[serde(rename = "errorCode")]
439 #[serde(alias = "@errorCode")]
440 #[serde(alias = "errorCode")]
441 pub error_code: String,
442 #[serde(rename = "whenCreated")]
443 #[serde(alias = "@whenCreated")]
444 #[serde(alias = "whenCreated")]
445 pub when_created: Option<DateTime<Utc>>,
446 #[serde(rename = "keepHistory")]
447 #[serde(alias = "@keepHistory")]
448 #[serde(alias = "keepHistory")]
449 pub keep_history: bool,
450
451 #[serde(alias = "entry")]
452 pub entries: Option<Vec<Entry>>,
453
454 #[serde(alias = "comment")]
455 pub comments: Option<Vec<Comment>>,
456}
457
458#[cfg(feature = "python")]
459#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
460#[pyclass]
461pub struct Field {
462 #[serde(rename = "name")]
463 #[serde(alias = "@name")]
464 #[serde(alias = "name")]
465 pub name: String,
466
467 #[serde(rename = "fieldType")]
468 #[serde(alias = "@type")]
469 #[serde(alias = "fieldType")]
470 pub field_type: String,
471
472 #[serde(rename = "dataType")]
473 #[serde(alias = "@dataType")]
474 #[serde(alias = "dataType")]
475 #[serde(
476 default = "default_string_none",
477 deserialize_with = "deserialize_empty_string_as_none"
478 )]
479 pub data_type: Option<String>,
480
481 #[serde(rename = "errorCode")]
482 #[serde(alias = "@errorCode")]
483 #[serde(alias = "errorCode")]
484 pub error_code: String,
485 #[serde(rename = "whenCreated")]
486 #[serde(alias = "@whenCreated")]
487 #[serde(alias = "whenCreated")]
488 pub when_created: Option<DateTime<Utc>>,
489 #[serde(rename = "keepHistory")]
490 #[serde(alias = "@keepHistory")]
491 #[serde(alias = "keepHistory")]
492 pub keep_history: bool,
493
494 #[serde(alias = "entry")]
495 pub entries: Option<Vec<Entry>>,
496
497 #[serde(alias = "comment")]
498 pub comments: Option<Vec<Comment>>,
499}
500
501#[cfg(feature = "python")]
502#[pymethods]
503impl Field {
504 #[getter]
505 fn name(&self) -> PyResult<String> {
506 Ok(self.name.clone())
507 }
508
509 #[getter]
510 fn field_type(&self) -> PyResult<String> {
511 Ok(self.field_type.clone())
512 }
513
514 #[getter]
515 fn data_type(&self) -> PyResult<Option<String>> {
516 Ok(self.data_type.clone())
517 }
518
519 #[getter]
520 fn error_code(&self) -> PyResult<String> {
521 Ok(self.error_code.clone())
522 }
523
524 #[getter]
525 fn when_created<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
526 self.when_created
527 .as_ref()
528 .map(|dt| to_py_datetime(py, dt))
529 .transpose()
530 }
531
532 #[getter]
533 fn keep_history(&self) -> PyResult<bool> {
534 Ok(self.keep_history)
535 }
536
537 #[getter]
538 fn entries(&self) -> PyResult<Option<Vec<Entry>>> {
539 Ok(self.entries.clone())
540 }
541
542 #[getter]
543 fn comments(&self) -> PyResult<Option<Vec<Comment>>> {
544 Ok(self.comments.clone())
545 }
546
547 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
548 let dict = PyDict::new(py);
549 dict.set_item("name", &self.name)?;
550 dict.set_item("field_type", &self.field_type)?;
551 dict.set_item("data_type", &self.data_type)?;
552 dict.set_item("error_code", &self.error_code)?;
553 dict.set_item(
554 "when_created",
555 self.when_created
556 .as_ref()
557 .map(|dt| to_py_datetime(py, dt))
558 .transpose()?,
559 )?;
560 dict.set_item("keep_history", self.keep_history)?;
561
562 let mut entry_dicts = Vec::new();
563 if let Some(entries) = &self.entries {
564 for entry in entries {
565 let entry_dict = entry.to_dict(py)?;
566 entry_dicts.push(entry_dict);
567 }
568 dict.set_item("entries", entry_dicts)?;
569 } else {
570 dict.set_item("entries", py.None())?;
571 }
572
573 let mut comment_dicts = Vec::new();
574 if let Some(comments) = &self.comments {
575 for comment in comments {
576 let comment_dict = comment.to_dict(py)?;
577 comment_dicts.push(comment_dict);
578 }
579 dict.set_item("comments", comment_dicts)?;
580 } else {
581 dict.set_item("comments", py.None())?;
582 }
583
584 Ok(dict)
585 }
586}
587
588#[cfg(not(feature = "python"))]
589#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
590pub struct Category {
591 #[serde(rename = "name")]
592 #[serde(alias = "@name")]
593 #[serde(alias = "name")]
594 pub name: String,
595
596 #[serde(rename = "categoryType")]
597 #[serde(alias = "@type")]
598 #[serde(alias = "categoryType")]
599 pub category_type: String,
600
601 #[serde(rename = "highestIndex")]
602 #[serde(alias = "@highestIndex")]
603 #[serde(alias = "highestIndex")]
604 pub highest_index: usize,
605
606 #[serde(alias = "field")]
607 pub fields: Option<Vec<Field>>,
608}
609
610#[cfg(feature = "python")]
611#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
612#[pyclass(get_all)]
613pub struct Category {
614 #[serde(rename = "name")]
615 #[serde(alias = "@name")]
616 #[serde(alias = "name")]
617 pub name: String,
618
619 #[serde(rename = "categoryType")]
620 #[serde(alias = "@type")]
621 #[serde(alias = "categoryType")]
622 pub category_type: String,
623
624 #[serde(rename = "highestIndex")]
625 #[serde(alias = "@highestIndex")]
626 #[serde(alias = "highestIndex")]
627 pub highest_index: usize,
628
629 #[serde(alias = "field")]
630 pub fields: Option<Vec<Field>>,
631}
632
633#[cfg(feature = "python")]
634#[pymethods]
635impl Category {
636 #[getter]
637 fn name(&self) -> PyResult<String> {
638 Ok(self.name.clone())
639 }
640
641 #[getter]
642 fn category_type(&self) -> PyResult<String> {
643 Ok(self.category_type.clone())
644 }
645
646 #[getter]
647 fn highest_index(&self) -> PyResult<usize> {
648 Ok(self.highest_index)
649 }
650
651 #[getter]
652 fn fields(&self) -> PyResult<Option<Vec<Field>>> {
653 Ok(self.fields.clone())
654 }
655
656 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
657 let dict = PyDict::new(py);
658 dict.set_item("name", &self.name)?;
659 dict.set_item("category_type", &self.category_type)?;
660 dict.set_item("highest_index", self.highest_index)?;
661
662 let mut field_dicts = Vec::new();
663 if let Some(fields) = &self.fields {
664 for field in fields {
665 let field_dict = field.to_dict(py)?;
666 field_dicts.push(field_dict);
667 }
668 dict.set_item("fields", field_dicts)?;
669 } else {
670 dict.set_item("fields", py.None())?;
671 }
672
673 Ok(dict)
674 }
675}
676
677impl Form {
678 pub fn from_attributes(
679 attrs: std::collections::HashMap<String, String>,
680 ) -> Result<Self, crate::errors::Error> {
681 let name = attrs.get("name").cloned().unwrap_or_default();
682
683 let last_modified = if let Some(lm) = attrs.get("lastModified") {
684 if lm.is_empty() {
685 None
686 } else {
687 parse_datetime_internal(lm).ok()
688 }
689 } else {
690 None
691 };
692
693 let who_last_modified_name = attrs
694 .get("whoLastModifiedName")
695 .filter(|s| !s.is_empty())
696 .cloned();
697 let who_last_modified_role = attrs
698 .get("whoLastModifiedRole")
699 .filter(|s| !s.is_empty())
700 .cloned();
701
702 let when_created = attrs
703 .get("whenCreated")
704 .and_then(|s| s.parse().ok())
705 .unwrap_or(0);
706
707 let has_errors = attrs.get("hasErrors").map(|s| s == "true").unwrap_or(false);
708
709 let has_warnings = attrs
710 .get("hasWarnings")
711 .map(|s| s == "true")
712 .unwrap_or(false);
713
714 let locked = attrs.get("locked").map(|s| s == "true").unwrap_or(false);
715
716 let user = attrs.get("user").filter(|s| !s.is_empty()).cloned();
717
718 let date_time_changed = if let Some(dtc) = attrs.get("dateTimeChanged") {
719 if dtc.is_empty() {
720 None
721 } else {
722 parse_datetime_internal(dtc).ok()
723 }
724 } else {
725 None
726 };
727
728 let form_title = attrs.get("formTitle").cloned().unwrap_or_default();
729
730 let form_index = attrs
731 .get("formIndex")
732 .and_then(|s| s.parse().ok())
733 .unwrap_or(0);
734
735 let form_group = attrs.get("formGroup").filter(|s| !s.is_empty()).cloned();
736 let form_state = attrs.get("formState").cloned().unwrap_or_default();
737
738 Ok(Form {
739 name,
740 last_modified,
741 who_last_modified_name,
742 who_last_modified_role,
743 when_created,
744 has_errors,
745 has_warnings,
746 locked,
747 user,
748 date_time_changed,
749 form_title,
750 form_index,
751 form_group,
752 form_state,
753 states: None,
754 lock_state: None,
755 categories: None,
756 })
757 }
758}
759
760fn parse_datetime_internal(s: &str) -> Result<DateTime<Utc>, crate::errors::Error> {
761 if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S %z") {
762 Ok(dt.with_timezone(&Utc))
763 } else if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%z") {
764 Ok(dt.with_timezone(&Utc))
765 } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
766 Ok(dt.with_timezone(&Utc))
767 } else {
768 Err(crate::errors::Error::ParsingError(
769 quick_xml::de::DeError::Custom(format!("Invalid datetime format: {}", s)),
770 ))
771 }
772}
773
774#[cfg(not(feature = "python"))]
775#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
776pub struct State {
777 #[serde(rename = "value")]
778 #[serde(alias = "@value")]
779 #[serde(alias = "value")]
780 pub value: String,
781 #[serde(rename = "signer")]
782 #[serde(alias = "@signer")]
783 #[serde(alias = "signer")]
784 pub signer: String,
785 #[serde(rename = "signerUniqueId")]
786 #[serde(alias = "@signerUniqueId")]
787 #[serde(alias = "signerUniqueId")]
788 pub signer_unique_id: String,
789
790 #[serde(rename = "dateSigned")]
791 #[serde(alias = "@dateSigned")]
792 #[serde(alias = "dateSigned")]
793 #[serde(
794 default = "default_datetime_none",
795 deserialize_with = "deserialize_empty_string_as_none_datetime"
796 )]
797 pub date_signed: Option<DateTime<Utc>>,
798}
799
800#[cfg(feature = "python")]
801#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
802#[pyclass]
803pub struct State {
804 #[serde(rename = "value")]
805 #[serde(alias = "@value")]
806 #[serde(alias = "value")]
807 pub value: String,
808 #[serde(rename = "signer")]
809 #[serde(alias = "@signer")]
810 #[serde(alias = "signer")]
811 pub signer: String,
812 #[serde(rename = "signerUniqueId")]
813 #[serde(alias = "@signerUniqueId")]
814 #[serde(alias = "signerUniqueId")]
815 pub signer_unique_id: String,
816
817 #[serde(rename = "dateSigned")]
818 #[serde(alias = "@dateSigned")]
819 #[serde(alias = "dateSigned")]
820 #[serde(
821 default = "default_datetime_none",
822 deserialize_with = "deserialize_empty_string_as_none_datetime"
823 )]
824 pub date_signed: Option<DateTime<Utc>>,
825}
826
827#[cfg(feature = "python")]
828#[pymethods]
829impl State {
830 #[getter]
831 fn value(&self) -> PyResult<String> {
832 Ok(self.value.clone())
833 }
834
835 #[getter]
836 fn signer(&self) -> PyResult<String> {
837 Ok(self.signer.clone())
838 }
839
840 #[getter]
841 fn signer_unique_id(&self) -> PyResult<String> {
842 Ok(self.signer_unique_id.clone())
843 }
844
845 #[getter]
846 fn date_signed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
847 to_py_datetime_option(py, &self.date_signed)
848 }
849
850 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
851 let dict = PyDict::new(py);
852 dict.set_item("value", &self.value)?;
853 dict.set_item("signer", &self.signer)?;
854 dict.set_item("signer_unique_id", &self.signer_unique_id)?;
855 dict.set_item("date_signed", to_py_datetime_option(py, &self.date_signed)?)?;
856
857 Ok(dict)
858 }
859}
860
861#[cfg(not(feature = "python"))]
862#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
863pub struct LockState {
864 #[serde(rename = "locked")]
865 #[serde(alias = "@locked")]
866 #[serde(alias = "locked")]
867 pub locked: bool,
868
869 #[serde(rename = "user")]
870 #[serde(alias = "@user")]
871 #[serde(alias = "user")]
872 #[serde(
873 default = "default_string_none",
874 deserialize_with = "deserialize_empty_string_as_none"
875 )]
876 pub user: Option<String>,
877
878 #[serde(rename = "userUniqueId")]
879 #[serde(alias = "@userUniqueId")]
880 #[serde(alias = "userUniqueId")]
881 #[serde(
882 default = "default_string_none",
883 deserialize_with = "deserialize_empty_string_as_none"
884 )]
885 pub user_unique_id: Option<String>,
886
887 #[serde(rename = "dateTimeChanged")]
888 #[serde(alias = "@dateTimeChanged")]
889 #[serde(alias = "dateTimeChanged")]
890 #[serde(
891 default = "default_datetime_none",
892 deserialize_with = "deserialize_empty_string_as_none_datetime"
893 )]
894 pub date_time_changed: Option<DateTime<Utc>>,
895}
896
897#[cfg(feature = "python")]
898#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
899#[pyclass]
900pub struct LockState {
901 #[serde(rename = "locked")]
902 #[serde(alias = "@locked")]
903 #[serde(alias = "locked")]
904 pub locked: bool,
905
906 #[serde(rename = "user")]
907 #[serde(alias = "@user")]
908 #[serde(alias = "user")]
909 #[serde(
910 default = "default_string_none",
911 deserialize_with = "deserialize_empty_string_as_none"
912 )]
913 pub user: Option<String>,
914
915 #[serde(rename = "userUniqueId")]
916 #[serde(alias = "@userUniqueId")]
917 #[serde(alias = "userUniqueId")]
918 #[serde(
919 default = "default_string_none",
920 deserialize_with = "deserialize_empty_string_as_none"
921 )]
922 pub user_unique_id: Option<String>,
923
924 #[serde(rename = "dateTimeChanged")]
925 #[serde(alias = "@dateTimeChanged")]
926 #[serde(alias = "dateTimeChanged")]
927 #[serde(
928 default = "default_datetime_none",
929 deserialize_with = "deserialize_empty_string_as_none_datetime"
930 )]
931 pub date_time_changed: Option<DateTime<Utc>>,
932}
933
934#[cfg(feature = "python")]
935#[pymethods]
936impl LockState {
937 #[getter]
938 fn locked(&self) -> PyResult<bool> {
939 Ok(self.locked)
940 }
941
942 #[getter]
943 fn user(&self) -> PyResult<Option<String>> {
944 Ok(self.user.clone())
945 }
946
947 #[getter]
948 fn user_unique_id(&self) -> PyResult<Option<String>> {
949 Ok(self.user_unique_id.clone())
950 }
951
952 #[getter]
953 fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
954 to_py_datetime_option(py, &self.date_time_changed)
955 }
956
957 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
958 let dict = PyDict::new(py);
959 dict.set_item("locked", self.locked)?;
960 dict.set_item("user", &self.user)?;
961 dict.set_item("user_unique_id", &self.user_unique_id)?;
962 dict.set_item(
963 "date_time_changed",
964 to_py_datetime_option(py, &self.date_time_changed)?,
965 )?;
966
967 Ok(dict)
968 }
969}
970
971#[cfg(not(feature = "python"))]
972#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
973pub struct Form {
974 #[serde(rename = "name")]
975 #[serde(alias = "@name")]
976 #[serde(alias = "name")]
977 pub name: String,
978
979 #[serde(rename = "lastModified")]
980 #[serde(alias = "@lastModified")]
981 #[serde(alias = "lastModified")]
982 #[serde(
983 default = "default_datetime_none",
984 deserialize_with = "deserialize_empty_string_as_none_datetime"
985 )]
986 pub last_modified: Option<DateTime<Utc>>,
987
988 #[serde(rename = "whoLastModifiedName")]
989 #[serde(alias = "@whoLastModifiedName")]
990 #[serde(alias = "whoLastModifiedName")]
991 #[serde(
992 default = "default_string_none",
993 deserialize_with = "deserialize_empty_string_as_none"
994 )]
995 pub who_last_modified_name: Option<String>,
996
997 #[serde(rename = "whoLastModifiedRole")]
998 #[serde(alias = "@whoLastModifiedRole")]
999 #[serde(alias = "whoLastModifiedRole")]
1000 #[serde(
1001 default = "default_string_none",
1002 deserialize_with = "deserialize_empty_string_as_none"
1003 )]
1004 pub who_last_modified_role: Option<String>,
1005
1006 #[serde(rename = "whenCreated")]
1007 #[serde(alias = "@whenCreated")]
1008 #[serde(alias = "whenCreated")]
1009 pub when_created: usize,
1010 #[serde(rename = "hasErrors")]
1011 #[serde(alias = "@hasErrors")]
1012 #[serde(alias = "hasErrors")]
1013 pub has_errors: bool,
1014 #[serde(rename = "hasWarnings")]
1015 #[serde(alias = "@hasWarnings")]
1016 #[serde(alias = "hasWarnings")]
1017 pub has_warnings: bool,
1018 #[serde(rename = "locked")]
1019 #[serde(alias = "@locked")]
1020 #[serde(alias = "locked")]
1021 pub locked: bool,
1022
1023 #[serde(rename = "user")]
1024 #[serde(alias = "@user")]
1025 #[serde(alias = "user")]
1026 #[serde(
1027 default = "default_string_none",
1028 deserialize_with = "deserialize_empty_string_as_none"
1029 )]
1030 pub user: Option<String>,
1031
1032 #[serde(rename = "dateTimeChanged")]
1033 #[serde(alias = "@dateTimeChanged")]
1034 #[serde(alias = "dateTimeChanged")]
1035 #[serde(
1036 default = "default_datetime_none",
1037 deserialize_with = "deserialize_empty_string_as_none_datetime"
1038 )]
1039 pub date_time_changed: Option<DateTime<Utc>>,
1040
1041 #[serde(rename = "formTitle")]
1042 #[serde(alias = "@formTitle")]
1043 #[serde(alias = "formTitle")]
1044 pub form_title: String,
1045 #[serde(rename = "formIndex")]
1046 #[serde(alias = "@formIndex")]
1047 #[serde(alias = "formIndex")]
1048 pub form_index: usize,
1049
1050 #[serde(rename = "formGroup")]
1051 #[serde(alias = "@formGroup")]
1052 #[serde(alias = "formGroup")]
1053 #[serde(
1054 default = "default_string_none",
1055 deserialize_with = "deserialize_empty_string_as_none"
1056 )]
1057 pub form_group: Option<String>,
1058
1059 #[serde(rename = "formState")]
1060 #[serde(alias = "@formState")]
1061 #[serde(alias = "formState")]
1062 pub form_state: String,
1063
1064 #[serde(alias = "state")]
1065 pub states: Option<Vec<State>>,
1066
1067 #[serde(alias = "lockState")]
1068 pub lock_state: Option<LockState>,
1069
1070 #[serde(alias = "category")]
1071 pub categories: Option<Vec<Category>>,
1072}
1073
1074#[cfg(feature = "python")]
1075#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1076#[pyclass]
1077pub struct Form {
1078 #[serde(rename = "name")]
1079 #[serde(alias = "@name")]
1080 #[serde(alias = "name")]
1081 pub name: String,
1082
1083 #[serde(rename = "lastModified")]
1084 #[serde(alias = "@lastModified")]
1085 #[serde(alias = "lastModified")]
1086 #[serde(
1087 default = "default_datetime_none",
1088 deserialize_with = "deserialize_empty_string_as_none_datetime"
1089 )]
1090 pub last_modified: Option<DateTime<Utc>>,
1091
1092 #[serde(rename = "whoLastModifiedName")]
1093 #[serde(alias = "@whoLastModifiedName")]
1094 #[serde(alias = "whoLastModifiedName")]
1095 #[serde(
1096 default = "default_string_none",
1097 deserialize_with = "deserialize_empty_string_as_none"
1098 )]
1099 pub who_last_modified_name: Option<String>,
1100
1101 #[serde(rename = "whoLastModifiedRole")]
1102 #[serde(alias = "@whoLastModifiedRole")]
1103 #[serde(alias = "whoLastModifiedRole")]
1104 #[serde(
1105 default = "default_string_none",
1106 deserialize_with = "deserialize_empty_string_as_none"
1107 )]
1108 pub who_last_modified_role: Option<String>,
1109
1110 #[serde(rename = "whenCreated")]
1111 #[serde(alias = "@whenCreated")]
1112 #[serde(alias = "whenCreated")]
1113 pub when_created: usize,
1114 #[serde(rename = "hasErrors")]
1115 #[serde(alias = "@hasErrors")]
1116 #[serde(alias = "hasErrors")]
1117 pub has_errors: bool,
1118 #[serde(rename = "hasWarnings")]
1119 #[serde(alias = "@hasWarnings")]
1120 #[serde(alias = "hasWarnings")]
1121 pub has_warnings: bool,
1122 #[serde(rename = "locked")]
1123 #[serde(alias = "@locked")]
1124 #[serde(alias = "locked")]
1125 pub locked: bool,
1126
1127 #[serde(rename = "user")]
1128 #[serde(alias = "@user")]
1129 #[serde(alias = "user")]
1130 #[serde(
1131 default = "default_string_none",
1132 deserialize_with = "deserialize_empty_string_as_none"
1133 )]
1134 pub user: Option<String>,
1135
1136 #[serde(rename = "dateTimeChanged")]
1137 #[serde(alias = "@dateTimeChanged")]
1138 #[serde(alias = "dateTimeChanged")]
1139 #[serde(
1140 default = "default_datetime_none",
1141 deserialize_with = "deserialize_empty_string_as_none_datetime"
1142 )]
1143 pub date_time_changed: Option<DateTime<Utc>>,
1144
1145 #[serde(rename = "formTitle")]
1146 #[serde(alias = "@formTitle")]
1147 #[serde(alias = "formTitle")]
1148 pub form_title: String,
1149 #[serde(rename = "formIndex")]
1150 #[serde(alias = "@formIndex")]
1151 #[serde(alias = "formIndex")]
1152 pub form_index: usize,
1153
1154 #[serde(rename = "formGroup")]
1155 #[serde(alias = "@formGroup")]
1156 #[serde(alias = "formGroup")]
1157 #[serde(
1158 default = "default_string_none",
1159 deserialize_with = "deserialize_empty_string_as_none"
1160 )]
1161 pub form_group: Option<String>,
1162
1163 #[serde(rename = "formState")]
1164 #[serde(alias = "@formState")]
1165 #[serde(alias = "formState")]
1166 pub form_state: String,
1167
1168 #[serde(alias = "state")]
1169 pub states: Option<Vec<State>>,
1170
1171 #[serde(alias = "lockState")]
1172 pub lock_state: Option<LockState>,
1173
1174 #[serde(alias = "category")]
1175 pub categories: Option<Vec<Category>>,
1176}
1177
1178#[cfg(feature = "python")]
1179#[pymethods]
1180impl Form {
1181 #[getter]
1182 fn name(&self) -> PyResult<String> {
1183 Ok(self.name.clone())
1184 }
1185
1186 #[getter]
1187 fn last_modified<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
1188 to_py_datetime_option(py, &self.last_modified)
1189 }
1190
1191 #[getter]
1192 fn who_last_modified_name(&self) -> PyResult<Option<String>> {
1193 Ok(self.who_last_modified_name.clone())
1194 }
1195
1196 #[getter]
1197 fn who_last_modified_role(&self) -> PyResult<Option<String>> {
1198 Ok(self.who_last_modified_role.clone())
1199 }
1200
1201 #[getter]
1202 fn when_created(&self) -> PyResult<usize> {
1203 Ok(self.when_created)
1204 }
1205
1206 #[getter]
1207 fn has_errors(&self) -> PyResult<bool> {
1208 Ok(self.has_errors)
1209 }
1210
1211 #[getter]
1212 fn has_warnings(&self) -> PyResult<bool> {
1213 Ok(self.has_warnings)
1214 }
1215
1216 #[getter]
1217 fn locked(&self) -> PyResult<bool> {
1218 Ok(self.locked)
1219 }
1220
1221 #[getter]
1222 fn user(&self) -> PyResult<Option<String>> {
1223 Ok(self.user.clone())
1224 }
1225
1226 #[getter]
1227 fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
1228 to_py_datetime_option(py, &self.date_time_changed)
1229 }
1230
1231 #[getter]
1232 fn form_title(&self) -> PyResult<String> {
1233 Ok(self.form_title.clone())
1234 }
1235
1236 #[getter]
1237 fn form_index(&self) -> PyResult<usize> {
1238 Ok(self.form_index)
1239 }
1240
1241 #[getter]
1242 fn form_group(&self) -> PyResult<Option<String>> {
1243 Ok(self.form_group.clone())
1244 }
1245
1246 #[getter]
1247 fn form_state(&self) -> PyResult<String> {
1248 Ok(self.form_state.clone())
1249 }
1250
1251 #[getter]
1252 fn states(&self) -> PyResult<Option<Vec<State>>> {
1253 Ok(self.states.clone())
1254 }
1255
1256 #[getter]
1257 fn lock_state(&self) -> PyResult<Option<LockState>> {
1258 Ok(self.lock_state.clone())
1259 }
1260
1261 #[getter]
1262 fn categories(&self) -> PyResult<Option<Vec<Category>>> {
1263 Ok(self.categories.clone())
1264 }
1265
1266 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
1267 let dict = PyDict::new(py);
1268 dict.set_item("name", &self.name)?;
1269 dict.set_item(
1270 "last_modified",
1271 to_py_datetime_option(py, &self.last_modified)?,
1272 )?;
1273 dict.set_item("who_last_modified_name", &self.who_last_modified_name)?;
1274 dict.set_item("who_last_modified_role", &self.who_last_modified_role)?;
1275 dict.set_item("when_created", self.when_created)?;
1276 dict.set_item("has_errors", self.has_errors)?;
1277 dict.set_item("has_warnings", self.has_warnings)?;
1278 dict.set_item("locked", self.locked)?;
1279 dict.set_item("user", &self.user)?;
1280 dict.set_item(
1281 "date_time_changed",
1282 to_py_datetime_option(py, &self.date_time_changed)?,
1283 )?;
1284 dict.set_item("form_title", &self.form_title)?;
1285 dict.set_item("form_index", self.form_index)?;
1286 dict.set_item("form_group", &self.form_group)?;
1287 dict.set_item("form_state", &self.form_state)?;
1288
1289 let mut state_dicts = Vec::new();
1290 if let Some(states) = &self.states {
1291 for state in states {
1292 let state_dict = state.to_dict(py)?;
1293 state_dicts.push(state_dict);
1294 }
1295 dict.set_item("states", state_dicts)?;
1296 } else {
1297 dict.set_item("states", py.None())?;
1298 }
1299
1300 if let Some(lock_state) = &self.lock_state {
1301 dict.set_item("lock_state", lock_state.to_dict(py)?)?;
1302 } else {
1303 dict.set_item("lock_state", py.None())?;
1304 }
1305
1306 if let Some(categories) = &self.categories {
1307 let mut category_dicts = Vec::new();
1308 for category in categories {
1309 let category_dict = category.to_dict(py)?;
1310 category_dicts.push(category_dict);
1311 }
1312 dict.set_item("categories", category_dicts)?;
1313 } else {
1314 dict.set_item("categories", py.None())?;
1315 }
1316
1317 Ok(dict)
1318 }
1319}
1320
1321impl State {
1322 pub fn from_attributes(
1323 attrs: std::collections::HashMap<String, String>,
1324 ) -> Result<Self, crate::errors::Error> {
1325 let value = attrs.get("value").cloned().unwrap_or_default();
1326 let signer = attrs.get("signer").cloned().unwrap_or_default();
1327 let signer_unique_id = attrs.get("signerUniqueId").cloned().unwrap_or_default();
1328
1329 let date_signed = if let Some(ds) = attrs.get("dateSigned") {
1330 if ds.is_empty() {
1331 None
1332 } else {
1333 parse_datetime_internal(ds).ok()
1334 }
1335 } else {
1336 None
1337 };
1338
1339 Ok(State {
1340 value,
1341 signer,
1342 signer_unique_id,
1343 date_signed,
1344 })
1345 }
1346}
1347
1348impl LockState {
1349 pub fn from_attributes(
1350 attrs: std::collections::HashMap<String, String>,
1351 ) -> Result<Self, crate::errors::Error> {
1352 let locked = attrs.get("locked").map(|s| s == "true").unwrap_or(false);
1353 let user = attrs.get("user").filter(|s| !s.is_empty()).cloned();
1354 let user_unique_id = attrs.get("userUniqueId").filter(|s| !s.is_empty()).cloned();
1355
1356 let date_time_changed = if let Some(dtc) = attrs.get("dateTimeChanged") {
1357 if dtc.is_empty() {
1358 None
1359 } else {
1360 parse_datetime_internal(dtc).ok()
1361 }
1362 } else {
1363 None
1364 };
1365
1366 Ok(LockState {
1367 locked,
1368 user,
1369 user_unique_id,
1370 date_time_changed,
1371 })
1372 }
1373}
1374
1375impl Category {
1376 pub fn from_attributes(
1377 attrs: std::collections::HashMap<String, String>,
1378 ) -> Result<Self, crate::errors::Error> {
1379 let name = attrs.get("name").cloned().unwrap_or_default();
1380 let category_type = attrs.get("type").cloned().unwrap_or_default();
1381 let highest_index = attrs
1382 .get("highestIndex")
1383 .and_then(|s| s.parse().ok())
1384 .unwrap_or(0);
1385
1386 Ok(Category {
1387 name,
1388 category_type,
1389 highest_index,
1390 fields: None,
1391 })
1392 }
1393}
1394
1395impl Field {
1396 pub fn from_attributes(
1397 attrs: std::collections::HashMap<String, String>,
1398 ) -> Result<Self, crate::errors::Error> {
1399 let name = attrs.get("name").cloned().unwrap_or_default();
1400 let field_type = attrs.get("type").cloned().unwrap_or_default();
1401 let data_type = attrs.get("dataType").filter(|s| !s.is_empty()).cloned();
1402 let error_code = attrs.get("errorCode").cloned().unwrap_or_default();
1403
1404 let when_created = if let Some(wc) = attrs.get("whenCreated") {
1405 if wc.is_empty() {
1406 None
1407 } else {
1408 Some(parse_datetime_internal(wc)?)
1409 }
1410 } else {
1411 None
1412 };
1413
1414 let keep_history = attrs
1415 .get("keepHistory")
1416 .map(|s| s == "true")
1417 .unwrap_or(false);
1418
1419 Ok(Field {
1420 name,
1421 field_type,
1422 data_type,
1423 error_code,
1424 when_created,
1425 keep_history,
1426 entries: None,
1427 comments: None,
1428 })
1429 }
1430}
1431
1432impl Entry {
1433 pub fn from_attributes(
1434 attrs: std::collections::HashMap<String, String>,
1435 ) -> Result<Self, crate::errors::Error> {
1436 let entry_id = attrs
1437 .get("id")
1438 .or_else(|| attrs.get("entryId"))
1439 .cloned()
1440 .unwrap_or_default();
1441
1442 let reviewed_by = attrs.get("reviewedBy").filter(|s| !s.is_empty()).cloned();
1443 let reviewed_by_unique_id = attrs
1444 .get("reviewedByUniqueId")
1445 .filter(|s| !s.is_empty())
1446 .cloned();
1447
1448 let reviewed_by_when = if let Some(rbw) = attrs.get("reviewedByWhen") {
1449 if rbw.is_empty() {
1450 None
1451 } else {
1452 parse_datetime_internal(rbw).ok()
1453 }
1454 } else {
1455 None
1456 };
1457
1458 Ok(Entry {
1459 entry_id,
1460 reviewed_by,
1461 reviewed_by_unique_id,
1462 reviewed_by_when,
1463 value: None,
1464 reason: None,
1465 })
1466 }
1467}
1468
1469impl Value {
1470 pub fn from_attributes(
1471 attrs: std::collections::HashMap<String, String>,
1472 ) -> Result<Self, crate::errors::Error> {
1473 let by = attrs.get("by").cloned().unwrap_or_default();
1474 let by_unique_id = attrs.get("byUniqueId").filter(|s| !s.is_empty()).cloned();
1475 let role = attrs.get("role").cloned().unwrap_or_default();
1476
1477 let when = if let Some(w) = attrs.get("when") {
1478 if w.is_empty() {
1479 None
1480 } else {
1481 Some(parse_datetime_internal(w)?)
1482 }
1483 } else {
1484 None
1485 };
1486
1487 Ok(Value {
1488 by,
1489 by_unique_id,
1490 role,
1491 when,
1492 value: String::new(),
1493 })
1494 }
1495}
1496
1497impl Reason {
1498 pub fn from_attributes(
1499 attrs: std::collections::HashMap<String, String>,
1500 ) -> Result<Self, crate::errors::Error> {
1501 let by = attrs.get("by").cloned().unwrap_or_default();
1502 let by_unique_id = attrs.get("byUniqueId").filter(|s| !s.is_empty()).cloned();
1503 let role = attrs.get("role").cloned().unwrap_or_default();
1504
1505 let when = if let Some(w) = attrs.get("when") {
1506 if w.is_empty() {
1507 None
1508 } else {
1509 Some(parse_datetime_internal(w)?)
1510 }
1511 } else {
1512 None
1513 };
1514
1515 Ok(Reason {
1516 by,
1517 by_unique_id,
1518 role,
1519 when,
1520 value: String::new(),
1521 })
1522 }
1523}