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 pub value: Option<Value>,
242 pub reason: Option<Reason>,
243}
244
245#[cfg(feature = "python")]
246#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
247#[pyclass(get_all)]
248pub struct Entry {
249 #[serde(rename = "entryId")]
250 #[serde(alias = "@id")]
251 #[serde(alias = "entryId")]
252 pub entry_id: String,
253 pub value: Option<Value>,
254 pub reason: Option<Reason>,
255}
256
257#[cfg(feature = "python")]
258#[pymethods]
259impl Entry {
260 #[getter]
261 fn entry_id(&self) -> PyResult<String> {
262 Ok(self.entry_id.clone())
263 }
264
265 #[getter]
266 fn value(&self) -> PyResult<Option<Value>> {
267 Ok(self.value.clone())
268 }
269
270 #[getter]
271 fn reason(&self) -> PyResult<Option<Reason>> {
272 Ok(self.reason.clone())
273 }
274
275 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
276 let dict = PyDict::new(py);
277 dict.set_item("entry_id", &self.entry_id)?;
278 if let Some(value) = &self.value {
279 dict.set_item("value", value.to_dict(py)?)?;
280 } else {
281 dict.set_item("value", py.None())?;
282 }
283 if let Some(reason) = &self.reason {
284 dict.set_item("reason", reason.to_dict(py)?)?;
285 } else {
286 dict.set_item("reason", py.None())?;
287 }
288
289 Ok(dict)
290 }
291}
292
293#[cfg(not(feature = "python"))]
294#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
295pub struct Comment {
296 #[serde(rename = "commentId")]
297 #[serde(alias = "@id")]
298 #[serde(alias = "commentId")]
299 pub comment_id: String,
300 pub value: Option<Value>,
301}
302
303#[cfg(feature = "python")]
304#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
305#[pyclass(get_all)]
306pub struct Comment {
307 #[serde(rename = "commentId")]
308 #[serde(alias = "@id")]
309 #[serde(alias = "commentId")]
310 pub comment_id: String,
311 pub value: Option<Value>,
312}
313
314#[cfg(feature = "python")]
315#[pymethods]
316impl Comment {
317 #[getter]
318 fn comment_id(&self) -> PyResult<String> {
319 Ok(self.comment_id.clone())
320 }
321
322 #[getter]
323 fn value(&self) -> PyResult<Option<Value>> {
324 Ok(self.value.clone())
325 }
326
327 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
328 let dict = PyDict::new(py);
329 dict.set_item("comment_id", &self.comment_id)?;
330 if let Some(value) = &self.value {
331 dict.set_item("value", value.to_dict(py)?)?;
332 } else {
333 dict.set_item("value", py.None())?;
334 }
335
336 Ok(dict)
337 }
338}
339
340#[cfg(not(feature = "python"))]
341#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
342pub struct Field {
343 #[serde(rename = "name")]
344 #[serde(alias = "@name")]
345 #[serde(alias = "name")]
346 pub name: String,
347
348 #[serde(rename = "fieldType")]
349 #[serde(alias = "@type")]
350 #[serde(alias = "fieldType")]
351 pub field_type: String,
352
353 #[serde(rename = "dataType")]
354 #[serde(alias = "@dataType")]
355 #[serde(alias = "dataType")]
356 #[serde(
357 default = "default_string_none",
358 deserialize_with = "deserialize_empty_string_as_none"
359 )]
360 pub data_type: Option<String>,
361 #[serde(rename = "errorCode")]
362 #[serde(alias = "@errorCode")]
363 #[serde(alias = "errorCode")]
364 pub error_code: String,
365 #[serde(rename = "whenCreated")]
366 #[serde(alias = "@whenCreated")]
367 #[serde(alias = "whenCreated")]
368 pub when_created: Option<DateTime<Utc>>,
369 #[serde(rename = "keepHistory")]
370 #[serde(alias = "@keepHistory")]
371 #[serde(alias = "keepHistory")]
372 pub keep_history: bool,
373
374 #[serde(alias = "entry")]
375 pub entries: Option<Vec<Entry>>,
376
377 #[serde(alias = "comment")]
378 pub comments: Option<Vec<Comment>>,
379}
380
381#[cfg(feature = "python")]
382#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
383#[pyclass]
384pub struct Field {
385 #[serde(rename = "name")]
386 #[serde(alias = "@name")]
387 #[serde(alias = "name")]
388 pub name: String,
389
390 #[serde(rename = "fieldType")]
391 #[serde(alias = "@type")]
392 #[serde(alias = "fieldType")]
393 pub field_type: String,
394
395 #[serde(rename = "dataType")]
396 #[serde(alias = "@dataType")]
397 #[serde(alias = "dataType")]
398 #[serde(
399 default = "default_string_none",
400 deserialize_with = "deserialize_empty_string_as_none"
401 )]
402 pub data_type: Option<String>,
403
404 #[serde(rename = "errorCode")]
405 #[serde(alias = "@errorCode")]
406 #[serde(alias = "errorCode")]
407 pub error_code: String,
408 #[serde(rename = "whenCreated")]
409 #[serde(alias = "@whenCreated")]
410 #[serde(alias = "whenCreated")]
411 pub when_created: Option<DateTime<Utc>>,
412 #[serde(rename = "keepHistory")]
413 #[serde(alias = "@keepHistory")]
414 #[serde(alias = "keepHistory")]
415 pub keep_history: bool,
416
417 #[serde(alias = "entry")]
418 pub entries: Option<Vec<Entry>>,
419
420 #[serde(alias = "comment")]
421 pub comments: Option<Vec<Comment>>,
422}
423
424#[cfg(feature = "python")]
425#[pymethods]
426impl Field {
427 #[getter]
428 fn name(&self) -> PyResult<String> {
429 Ok(self.name.clone())
430 }
431
432 #[getter]
433 fn field_type(&self) -> PyResult<String> {
434 Ok(self.field_type.clone())
435 }
436
437 #[getter]
438 fn data_type(&self) -> PyResult<Option<String>> {
439 Ok(self.data_type.clone())
440 }
441
442 #[getter]
443 fn error_code(&self) -> PyResult<String> {
444 Ok(self.error_code.clone())
445 }
446
447 #[getter]
448 fn when_created<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
449 self.when_created
450 .as_ref()
451 .map(|dt| to_py_datetime(py, dt))
452 .transpose()
453 }
454
455 #[getter]
456 fn keep_history(&self) -> PyResult<bool> {
457 Ok(self.keep_history)
458 }
459
460 #[getter]
461 fn entries(&self) -> PyResult<Option<Vec<Entry>>> {
462 Ok(self.entries.clone())
463 }
464
465 #[getter]
466 fn comments(&self) -> PyResult<Option<Vec<Comment>>> {
467 Ok(self.comments.clone())
468 }
469
470 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
471 let dict = PyDict::new(py);
472 dict.set_item("name", &self.name)?;
473 dict.set_item("field_type", &self.field_type)?;
474 dict.set_item("data_type", &self.data_type)?;
475 dict.set_item("error_code", &self.error_code)?;
476 dict.set_item(
477 "when_created",
478 self.when_created
479 .as_ref()
480 .map(|dt| to_py_datetime(py, dt))
481 .transpose()?,
482 )?;
483 dict.set_item("keep_history", self.keep_history)?;
484
485 let mut entry_dicts = Vec::new();
486 if let Some(entries) = &self.entries {
487 for entry in entries {
488 let entry_dict = entry.to_dict(py)?;
489 entry_dicts.push(entry_dict);
490 }
491 dict.set_item("entries", entry_dicts)?;
492 } else {
493 dict.set_item("entries", py.None())?;
494 }
495
496 let mut comment_dicts = Vec::new();
497 if let Some(comments) = &self.comments {
498 for comment in comments {
499 let comment_dict = comment.to_dict(py)?;
500 comment_dicts.push(comment_dict);
501 }
502 dict.set_item("comments", comment_dicts)?;
503 } else {
504 dict.set_item("comments", py.None())?;
505 }
506
507 Ok(dict)
508 }
509}
510
511#[cfg(not(feature = "python"))]
512#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
513pub struct Category {
514 #[serde(rename = "name")]
515 #[serde(alias = "@name")]
516 #[serde(alias = "name")]
517 pub name: String,
518
519 #[serde(rename = "categoryType")]
520 #[serde(alias = "@type")]
521 #[serde(alias = "categoryType")]
522 pub category_type: String,
523
524 #[serde(rename = "highestIndex")]
525 #[serde(alias = "@highestIndex")]
526 #[serde(alias = "highestIndex")]
527 pub highest_index: usize,
528
529 #[serde(alias = "field")]
530 pub fields: Option<Vec<Field>>,
531}
532
533#[cfg(feature = "python")]
534#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
535#[pyclass(get_all)]
536pub struct Category {
537 #[serde(rename = "name")]
538 #[serde(alias = "@name")]
539 #[serde(alias = "name")]
540 pub name: String,
541
542 #[serde(rename = "categoryType")]
543 #[serde(alias = "@type")]
544 #[serde(alias = "categoryType")]
545 pub category_type: String,
546
547 #[serde(rename = "highestIndex")]
548 #[serde(alias = "@highestIndex")]
549 #[serde(alias = "highestIndex")]
550 pub highest_index: usize,
551
552 #[serde(alias = "field")]
553 pub fields: Option<Vec<Field>>,
554}
555
556#[cfg(feature = "python")]
557#[pymethods]
558impl Category {
559 #[getter]
560 fn name(&self) -> PyResult<String> {
561 Ok(self.name.clone())
562 }
563
564 #[getter]
565 fn category_type(&self) -> PyResult<String> {
566 Ok(self.category_type.clone())
567 }
568
569 #[getter]
570 fn highest_index(&self) -> PyResult<usize> {
571 Ok(self.highest_index)
572 }
573
574 #[getter]
575 fn fields(&self) -> PyResult<Option<Vec<Field>>> {
576 Ok(self.fields.clone())
577 }
578
579 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
580 let dict = PyDict::new(py);
581 dict.set_item("name", &self.name)?;
582 dict.set_item("category_type", &self.category_type)?;
583 dict.set_item("highest_index", self.highest_index)?;
584
585 let mut field_dicts = Vec::new();
586 if let Some(fields) = &self.fields {
587 for field in fields {
588 let field_dict = field.to_dict(py)?;
589 field_dicts.push(field_dict);
590 }
591 dict.set_item("fields", field_dicts)?;
592 } else {
593 dict.set_item("fields", py.None())?;
594 }
595
596 Ok(dict)
597 }
598}
599
600impl Form {
601 pub fn from_attributes(
602 attrs: std::collections::HashMap<String, String>,
603 ) -> Result<Self, crate::errors::Error> {
604 let name = attrs.get("name").cloned().unwrap_or_default();
605
606 let last_modified = if let Some(lm) = attrs.get("lastModified") {
607 if lm.is_empty() {
608 None
609 } else {
610 parse_datetime_internal(lm).ok()
611 }
612 } else {
613 None
614 };
615
616 let who_last_modified_name = attrs
617 .get("whoLastModifiedName")
618 .filter(|s| !s.is_empty())
619 .cloned();
620 let who_last_modified_role = attrs
621 .get("whoLastModifiedRole")
622 .filter(|s| !s.is_empty())
623 .cloned();
624
625 let when_created = attrs
626 .get("whenCreated")
627 .and_then(|s| s.parse().ok())
628 .unwrap_or(0);
629
630 let has_errors = attrs.get("hasErrors").map(|s| s == "true").unwrap_or(false);
631
632 let has_warnings = attrs
633 .get("hasWarnings")
634 .map(|s| s == "true")
635 .unwrap_or(false);
636
637 let locked = attrs.get("locked").map(|s| s == "true").unwrap_or(false);
638
639 let user = attrs.get("user").filter(|s| !s.is_empty()).cloned();
640
641 let date_time_changed = if let Some(dtc) = attrs.get("dateTimeChanged") {
642 if dtc.is_empty() {
643 None
644 } else {
645 parse_datetime_internal(dtc).ok()
646 }
647 } else {
648 None
649 };
650
651 let form_title = attrs.get("formTitle").cloned().unwrap_or_default();
652
653 let form_index = attrs
654 .get("formIndex")
655 .and_then(|s| s.parse().ok())
656 .unwrap_or(0);
657
658 let form_group = attrs.get("formGroup").filter(|s| !s.is_empty()).cloned();
659 let form_state = attrs.get("formState").cloned().unwrap_or_default();
660
661 Ok(Form {
662 name,
663 last_modified,
664 who_last_modified_name,
665 who_last_modified_role,
666 when_created,
667 has_errors,
668 has_warnings,
669 locked,
670 user,
671 date_time_changed,
672 form_title,
673 form_index,
674 form_group,
675 form_state,
676 states: None,
677 categories: None,
678 })
679 }
680}
681
682fn parse_datetime_internal(s: &str) -> Result<DateTime<Utc>, crate::errors::Error> {
683 if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S %z") {
684 Ok(dt.with_timezone(&Utc))
685 } else if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%z") {
686 Ok(dt.with_timezone(&Utc))
687 } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
688 Ok(dt.with_timezone(&Utc))
689 } else {
690 Err(crate::errors::Error::ParsingError(
691 quick_xml::de::DeError::Custom(format!("Invalid datetime format: {}", s)),
692 ))
693 }
694}
695
696#[cfg(not(feature = "python"))]
697#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
698pub struct State {
699 #[serde(rename = "value")]
700 #[serde(alias = "@value")]
701 #[serde(alias = "value")]
702 pub value: String,
703 #[serde(rename = "signer")]
704 #[serde(alias = "@signer")]
705 #[serde(alias = "signer")]
706 pub signer: String,
707 #[serde(rename = "signerUniqueId")]
708 #[serde(alias = "@signerUniqueId")]
709 #[serde(alias = "signerUniqueId")]
710 pub signer_unique_id: String,
711
712 #[serde(rename = "dateSigned")]
713 #[serde(alias = "@dateSigned")]
714 #[serde(alias = "dateSigned")]
715 #[serde(
716 default = "default_datetime_none",
717 deserialize_with = "deserialize_empty_string_as_none_datetime"
718 )]
719 pub date_signed: Option<DateTime<Utc>>,
720}
721
722#[cfg(feature = "python")]
723#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
724#[pyclass]
725pub struct State {
726 #[serde(rename = "value")]
727 #[serde(alias = "@value")]
728 #[serde(alias = "value")]
729 pub value: String,
730 #[serde(rename = "signer")]
731 #[serde(alias = "@signer")]
732 #[serde(alias = "signer")]
733 pub signer: String,
734 #[serde(rename = "signerUniqueId")]
735 #[serde(alias = "@signerUniqueId")]
736 #[serde(alias = "signerUniqueId")]
737 pub signer_unique_id: String,
738
739 #[serde(rename = "dateSigned")]
740 #[serde(alias = "@dateSigned")]
741 #[serde(alias = "dateSigned")]
742 #[serde(
743 default = "default_datetime_none",
744 deserialize_with = "deserialize_empty_string_as_none_datetime"
745 )]
746 pub date_signed: Option<DateTime<Utc>>,
747}
748
749#[cfg(feature = "python")]
750#[pymethods]
751impl State {
752 #[getter]
753 fn value(&self) -> PyResult<String> {
754 Ok(self.value.clone())
755 }
756
757 #[getter]
758 fn signer(&self) -> PyResult<String> {
759 Ok(self.signer.clone())
760 }
761
762 #[getter]
763 fn signer_unique_id(&self) -> PyResult<String> {
764 Ok(self.signer_unique_id.clone())
765 }
766
767 #[getter]
768 fn date_signed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
769 to_py_datetime_option(py, &self.date_signed)
770 }
771
772 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
773 let dict = PyDict::new(py);
774 dict.set_item("value", &self.value)?;
775 dict.set_item("signer", &self.signer)?;
776 dict.set_item("signer_unique_id", &self.signer_unique_id)?;
777 dict.set_item("date_signed", to_py_datetime_option(py, &self.date_signed)?)?;
778
779 Ok(dict)
780 }
781}
782
783#[cfg(not(feature = "python"))]
784#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
785pub struct Form {
786 #[serde(rename = "name")]
787 #[serde(alias = "@name")]
788 #[serde(alias = "name")]
789 pub name: String,
790
791 #[serde(rename = "lastModified")]
792 #[serde(alias = "@lastModified")]
793 #[serde(alias = "lastModified")]
794 #[serde(
795 default = "default_datetime_none",
796 deserialize_with = "deserialize_empty_string_as_none_datetime"
797 )]
798 pub last_modified: Option<DateTime<Utc>>,
799
800 #[serde(rename = "whoLastModifiedName")]
801 #[serde(alias = "@whoLastModifiedName")]
802 #[serde(alias = "whoLastModifiedName")]
803 #[serde(
804 default = "default_string_none",
805 deserialize_with = "deserialize_empty_string_as_none"
806 )]
807 pub who_last_modified_name: Option<String>,
808
809 #[serde(rename = "whoLastModifiedRole")]
810 #[serde(alias = "@whoLastModifiedRole")]
811 #[serde(alias = "whoLastModifiedRole")]
812 #[serde(
813 default = "default_string_none",
814 deserialize_with = "deserialize_empty_string_as_none"
815 )]
816 pub who_last_modified_role: Option<String>,
817
818 #[serde(rename = "whenCreated")]
819 #[serde(alias = "@whenCreated")]
820 #[serde(alias = "whenCreated")]
821 pub when_created: usize,
822 #[serde(rename = "hasErrors")]
823 #[serde(alias = "@hasErrors")]
824 #[serde(alias = "hasErrors")]
825 pub has_errors: bool,
826 #[serde(rename = "hasWarnings")]
827 #[serde(alias = "@hasWarnings")]
828 #[serde(alias = "hasWarnings")]
829 pub has_warnings: bool,
830 #[serde(rename = "locked")]
831 #[serde(alias = "@locked")]
832 #[serde(alias = "locked")]
833 pub locked: bool,
834
835 #[serde(rename = "user")]
836 #[serde(alias = "@user")]
837 #[serde(alias = "user")]
838 #[serde(
839 default = "default_string_none",
840 deserialize_with = "deserialize_empty_string_as_none"
841 )]
842 pub user: Option<String>,
843
844 #[serde(rename = "dateTimeChanged")]
845 #[serde(alias = "@dateTimeChanged")]
846 #[serde(alias = "dateTimeChanged")]
847 #[serde(
848 default = "default_datetime_none",
849 deserialize_with = "deserialize_empty_string_as_none_datetime"
850 )]
851 pub date_time_changed: Option<DateTime<Utc>>,
852
853 #[serde(rename = "formTitle")]
854 #[serde(alias = "@formTitle")]
855 #[serde(alias = "formTitle")]
856 pub form_title: String,
857 #[serde(rename = "formIndex")]
858 #[serde(alias = "@formIndex")]
859 #[serde(alias = "formIndex")]
860 pub form_index: usize,
861
862 #[serde(rename = "formGroup")]
863 #[serde(alias = "@formGroup")]
864 #[serde(alias = "formGroup")]
865 #[serde(
866 default = "default_string_none",
867 deserialize_with = "deserialize_empty_string_as_none"
868 )]
869 pub form_group: Option<String>,
870
871 #[serde(rename = "formState")]
872 #[serde(alias = "@formState")]
873 #[serde(alias = "formState")]
874 pub form_state: String,
875
876 #[serde(alias = "state")]
877 pub states: Option<Vec<State>>,
878
879 #[serde(alias = "category")]
880 pub categories: Option<Vec<Category>>,
881}
882
883#[cfg(feature = "python")]
884#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
885#[pyclass]
886pub struct Form {
887 #[serde(rename = "name")]
888 #[serde(alias = "@name")]
889 #[serde(alias = "name")]
890 pub name: String,
891
892 #[serde(rename = "lastModified")]
893 #[serde(alias = "@lastModified")]
894 #[serde(alias = "lastModified")]
895 #[serde(
896 default = "default_datetime_none",
897 deserialize_with = "deserialize_empty_string_as_none_datetime"
898 )]
899 pub last_modified: Option<DateTime<Utc>>,
900
901 #[serde(rename = "whoLastModifiedName")]
902 #[serde(alias = "@whoLastModifiedName")]
903 #[serde(alias = "whoLastModifiedName")]
904 #[serde(
905 default = "default_string_none",
906 deserialize_with = "deserialize_empty_string_as_none"
907 )]
908 pub who_last_modified_name: Option<String>,
909
910 #[serde(rename = "whoLastModifiedRole")]
911 #[serde(alias = "@whoLastModifiedRole")]
912 #[serde(alias = "whoLastModifiedRole")]
913 #[serde(
914 default = "default_string_none",
915 deserialize_with = "deserialize_empty_string_as_none"
916 )]
917 pub who_last_modified_role: Option<String>,
918
919 #[serde(rename = "whenCreated")]
920 #[serde(alias = "@whenCreated")]
921 #[serde(alias = "whenCreated")]
922 pub when_created: usize,
923 #[serde(rename = "hasErrors")]
924 #[serde(alias = "@hasErrors")]
925 #[serde(alias = "hasErrors")]
926 pub has_errors: bool,
927 #[serde(rename = "hasWarnings")]
928 #[serde(alias = "@hasWarnings")]
929 #[serde(alias = "hasWarnings")]
930 pub has_warnings: bool,
931 #[serde(rename = "locked")]
932 #[serde(alias = "@locked")]
933 #[serde(alias = "locked")]
934 pub locked: bool,
935
936 #[serde(rename = "user")]
937 #[serde(alias = "@user")]
938 #[serde(alias = "user")]
939 #[serde(
940 default = "default_string_none",
941 deserialize_with = "deserialize_empty_string_as_none"
942 )]
943 pub user: Option<String>,
944
945 #[serde(rename = "dateTimeChanged")]
946 #[serde(alias = "@dateTimeChanged")]
947 #[serde(alias = "dateTimeChanged")]
948 #[serde(
949 default = "default_datetime_none",
950 deserialize_with = "deserialize_empty_string_as_none_datetime"
951 )]
952 pub date_time_changed: Option<DateTime<Utc>>,
953
954 #[serde(rename = "formTitle")]
955 #[serde(alias = "@formTitle")]
956 #[serde(alias = "formTitle")]
957 pub form_title: String,
958 #[serde(rename = "formIndex")]
959 #[serde(alias = "@formIndex")]
960 #[serde(alias = "formIndex")]
961 pub form_index: usize,
962
963 #[serde(rename = "formGroup")]
964 #[serde(alias = "@formGroup")]
965 #[serde(alias = "formGroup")]
966 #[serde(
967 default = "default_string_none",
968 deserialize_with = "deserialize_empty_string_as_none"
969 )]
970 pub form_group: Option<String>,
971
972 #[serde(rename = "formState")]
973 #[serde(alias = "@formState")]
974 #[serde(alias = "formState")]
975 pub form_state: String,
976
977 #[serde(alias = "state")]
978 pub states: Option<Vec<State>>,
979
980 #[serde(alias = "category")]
981 pub categories: Option<Vec<Category>>,
982}
983
984#[cfg(feature = "python")]
985#[pymethods]
986impl Form {
987 #[getter]
988 fn name(&self) -> PyResult<String> {
989 Ok(self.name.clone())
990 }
991
992 #[getter]
993 fn last_modified<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
994 to_py_datetime_option(py, &self.last_modified)
995 }
996
997 #[getter]
998 fn who_last_modified_name(&self) -> PyResult<Option<String>> {
999 Ok(self.who_last_modified_name.clone())
1000 }
1001
1002 #[getter]
1003 fn who_last_modified_role(&self) -> PyResult<Option<String>> {
1004 Ok(self.who_last_modified_role.clone())
1005 }
1006
1007 #[getter]
1008 fn when_created(&self) -> PyResult<usize> {
1009 Ok(self.when_created)
1010 }
1011
1012 #[getter]
1013 fn has_errors(&self) -> PyResult<bool> {
1014 Ok(self.has_errors)
1015 }
1016
1017 #[getter]
1018 fn has_warnings(&self) -> PyResult<bool> {
1019 Ok(self.has_warnings)
1020 }
1021
1022 #[getter]
1023 fn locked(&self) -> PyResult<bool> {
1024 Ok(self.locked)
1025 }
1026
1027 #[getter]
1028 fn user(&self) -> PyResult<Option<String>> {
1029 Ok(self.user.clone())
1030 }
1031
1032 #[getter]
1033 fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
1034 to_py_datetime_option(py, &self.date_time_changed)
1035 }
1036
1037 #[getter]
1038 fn form_title(&self) -> PyResult<String> {
1039 Ok(self.form_title.clone())
1040 }
1041
1042 #[getter]
1043 fn form_index(&self) -> PyResult<usize> {
1044 Ok(self.form_index)
1045 }
1046
1047 #[getter]
1048 fn form_group(&self) -> PyResult<Option<String>> {
1049 Ok(self.form_group.clone())
1050 }
1051
1052 #[getter]
1053 fn form_state(&self) -> PyResult<String> {
1054 Ok(self.form_state.clone())
1055 }
1056
1057 #[getter]
1058 fn states(&self) -> PyResult<Option<Vec<State>>> {
1059 Ok(self.states.clone())
1060 }
1061
1062 #[getter]
1063 fn categories(&self) -> PyResult<Option<Vec<Category>>> {
1064 Ok(self.categories.clone())
1065 }
1066
1067 pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
1068 let dict = PyDict::new(py);
1069 dict.set_item("name", &self.name)?;
1070 dict.set_item(
1071 "last_modified",
1072 to_py_datetime_option(py, &self.last_modified)?,
1073 )?;
1074 dict.set_item("who_last_modified_name", &self.who_last_modified_name)?;
1075 dict.set_item("who_last_modified_role", &self.who_last_modified_role)?;
1076 dict.set_item("when_created", self.when_created)?;
1077 dict.set_item("has_errors", self.has_errors)?;
1078 dict.set_item("has_warnings", self.has_warnings)?;
1079 dict.set_item("locked", self.locked)?;
1080 dict.set_item("user", &self.user)?;
1081 dict.set_item(
1082 "date_time_changed",
1083 to_py_datetime_option(py, &self.date_time_changed)?,
1084 )?;
1085 dict.set_item("form_title", &self.form_title)?;
1086 dict.set_item("form_index", self.form_index)?;
1087 dict.set_item("form_group", &self.form_group)?;
1088 dict.set_item("form_state", &self.form_state)?;
1089
1090 let mut state_dicts = Vec::new();
1091 if let Some(states) = &self.states {
1092 for state in states {
1093 let state_dict = state.to_dict(py)?;
1094 state_dicts.push(state_dict);
1095 }
1096 dict.set_item("states", state_dicts)?;
1097 } else {
1098 dict.set_item("states", py.None())?;
1099 }
1100
1101 if let Some(categories) = &self.categories {
1102 let mut category_dicts = Vec::new();
1103 for category in categories {
1104 let category_dict = category.to_dict(py)?;
1105 category_dicts.push(category_dict);
1106 }
1107 dict.set_item("categories", category_dicts)?;
1108 } else {
1109 dict.set_item("categories", py.None())?;
1110 }
1111
1112 Ok(dict)
1113 }
1114}
1115
1116impl State {
1117 pub fn from_attributes(
1118 attrs: std::collections::HashMap<String, String>,
1119 ) -> Result<Self, crate::errors::Error> {
1120 let value = attrs.get("value").cloned().unwrap_or_default();
1121 let signer = attrs.get("signer").cloned().unwrap_or_default();
1122 let signer_unique_id = attrs.get("signerUniqueId").cloned().unwrap_or_default();
1123
1124 let date_signed = if let Some(ds) = attrs.get("dateSigned") {
1125 if ds.is_empty() {
1126 None
1127 } else {
1128 parse_datetime_internal(ds).ok()
1129 }
1130 } else {
1131 None
1132 };
1133
1134 Ok(State {
1135 value,
1136 signer,
1137 signer_unique_id,
1138 date_signed,
1139 })
1140 }
1141}
1142
1143impl Category {
1144 pub fn from_attributes(
1145 attrs: std::collections::HashMap<String, String>,
1146 ) -> Result<Self, crate::errors::Error> {
1147 let name = attrs.get("name").cloned().unwrap_or_default();
1148 let category_type = attrs.get("type").cloned().unwrap_or_default();
1149 let highest_index = attrs
1150 .get("highestIndex")
1151 .and_then(|s| s.parse().ok())
1152 .unwrap_or(0);
1153
1154 Ok(Category {
1155 name,
1156 category_type,
1157 highest_index,
1158 fields: None,
1159 })
1160 }
1161}
1162
1163impl Field {
1164 pub fn from_attributes(
1165 attrs: std::collections::HashMap<String, String>,
1166 ) -> Result<Self, crate::errors::Error> {
1167 let name = attrs.get("name").cloned().unwrap_or_default();
1168 let field_type = attrs.get("type").cloned().unwrap_or_default();
1169 let data_type = attrs.get("dataType").filter(|s| !s.is_empty()).cloned();
1170 let error_code = attrs.get("errorCode").cloned().unwrap_or_default();
1171
1172 let when_created = if let Some(wc) = attrs.get("whenCreated") {
1173 if wc.is_empty() {
1174 None
1175 } else {
1176 Some(parse_datetime_internal(wc)?)
1177 }
1178 } else {
1179 None
1180 };
1181
1182 let keep_history = attrs
1183 .get("keepHistory")
1184 .map(|s| s == "true")
1185 .unwrap_or(false);
1186
1187 Ok(Field {
1188 name,
1189 field_type,
1190 data_type,
1191 error_code,
1192 when_created,
1193 keep_history,
1194 entries: None,
1195 comments: None,
1196 })
1197 }
1198}
1199
1200impl Entry {
1201 pub fn from_attributes(
1202 attrs: std::collections::HashMap<String, String>,
1203 ) -> Result<Self, crate::errors::Error> {
1204 let entry_id = attrs
1205 .get("id")
1206 .or_else(|| attrs.get("entryId"))
1207 .cloned()
1208 .unwrap_or_default();
1209
1210 Ok(Entry {
1211 entry_id,
1212 value: None,
1213 reason: None,
1214 })
1215 }
1216}
1217
1218impl Value {
1219 pub fn from_attributes(
1220 attrs: std::collections::HashMap<String, String>,
1221 ) -> Result<Self, crate::errors::Error> {
1222 let by = attrs.get("by").cloned().unwrap_or_default();
1223 let by_unique_id = attrs.get("byUniqueId").filter(|s| !s.is_empty()).cloned();
1224 let role = attrs.get("role").cloned().unwrap_or_default();
1225
1226 let when = if let Some(w) = attrs.get("when") {
1227 if w.is_empty() {
1228 None
1229 } else {
1230 Some(parse_datetime_internal(w)?)
1231 }
1232 } else {
1233 None
1234 };
1235
1236 Ok(Value {
1237 by,
1238 by_unique_id,
1239 role,
1240 when,
1241 value: String::new(),
1242 })
1243 }
1244}
1245
1246impl Reason {
1247 pub fn from_attributes(
1248 attrs: std::collections::HashMap<String, String>,
1249 ) -> Result<Self, crate::errors::Error> {
1250 let by = attrs.get("by").cloned().unwrap_or_default();
1251 let by_unique_id = attrs.get("byUniqueId").filter(|s| !s.is_empty()).cloned();
1252 let role = attrs.get("role").cloned().unwrap_or_default();
1253
1254 let when = if let Some(w) = attrs.get("when") {
1255 if w.is_empty() {
1256 None
1257 } else {
1258 Some(parse_datetime_internal(w)?)
1259 }
1260 } else {
1261 None
1262 };
1263
1264 Ok(Reason {
1265 by,
1266 by_unique_id,
1267 role,
1268 when,
1269 value: String::new(),
1270 })
1271 }
1272}