prelude_xml_parser/native/
user_native.rs

1use serde::{Deserialize, Serialize};
2
3#[cfg(feature = "python")]
4use pyo3::{exceptions::PyValueError, prelude::*, types::PyDict};
5
6pub use crate::native::common::{Category, Comment, Entry, Field, Form, Reason, State, Value};
7use crate::native::deserializers::{default_string_none, deserialize_empty_string_as_none};
8
9#[cfg(not(feature = "python"))]
10#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
11#[serde(rename_all = "camelCase")]
12pub struct User {
13    #[serde(rename = "uniqueId")]
14    #[serde(alias = "@uniqueId")]
15    #[serde(alias = "uniqueId")]
16    pub unique_id: String,
17
18    #[serde(rename = "lastLanguage")]
19    #[serde(alias = "@lastLanguage")]
20    #[serde(alias = "lastLanguage")]
21    #[serde(
22        default = "default_string_none",
23        deserialize_with = "deserialize_empty_string_as_none"
24    )]
25    pub last_language: Option<String>,
26    #[serde(rename = "creator")]
27    #[serde(alias = "@creator")]
28    #[serde(alias = "creator")]
29    pub creator: String,
30    #[serde(rename = "numberOfForms")]
31    #[serde(alias = "@numberOfForms")]
32    #[serde(alias = "numberOfForms")]
33    pub number_of_forms: usize,
34
35    #[serde(alias = "form")]
36    pub forms: Option<Vec<Form>>,
37}
38
39#[cfg(feature = "python")]
40#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
41#[serde(rename_all = "camelCase")]
42#[pyclass(get_all)]
43pub struct User {
44    #[serde(rename = "uniqueId")]
45    #[serde(alias = "@uniqueId")]
46    #[serde(alias = "uniqueId")]
47    pub unique_id: String,
48
49    #[serde(rename = "lastLanguage")]
50    #[serde(alias = "@lastLanguage")]
51    #[serde(alias = "lastLanguage")]
52    #[serde(
53        default = "default_string_none",
54        deserialize_with = "deserialize_empty_string_as_none"
55    )]
56    pub last_language: Option<String>,
57    #[serde(rename = "creator")]
58    #[serde(alias = "@creator")]
59    #[serde(alias = "creator")]
60    pub creator: String,
61    #[serde(rename = "numberOfForms")]
62    #[serde(alias = "@numberOfForms")]
63    #[serde(alias = "numberOfForms")]
64    pub number_of_forms: usize,
65
66    #[serde(alias = "form")]
67    pub forms: Option<Vec<Form>>,
68}
69
70#[cfg(feature = "python")]
71#[pymethods]
72impl User {
73    #[getter]
74    fn unique_id(&self) -> PyResult<String> {
75        Ok(self.unique_id.clone())
76    }
77
78    #[getter]
79    fn last_language(&self) -> PyResult<Option<String>> {
80        Ok(self.last_language.clone())
81    }
82
83    #[getter]
84    fn creator(&self) -> PyResult<String> {
85        Ok(self.creator.clone())
86    }
87
88    #[getter]
89    fn forms(&self) -> PyResult<Option<Vec<Form>>> {
90        Ok(self.forms.clone())
91    }
92
93    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
94        let dict = PyDict::new(py);
95        dict.set_item("unique_id", &self.unique_id)?;
96        dict.set_item("last_language", &self.last_language)?;
97        dict.set_item("creator", &self.creator)?;
98        dict.set_item("number_of_forms", self.number_of_forms)?;
99
100        let mut form_dicts = Vec::new();
101        if let Some(forms) = &self.forms {
102            for form in forms {
103                let form_dict = form.to_dict(py)?;
104                form_dicts.push(form_dict);
105            }
106            dict.set_item("forms", form_dicts)?;
107        } else {
108            dict.set_item("forms", py.None())?;
109        }
110
111        Ok(dict)
112    }
113}
114
115#[cfg(not(feature = "python"))]
116/// Contains the information from the Prelude native user XML.
117#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
118#[serde(rename_all = "camelCase")]
119pub struct UserNative {
120    #[serde(alias = "user")]
121    pub users: Vec<User>,
122}
123
124#[cfg(not(feature = "python"))]
125impl UserNative {
126    /// Convert to a JSON string
127    ///
128    /// # Example
129    ///
130    /// ```
131    /// use std::path::Path;
132    ///
133    /// use prelude_xml_parser::parse_user_native_file;
134    ///
135    /// let file_path = Path::new("tests/assets/user_native_small.xml");
136    /// let native = parse_user_native_file(&file_path).unwrap();
137    /// let json = native.to_json().unwrap();
138    /// // Verify it's valid JSON and contains the expected user data
139    /// assert!(json.contains("uniqueId\":\"1691421275437\""));
140    /// assert!(json.contains("\"value\":\"jazz@artemis.com\""));
141    /// ```
142    pub fn to_json(&self) -> serde_json::Result<String> {
143        let json = serde_json::to_string(&self)?;
144
145        Ok(json)
146    }
147}
148
149#[cfg(feature = "python")]
150/// Contains the information from the Prelude native user XML.
151#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
152#[serde(rename_all = "camelCase")]
153#[pyclass(get_all)]
154pub struct UserNative {
155    #[serde(alias = "user")]
156    pub users: Vec<User>,
157}
158
159#[cfg(feature = "python")]
160#[pymethods]
161impl UserNative {
162    #[getter]
163    fn users(&self) -> PyResult<Vec<User>> {
164        Ok(self.users.clone())
165    }
166
167    /// Convert the class instance to a dictionary
168    fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
169        let dict = PyDict::new(py);
170        let mut user_dicts = Vec::new();
171        for user in &self.users {
172            let user_dict = user.to_dict(py)?;
173            user_dicts.push(user_dict);
174        }
175        dict.set_item("users", user_dicts)?;
176        Ok(dict)
177    }
178
179    /// Convert the class instance to a JSON string
180    fn to_json(&self) -> PyResult<String> {
181        serde_json::to_string(&self)
182            .map_err(|_| PyErr::new::<PyValueError, _>("Error converting to JSON"))
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use insta::assert_yaml_snapshot;
190
191    #[test]
192    fn deserialize_user_native_json() {
193        let json_str = r#"{
194    "users": [
195        {
196            "uniqueId": "1691421275437",
197            "lastLanguage": null,
198            "creator": "Paul Sanders(1681162687395)",
199            "numberOfForms": 1,
200            "forms": [
201                {
202                    "name": "form.name.demographics",
203                    "lastModified": "2023-08-07T15:15:41Z",
204                    "whoLastModifiedName": "Paul Sanders",
205                    "whoLastModifiedRole": "Project Manager",
206                    "whenCreated": 1691421341578,
207                    "hasErrors": false,
208                    "hasWarnings": false,
209                    "locked": false,
210                    "user": null,
211                    "dateTimeChanged": null,
212                    "formTitle": "User Demographics",
213                    "formIndex": 1,
214                    "formGroup": null,
215                    "formState": "In-Work",
216                    "states": [
217                        {
218                            "value": "form.state.in.work",
219                            "signer": "Paul Sanders - Project Manager",
220                            "signerUniqueId": "1681162687395",
221                            "dateSigned": "2023-08-07T15:15:41Z"
222                        }
223                    ],
224                    "categories": [
225                        {
226                            "name": "demographics",
227                            "categoryType": "normal",
228                            "highestIndex": 0,
229                            "fields": [
230                                {
231                                    "name": "address",
232                                    "fieldType": "text",
233                                    "dataType": "string",
234                                    "errorCode": "undefined",
235                                    "whenCreated": "2024-01-12T20:14:09Z",
236                                    "keepHistory": true,
237                                    "entries": null
238                                },
239                                {
240                                    "name": "email",
241                                    "fieldType": "text",
242                                    "dataType": "string",
243                                    "errorCode": "undefined",
244                                    "whenCreated": "2023-08-07T15:15:41Z",
245                                    "keepHistory": true,
246                                    "entries": [
247                                        {
248                                            "entryId": "1",
249                                            "value": {
250                                                "by": "Paul Sanders",
251                                                "byUniqueId": "1681162687395",
252                                                "role": "Project Manager",
253                                                "when": "2023-08-07T15:15:41Z",
254                                                "value": "jazz@artemis.com"
255                                            },
256                                            "reason": null
257                                        }
258                                    ]
259                                }
260                            ]
261                        },
262                        {
263                            "name": "Administrative",
264                            "categoryType": "normal",
265                            "highestIndex": 0,
266                            "fields": [
267                                {
268                                    "name": "study_assignment",
269                                    "fieldType": "text",
270                                    "dataType": null,
271                                    "errorCode": "undefined",
272                                    "whenCreated": "2023-08-07T15:15:41Z",
273                                    "keepHistory": true,
274                                    "entries": [
275                                        {
276                                            "entryId": "1",
277                                            "value": {
278                                                "by": "set from calculation",
279                                                "byUniqueId": null,
280                                                "role": "System",
281                                                "when": "2023-08-07T15:15:41Z",
282                                                "value": "On 07-Aug-2023 10:15 -0500, Paul Sanders assigned user from another study"
283                                            },
284                                            "reason": {
285                                                "by": "set from calculation",
286                                                "byUniqueId": null,
287                                                "role": "System",
288                                                "when": "2023-08-07T15:15:41Z",
289                                                "value": "calculated value"
290                                            }
291                                        }
292                                    ]
293                                }
294                            ]
295                        }
296                    ]
297                }
298            ]
299        }
300    ]
301}
302
303        "#;
304
305        let result: UserNative = serde_json::from_str(json_str).unwrap();
306
307        assert_yaml_snapshot!(result);
308    }
309}