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