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            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}