prelude_xml_parser/native/
common.rs

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}