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(skip_from_py_object)]
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(skip_from_py_object)]
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(skip_from_py_object)]
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, skip_from_py_object)]
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(skip_from_py_object)]
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, skip_from_py_object)]
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(crate) fn from_attributes(
679 attrs: std::collections::HashMap<&str, &str>,
680 ) -> Result<Self, crate::errors::Error> {
681 let name = attrs.get("name").copied().unwrap_or_default().to_string();
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 .map(|s| s.to_string());
697 let who_last_modified_role = attrs
698 .get("whoLastModifiedRole")
699 .filter(|s| !s.is_empty())
700 .map(|s| s.to_string());
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
708 .get("hasErrors")
709 .map(|s| *s == "true")
710 .unwrap_or(false);
711
712 let has_warnings = attrs
713 .get("hasWarnings")
714 .map(|s| *s == "true")
715 .unwrap_or(false);
716
717 let locked = attrs.get("locked").map(|s| *s == "true").unwrap_or(false);
718
719 let user = attrs
720 .get("user")
721 .filter(|s| !s.is_empty())
722 .map(|s| s.to_string());
723
724 let date_time_changed = if let Some(dtc) = attrs.get("dateTimeChanged") {
725 if dtc.is_empty() {
726 None
727 } else {
728 parse_datetime_internal(dtc).ok()
729 }
730 } else {
731 None
732 };
733
734 let form_title = attrs
735 .get("formTitle")
736 .copied()
737 .unwrap_or_default()
738 .to_string();
739
740 let form_index = attrs
741 .get("formIndex")
742 .and_then(|s| s.parse().ok())
743 .unwrap_or(0);
744
745 let form_group = attrs
746 .get("formGroup")
747 .filter(|s| !s.is_empty())
748 .map(|s| s.to_string());
749 let form_state = attrs
750 .get("formState")
751 .copied()
752 .unwrap_or_default()
753 .to_string();
754
755 Ok(Form {
756 name,
757 last_modified,
758 who_last_modified_name,
759 who_last_modified_role,
760 when_created,
761 has_errors,
762 has_warnings,
763 locked,
764 user,
765 date_time_changed,
766 form_title,
767 form_index,
768 form_group,
769 form_state,
770 states: None,
771 lock_state: None,
772 categories: None,
773 })
774 }
775}
776
777fn parse_datetime_internal(s: &str) -> Result<DateTime<Utc>, crate::errors::Error> {
778 if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S %z") {
779 Ok(dt.with_timezone(&Utc))
780 } else if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%z") {
781 Ok(dt.with_timezone(&Utc))
782 } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
783 Ok(dt.with_timezone(&Utc))
784 } else {
785 Err(crate::errors::Error::ParsingError(
786 quick_xml::de::DeError::Custom(format!("Invalid datetime format: {}", s)),
787 ))
788 }
789}
790
791#[cfg(not(feature = "python"))]
792#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
793pub struct State {
794 #[serde(rename = "value")]
795 #[serde(alias = "@value")]
796 #[serde(alias = "value")]
797 pub value: String,
798 #[serde(rename = "signer")]
799 #[serde(alias = "@signer")]
800 #[serde(alias = "signer")]
801 pub signer: String,
802 #[serde(rename = "signerUniqueId")]
803 #[serde(alias = "@signerUniqueId")]
804 #[serde(alias = "signerUniqueId")]
805 pub signer_unique_id: String,
806
807 #[serde(rename = "dateSigned")]
808 #[serde(alias = "@dateSigned")]
809 #[serde(alias = "dateSigned")]
810 #[serde(
811 default = "default_datetime_none",
812 deserialize_with = "deserialize_empty_string_as_none_datetime"
813 )]
814 pub date_signed: Option<DateTime<Utc>>,
815}
816
817#[cfg(feature = "python")]
818#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
819#[pyclass(skip_from_py_object)]
820pub struct State {
821 #[serde(rename = "value")]
822 #[serde(alias = "@value")]
823 #[serde(alias = "value")]
824 pub value: String,
825 #[serde(rename = "signer")]
826 #[serde(alias = "@signer")]
827 #[serde(alias = "signer")]
828 pub signer: String,
829 #[serde(rename = "signerUniqueId")]
830 #[serde(alias = "@signerUniqueId")]
831 #[serde(alias = "signerUniqueId")]
832 pub signer_unique_id: String,
833
834 #[serde(rename = "dateSigned")]
835 #[serde(alias = "@dateSigned")]
836 #[serde(alias = "dateSigned")]
837 #[serde(
838 default = "default_datetime_none",
839 deserialize_with = "deserialize_empty_string_as_none_datetime"
840 )]
841 pub date_signed: Option<DateTime<Utc>>,
842}
843
844#[cfg(feature = "python")]
845#[pymethods]
846impl State {
847 #[getter]
848 fn value(&self) -> PyResult<String> {
849 Ok(self.value.clone())
850 }
851
852 #[getter]
853 fn signer(&self) -> PyResult<String> {
854 Ok(self.signer.clone())
855 }
856
857 #[getter]
858 fn signer_unique_id(&self) -> PyResult<String> {
859 Ok(self.signer_unique_id.clone())
860 }
861
862 #[getter]
863 fn date_signed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
864 to_py_datetime_option(py, &self.date_signed)
865 }
866
867 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
868 let dict = PyDict::new(py);
869 dict.set_item("value", &self.value)?;
870 dict.set_item("signer", &self.signer)?;
871 dict.set_item("signer_unique_id", &self.signer_unique_id)?;
872 dict.set_item("date_signed", to_py_datetime_option(py, &self.date_signed)?)?;
873
874 Ok(dict)
875 }
876}
877
878#[cfg(not(feature = "python"))]
879#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
880pub struct LockState {
881 #[serde(rename = "locked")]
882 #[serde(alias = "@locked")]
883 #[serde(alias = "locked")]
884 pub locked: bool,
885
886 #[serde(rename = "user")]
887 #[serde(alias = "@user")]
888 #[serde(alias = "user")]
889 #[serde(
890 default = "default_string_none",
891 deserialize_with = "deserialize_empty_string_as_none"
892 )]
893 pub user: Option<String>,
894
895 #[serde(rename = "userUniqueId")]
896 #[serde(alias = "@userUniqueId")]
897 #[serde(alias = "userUniqueId")]
898 #[serde(
899 default = "default_string_none",
900 deserialize_with = "deserialize_empty_string_as_none"
901 )]
902 pub user_unique_id: Option<String>,
903
904 #[serde(rename = "dateTimeChanged")]
905 #[serde(alias = "@dateTimeChanged")]
906 #[serde(alias = "dateTimeChanged")]
907 #[serde(
908 default = "default_datetime_none",
909 deserialize_with = "deserialize_empty_string_as_none_datetime"
910 )]
911 pub date_time_changed: Option<DateTime<Utc>>,
912}
913
914#[cfg(feature = "python")]
915#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
916#[pyclass(skip_from_py_object)]
917pub struct LockState {
918 #[serde(rename = "locked")]
919 #[serde(alias = "@locked")]
920 #[serde(alias = "locked")]
921 pub locked: bool,
922
923 #[serde(rename = "user")]
924 #[serde(alias = "@user")]
925 #[serde(alias = "user")]
926 #[serde(
927 default = "default_string_none",
928 deserialize_with = "deserialize_empty_string_as_none"
929 )]
930 pub user: Option<String>,
931
932 #[serde(rename = "userUniqueId")]
933 #[serde(alias = "@userUniqueId")]
934 #[serde(alias = "userUniqueId")]
935 #[serde(
936 default = "default_string_none",
937 deserialize_with = "deserialize_empty_string_as_none"
938 )]
939 pub user_unique_id: Option<String>,
940
941 #[serde(rename = "dateTimeChanged")]
942 #[serde(alias = "@dateTimeChanged")]
943 #[serde(alias = "dateTimeChanged")]
944 #[serde(
945 default = "default_datetime_none",
946 deserialize_with = "deserialize_empty_string_as_none_datetime"
947 )]
948 pub date_time_changed: Option<DateTime<Utc>>,
949}
950
951#[cfg(feature = "python")]
952#[pymethods]
953impl LockState {
954 #[getter]
955 fn locked(&self) -> PyResult<bool> {
956 Ok(self.locked)
957 }
958
959 #[getter]
960 fn user(&self) -> PyResult<Option<String>> {
961 Ok(self.user.clone())
962 }
963
964 #[getter]
965 fn user_unique_id(&self) -> PyResult<Option<String>> {
966 Ok(self.user_unique_id.clone())
967 }
968
969 #[getter]
970 fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
971 to_py_datetime_option(py, &self.date_time_changed)
972 }
973
974 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
975 let dict = PyDict::new(py);
976 dict.set_item("locked", self.locked)?;
977 dict.set_item("user", &self.user)?;
978 dict.set_item("user_unique_id", &self.user_unique_id)?;
979 dict.set_item(
980 "date_time_changed",
981 to_py_datetime_option(py, &self.date_time_changed)?,
982 )?;
983
984 Ok(dict)
985 }
986}
987
988#[cfg(not(feature = "python"))]
989#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
990pub struct Form {
991 #[serde(rename = "name")]
992 #[serde(alias = "@name")]
993 #[serde(alias = "name")]
994 pub name: String,
995
996 #[serde(rename = "lastModified")]
997 #[serde(alias = "@lastModified")]
998 #[serde(alias = "lastModified")]
999 #[serde(
1000 default = "default_datetime_none",
1001 deserialize_with = "deserialize_empty_string_as_none_datetime"
1002 )]
1003 pub last_modified: Option<DateTime<Utc>>,
1004
1005 #[serde(rename = "whoLastModifiedName")]
1006 #[serde(alias = "@whoLastModifiedName")]
1007 #[serde(alias = "whoLastModifiedName")]
1008 #[serde(
1009 default = "default_string_none",
1010 deserialize_with = "deserialize_empty_string_as_none"
1011 )]
1012 pub who_last_modified_name: Option<String>,
1013
1014 #[serde(rename = "whoLastModifiedRole")]
1015 #[serde(alias = "@whoLastModifiedRole")]
1016 #[serde(alias = "whoLastModifiedRole")]
1017 #[serde(
1018 default = "default_string_none",
1019 deserialize_with = "deserialize_empty_string_as_none"
1020 )]
1021 pub who_last_modified_role: Option<String>,
1022
1023 #[serde(rename = "whenCreated")]
1024 #[serde(alias = "@whenCreated")]
1025 #[serde(alias = "whenCreated")]
1026 pub when_created: usize,
1027 #[serde(rename = "hasErrors")]
1028 #[serde(alias = "@hasErrors")]
1029 #[serde(alias = "hasErrors")]
1030 pub has_errors: bool,
1031 #[serde(rename = "hasWarnings")]
1032 #[serde(alias = "@hasWarnings")]
1033 #[serde(alias = "hasWarnings")]
1034 pub has_warnings: bool,
1035 #[serde(rename = "locked")]
1036 #[serde(alias = "@locked")]
1037 #[serde(alias = "locked")]
1038 pub locked: bool,
1039
1040 #[serde(rename = "user")]
1041 #[serde(alias = "@user")]
1042 #[serde(alias = "user")]
1043 #[serde(
1044 default = "default_string_none",
1045 deserialize_with = "deserialize_empty_string_as_none"
1046 )]
1047 pub user: Option<String>,
1048
1049 #[serde(rename = "dateTimeChanged")]
1050 #[serde(alias = "@dateTimeChanged")]
1051 #[serde(alias = "dateTimeChanged")]
1052 #[serde(
1053 default = "default_datetime_none",
1054 deserialize_with = "deserialize_empty_string_as_none_datetime"
1055 )]
1056 pub date_time_changed: Option<DateTime<Utc>>,
1057
1058 #[serde(rename = "formTitle")]
1059 #[serde(alias = "@formTitle")]
1060 #[serde(alias = "formTitle")]
1061 pub form_title: String,
1062 #[serde(rename = "formIndex")]
1063 #[serde(alias = "@formIndex")]
1064 #[serde(alias = "formIndex")]
1065 pub form_index: usize,
1066
1067 #[serde(rename = "formGroup")]
1068 #[serde(alias = "@formGroup")]
1069 #[serde(alias = "formGroup")]
1070 #[serde(
1071 default = "default_string_none",
1072 deserialize_with = "deserialize_empty_string_as_none"
1073 )]
1074 pub form_group: Option<String>,
1075
1076 #[serde(rename = "formState")]
1077 #[serde(alias = "@formState")]
1078 #[serde(alias = "formState")]
1079 pub form_state: String,
1080
1081 #[serde(alias = "state")]
1082 pub states: Option<Vec<State>>,
1083
1084 #[serde(alias = "lockState")]
1085 pub lock_state: Option<LockState>,
1086
1087 #[serde(alias = "category")]
1088 pub categories: Option<Vec<Category>>,
1089}
1090
1091#[cfg(feature = "python")]
1092#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1093#[pyclass(skip_from_py_object)]
1094pub struct Form {
1095 #[serde(rename = "name")]
1096 #[serde(alias = "@name")]
1097 #[serde(alias = "name")]
1098 pub name: String,
1099
1100 #[serde(rename = "lastModified")]
1101 #[serde(alias = "@lastModified")]
1102 #[serde(alias = "lastModified")]
1103 #[serde(
1104 default = "default_datetime_none",
1105 deserialize_with = "deserialize_empty_string_as_none_datetime"
1106 )]
1107 pub last_modified: Option<DateTime<Utc>>,
1108
1109 #[serde(rename = "whoLastModifiedName")]
1110 #[serde(alias = "@whoLastModifiedName")]
1111 #[serde(alias = "whoLastModifiedName")]
1112 #[serde(
1113 default = "default_string_none",
1114 deserialize_with = "deserialize_empty_string_as_none"
1115 )]
1116 pub who_last_modified_name: Option<String>,
1117
1118 #[serde(rename = "whoLastModifiedRole")]
1119 #[serde(alias = "@whoLastModifiedRole")]
1120 #[serde(alias = "whoLastModifiedRole")]
1121 #[serde(
1122 default = "default_string_none",
1123 deserialize_with = "deserialize_empty_string_as_none"
1124 )]
1125 pub who_last_modified_role: Option<String>,
1126
1127 #[serde(rename = "whenCreated")]
1128 #[serde(alias = "@whenCreated")]
1129 #[serde(alias = "whenCreated")]
1130 pub when_created: usize,
1131 #[serde(rename = "hasErrors")]
1132 #[serde(alias = "@hasErrors")]
1133 #[serde(alias = "hasErrors")]
1134 pub has_errors: bool,
1135 #[serde(rename = "hasWarnings")]
1136 #[serde(alias = "@hasWarnings")]
1137 #[serde(alias = "hasWarnings")]
1138 pub has_warnings: bool,
1139 #[serde(rename = "locked")]
1140 #[serde(alias = "@locked")]
1141 #[serde(alias = "locked")]
1142 pub locked: bool,
1143
1144 #[serde(rename = "user")]
1145 #[serde(alias = "@user")]
1146 #[serde(alias = "user")]
1147 #[serde(
1148 default = "default_string_none",
1149 deserialize_with = "deserialize_empty_string_as_none"
1150 )]
1151 pub user: Option<String>,
1152
1153 #[serde(rename = "dateTimeChanged")]
1154 #[serde(alias = "@dateTimeChanged")]
1155 #[serde(alias = "dateTimeChanged")]
1156 #[serde(
1157 default = "default_datetime_none",
1158 deserialize_with = "deserialize_empty_string_as_none_datetime"
1159 )]
1160 pub date_time_changed: Option<DateTime<Utc>>,
1161
1162 #[serde(rename = "formTitle")]
1163 #[serde(alias = "@formTitle")]
1164 #[serde(alias = "formTitle")]
1165 pub form_title: String,
1166 #[serde(rename = "formIndex")]
1167 #[serde(alias = "@formIndex")]
1168 #[serde(alias = "formIndex")]
1169 pub form_index: usize,
1170
1171 #[serde(rename = "formGroup")]
1172 #[serde(alias = "@formGroup")]
1173 #[serde(alias = "formGroup")]
1174 #[serde(
1175 default = "default_string_none",
1176 deserialize_with = "deserialize_empty_string_as_none"
1177 )]
1178 pub form_group: Option<String>,
1179
1180 #[serde(rename = "formState")]
1181 #[serde(alias = "@formState")]
1182 #[serde(alias = "formState")]
1183 pub form_state: String,
1184
1185 #[serde(alias = "state")]
1186 pub states: Option<Vec<State>>,
1187
1188 #[serde(alias = "lockState")]
1189 pub lock_state: Option<LockState>,
1190
1191 #[serde(alias = "category")]
1192 pub categories: Option<Vec<Category>>,
1193}
1194
1195#[cfg(feature = "python")]
1196#[pymethods]
1197impl Form {
1198 #[getter]
1199 fn name(&self) -> PyResult<String> {
1200 Ok(self.name.clone())
1201 }
1202
1203 #[getter]
1204 fn last_modified<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
1205 to_py_datetime_option(py, &self.last_modified)
1206 }
1207
1208 #[getter]
1209 fn who_last_modified_name(&self) -> PyResult<Option<String>> {
1210 Ok(self.who_last_modified_name.clone())
1211 }
1212
1213 #[getter]
1214 fn who_last_modified_role(&self) -> PyResult<Option<String>> {
1215 Ok(self.who_last_modified_role.clone())
1216 }
1217
1218 #[getter]
1219 fn when_created(&self) -> PyResult<usize> {
1220 Ok(self.when_created)
1221 }
1222
1223 #[getter]
1224 fn has_errors(&self) -> PyResult<bool> {
1225 Ok(self.has_errors)
1226 }
1227
1228 #[getter]
1229 fn has_warnings(&self) -> PyResult<bool> {
1230 Ok(self.has_warnings)
1231 }
1232
1233 #[getter]
1234 fn locked(&self) -> PyResult<bool> {
1235 Ok(self.locked)
1236 }
1237
1238 #[getter]
1239 fn user(&self) -> PyResult<Option<String>> {
1240 Ok(self.user.clone())
1241 }
1242
1243 #[getter]
1244 fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
1245 to_py_datetime_option(py, &self.date_time_changed)
1246 }
1247
1248 #[getter]
1249 fn form_title(&self) -> PyResult<String> {
1250 Ok(self.form_title.clone())
1251 }
1252
1253 #[getter]
1254 fn form_index(&self) -> PyResult<usize> {
1255 Ok(self.form_index)
1256 }
1257
1258 #[getter]
1259 fn form_group(&self) -> PyResult<Option<String>> {
1260 Ok(self.form_group.clone())
1261 }
1262
1263 #[getter]
1264 fn form_state(&self) -> PyResult<String> {
1265 Ok(self.form_state.clone())
1266 }
1267
1268 #[getter]
1269 fn states(&self) -> PyResult<Option<Vec<State>>> {
1270 Ok(self.states.clone())
1271 }
1272
1273 #[getter]
1274 fn lock_state(&self) -> PyResult<Option<LockState>> {
1275 Ok(self.lock_state.clone())
1276 }
1277
1278 #[getter]
1279 fn categories(&self) -> PyResult<Option<Vec<Category>>> {
1280 Ok(self.categories.clone())
1281 }
1282
1283 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
1284 let dict = PyDict::new(py);
1285 dict.set_item("name", &self.name)?;
1286 dict.set_item(
1287 "last_modified",
1288 to_py_datetime_option(py, &self.last_modified)?,
1289 )?;
1290 dict.set_item("who_last_modified_name", &self.who_last_modified_name)?;
1291 dict.set_item("who_last_modified_role", &self.who_last_modified_role)?;
1292 dict.set_item("when_created", self.when_created)?;
1293 dict.set_item("has_errors", self.has_errors)?;
1294 dict.set_item("has_warnings", self.has_warnings)?;
1295 dict.set_item("locked", self.locked)?;
1296 dict.set_item("user", &self.user)?;
1297 dict.set_item(
1298 "date_time_changed",
1299 to_py_datetime_option(py, &self.date_time_changed)?,
1300 )?;
1301 dict.set_item("form_title", &self.form_title)?;
1302 dict.set_item("form_index", self.form_index)?;
1303 dict.set_item("form_group", &self.form_group)?;
1304 dict.set_item("form_state", &self.form_state)?;
1305
1306 let mut state_dicts = Vec::new();
1307 if let Some(states) = &self.states {
1308 for state in states {
1309 let state_dict = state.to_dict(py)?;
1310 state_dicts.push(state_dict);
1311 }
1312 dict.set_item("states", state_dicts)?;
1313 } else {
1314 dict.set_item("states", py.None())?;
1315 }
1316
1317 if let Some(lock_state) = &self.lock_state {
1318 dict.set_item("lock_state", lock_state.to_dict(py)?)?;
1319 } else {
1320 dict.set_item("lock_state", py.None())?;
1321 }
1322
1323 if let Some(categories) = &self.categories {
1324 let mut category_dicts = Vec::new();
1325 for category in categories {
1326 let category_dict = category.to_dict(py)?;
1327 category_dicts.push(category_dict);
1328 }
1329 dict.set_item("categories", category_dicts)?;
1330 } else {
1331 dict.set_item("categories", py.None())?;
1332 }
1333
1334 Ok(dict)
1335 }
1336}
1337
1338impl State {
1339 pub(crate) fn from_attributes(
1340 attrs: std::collections::HashMap<&str, &str>,
1341 ) -> Result<Self, crate::errors::Error> {
1342 let value = attrs.get("value").copied().unwrap_or_default().to_string();
1343 let signer = attrs.get("signer").copied().unwrap_or_default().to_string();
1344 let signer_unique_id = attrs
1345 .get("signerUniqueId")
1346 .copied()
1347 .unwrap_or_default()
1348 .to_string();
1349
1350 let date_signed = if let Some(ds) = attrs.get("dateSigned") {
1351 if ds.is_empty() {
1352 None
1353 } else {
1354 parse_datetime_internal(ds).ok()
1355 }
1356 } else {
1357 None
1358 };
1359
1360 Ok(State {
1361 value,
1362 signer,
1363 signer_unique_id,
1364 date_signed,
1365 })
1366 }
1367}
1368
1369impl LockState {
1370 pub(crate) fn from_attributes(
1371 attrs: std::collections::HashMap<&str, &str>,
1372 ) -> Result<Self, crate::errors::Error> {
1373 let locked = attrs.get("locked").map(|s| *s == "true").unwrap_or(false);
1374 let user = attrs
1375 .get("user")
1376 .filter(|s| !s.is_empty())
1377 .map(|s| s.to_string());
1378 let user_unique_id = attrs
1379 .get("userUniqueId")
1380 .filter(|s| !s.is_empty())
1381 .map(|s| s.to_string());
1382
1383 let date_time_changed = if let Some(dtc) = attrs.get("dateTimeChanged") {
1384 if dtc.is_empty() {
1385 None
1386 } else {
1387 parse_datetime_internal(dtc).ok()
1388 }
1389 } else {
1390 None
1391 };
1392
1393 Ok(LockState {
1394 locked,
1395 user,
1396 user_unique_id,
1397 date_time_changed,
1398 })
1399 }
1400}
1401
1402impl Category {
1403 pub(crate) fn from_attributes(
1404 attrs: std::collections::HashMap<&str, &str>,
1405 ) -> Result<Self, crate::errors::Error> {
1406 let name = attrs.get("name").copied().unwrap_or_default().to_string();
1407 let category_type = attrs.get("type").copied().unwrap_or_default().to_string();
1408 let highest_index = attrs
1409 .get("highestIndex")
1410 .and_then(|s| s.parse().ok())
1411 .unwrap_or(0);
1412
1413 Ok(Category {
1414 name,
1415 category_type,
1416 highest_index,
1417 fields: None,
1418 })
1419 }
1420}
1421
1422impl Field {
1423 pub(crate) fn from_attributes(
1424 attrs: std::collections::HashMap<&str, &str>,
1425 ) -> Result<Self, crate::errors::Error> {
1426 let name = attrs.get("name").copied().unwrap_or_default().to_string();
1427 let field_type = attrs.get("type").copied().unwrap_or_default().to_string();
1428 let data_type = attrs
1429 .get("dataType")
1430 .filter(|s| !s.is_empty())
1431 .map(|s| s.to_string());
1432 let error_code = attrs
1433 .get("errorCode")
1434 .copied()
1435 .unwrap_or_default()
1436 .to_string();
1437
1438 let when_created = if let Some(wc) = attrs.get("whenCreated") {
1439 if wc.is_empty() {
1440 None
1441 } else {
1442 Some(parse_datetime_internal(wc)?)
1443 }
1444 } else {
1445 None
1446 };
1447
1448 let keep_history = attrs
1449 .get("keepHistory")
1450 .map(|s| *s == "true")
1451 .unwrap_or(false);
1452
1453 Ok(Field {
1454 name,
1455 field_type,
1456 data_type,
1457 error_code,
1458 when_created,
1459 keep_history,
1460 entries: None,
1461 comments: None,
1462 })
1463 }
1464}
1465
1466impl Entry {
1467 pub(crate) fn from_attributes(
1468 attrs: std::collections::HashMap<&str, &str>,
1469 ) -> Result<Self, crate::errors::Error> {
1470 let entry_id = attrs
1471 .get("id")
1472 .or_else(|| attrs.get("entryId"))
1473 .copied()
1474 .unwrap_or_default()
1475 .to_string();
1476
1477 let reviewed_by = attrs
1478 .get("reviewedBy")
1479 .filter(|s| !s.is_empty())
1480 .map(|s| s.to_string());
1481 let reviewed_by_unique_id = attrs
1482 .get("reviewedByUniqueId")
1483 .filter(|s| !s.is_empty())
1484 .map(|s| s.to_string());
1485
1486 let reviewed_by_when = if let Some(rbw) = attrs.get("reviewedByWhen") {
1487 if rbw.is_empty() {
1488 None
1489 } else {
1490 parse_datetime_internal(rbw).ok()
1491 }
1492 } else {
1493 None
1494 };
1495
1496 Ok(Entry {
1497 entry_id,
1498 reviewed_by,
1499 reviewed_by_unique_id,
1500 reviewed_by_when,
1501 value: None,
1502 reason: None,
1503 })
1504 }
1505}
1506
1507impl Value {
1508 pub(crate) fn from_attributes(
1509 attrs: std::collections::HashMap<&str, &str>,
1510 ) -> Result<Self, crate::errors::Error> {
1511 let by = attrs.get("by").copied().unwrap_or_default().to_string();
1512 let by_unique_id = attrs
1513 .get("byUniqueId")
1514 .filter(|s| !s.is_empty())
1515 .map(|s| s.to_string());
1516 let role = attrs.get("role").copied().unwrap_or_default().to_string();
1517
1518 let when = if let Some(w) = attrs.get("when") {
1519 if w.is_empty() {
1520 None
1521 } else {
1522 Some(parse_datetime_internal(w)?)
1523 }
1524 } else {
1525 None
1526 };
1527
1528 Ok(Value {
1529 by,
1530 by_unique_id,
1531 role,
1532 when,
1533 value: String::new(),
1534 })
1535 }
1536}
1537
1538impl Reason {
1539 pub(crate) fn from_attributes(
1540 attrs: std::collections::HashMap<&str, &str>,
1541 ) -> Result<Self, crate::errors::Error> {
1542 let by = attrs.get("by").copied().unwrap_or_default().to_string();
1543 let by_unique_id = attrs
1544 .get("byUniqueId")
1545 .filter(|s| !s.is_empty())
1546 .map(|s| s.to_string());
1547 let role = attrs.get("role").copied().unwrap_or_default().to_string();
1548
1549 let when = if let Some(w) = attrs.get("when") {
1550 if w.is_empty() {
1551 None
1552 } else {
1553 Some(parse_datetime_internal(w)?)
1554 }
1555 } else {
1556 None
1557 };
1558
1559 Ok(Reason {
1560 by,
1561 by_unique_id,
1562 role,
1563 when,
1564 value: String::new(),
1565 })
1566 }
1567}