diff --git a/src/lib.rs b/src/lib.rs index 14f2142..4672759 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,7 +140,7 @@ pub fn parse_site_native_file(xml_path: &Path) -> Result { /// name: "Demographics".to_string(), /// category_type: "normal".to_string(), /// highest_index: 0, -/// fields: vec![ +/// fields: Some(vec![ /// Field { /// name: "address".to_string(), /// field_type: "text".to_string(), @@ -245,13 +245,13 @@ pub fn parse_site_native_file(xml_path: &Path) -> Result { /// }, /// ]), /// }, -/// ], +/// ]), /// }, /// Category { /// name: "Enrollment".to_string(), /// category_type: "normal".to_string(), /// highest_index: 0, -/// fields: vec![ +/// fields: Some(vec![ /// Field { /// name: "enrollment_closed_date".to_string(), /// field_type: "popUpCalendar".to_string(), @@ -305,7 +305,7 @@ pub fn parse_site_native_file(xml_path: &Path) -> Result { /// keep_history: true, /// entries: None, /// }, -/// ], +/// ]), /// }, /// ]), /// }]), @@ -353,7 +353,7 @@ pub fn parse_site_native_file(xml_path: &Path) -> Result { /// name: "Demographics".to_string(), /// category_type: "normal".to_string(), /// highest_index: 0, -/// fields: vec![Field { +/// fields: Some(vec![Field { /// name: "address".to_string(), /// field_type: "text".to_string(), /// data_type: Some("string".to_string()), @@ -375,7 +375,7 @@ pub fn parse_site_native_file(xml_path: &Path) -> Result { /// }), /// reason: None, /// }]), -/// }], +/// }]), /// }]), /// }]), /// }, @@ -493,7 +493,7 @@ pub fn parse_subject_native_file(xml_path: &Path) -> Result Result Result Result Result { @@ -696,7 +697,7 @@ pub fn parse_user_native_file(xml_path: &Path) -> Result { /// name: "demographics".to_string(), /// category_type: "normal".to_string(), /// highest_index: 0, -/// fields: vec![ +/// fields: Some(vec![ /// Field { /// name: "address".to_string(), /// field_type: "text".to_string(), @@ -731,13 +732,13 @@ pub fn parse_user_native_file(xml_path: &Path) -> Result { /// reason: None, /// }]), /// }, -/// ], +/// ]), /// }, /// Category { /// name: "Administrative".to_string(), /// category_type: "normal".to_string(), /// highest_index: 0, -/// fields: vec![ +/// fields: Some(vec![ /// Field { /// name: "study_assignment".to_string(), /// field_type: "text".to_string(), @@ -771,7 +772,7 @@ pub fn parse_user_native_file(xml_path: &Path) -> Result { /// }, /// ]), /// }, -/// ], +/// ]), /// }, /// ]), /// }]), @@ -779,6 +780,7 @@ pub fn parse_user_native_file(xml_path: &Path) -> Result { /// }; /// /// let result = parse_user_native_string(xml).unwrap(); +/// /// assert_eq!(result, expected); /// ``` pub fn parse_user_native_string(xml_str: &str) -> Result { diff --git a/src/native/common.rs b/src/native/common.rs index b123b37..0670b00 100644 --- a/src/native/common.rs +++ b/src/native/common.rs @@ -26,7 +26,7 @@ pub struct Value { pub role: String, pub when: DateTime, - #[serde(rename = "$value")] + #[serde(alias = "$value")] pub value: String, } @@ -45,7 +45,7 @@ pub struct Value { pub role: String, pub when: DateTime, - #[serde(rename = "$value")] + #[serde(alias = "$value")] pub value: String, } @@ -93,7 +93,7 @@ pub struct Reason { pub role: String, pub when: DateTime, - #[serde(rename = "$value")] + #[serde(alias = "$value")] pub value: String, } @@ -113,7 +113,7 @@ pub struct Reason { pub role: String, pub when: DateTime, - #[serde(rename = "$value")] + #[serde(alias = "$value")] pub value: String, } @@ -185,7 +185,7 @@ pub struct Field { pub when_created: DateTime, pub keep_history: bool, - #[serde(rename = "entry")] + #[serde(alias = "entry")] pub entries: Option>, } @@ -209,7 +209,7 @@ pub struct Field { pub when_created: DateTime, pub keep_history: bool, - #[serde(rename = "entry")] + #[serde(alias = "entry")] pub entries: Option>, } @@ -263,8 +263,8 @@ pub struct Category { pub highest_index: usize, - #[serde(rename = "field", default)] - pub fields: Vec, + #[serde(alias = "field")] + pub fields: Option>, } #[cfg(feature = "python")] @@ -279,8 +279,8 @@ pub struct Category { pub highest_index: usize, - #[serde(rename = "field", default)] - pub fields: Vec, + #[serde(alias = "field")] + pub fields: Option>, } #[cfg(not(feature = "python"))] @@ -390,10 +390,10 @@ pub struct Form { pub form_state: String, - #[serde(rename = "state", default)] + #[serde(alias = "state")] pub states: Option>, - #[serde(rename = "category", default)] + #[serde(alias = "category")] pub categories: Option>, } @@ -450,10 +450,10 @@ pub struct Form { pub form_state: String, - #[serde(rename = "state", default)] + #[serde(alias = "state")] pub states: Option>, - #[serde(rename = "category", default)] + #[serde(alias = "category")] pub categories: Option>, } diff --git a/src/native/deserializers.rs b/src/native/deserializers.rs index bd17dd7..600c6e6 100644 --- a/src/native/deserializers.rs +++ b/src/native/deserializers.rs @@ -14,14 +14,24 @@ pub fn deserialize_empty_string_as_none_datetime<'de, D>( where D: Deserializer<'de>, { - let s: String = Deserialize::deserialize(deserializer)?; - if s.is_empty() { - Ok(None) - } else { - // Parse the datetime with a fixed offset, then convert it to UTC - let dt_with_offset = DateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S %z") - .map_err(serde::de::Error::custom)?; - Ok(Some(dt_with_offset.with_timezone(&Utc))) + let s: Option = Deserialize::deserialize(deserializer)?; + match s { + Some(v) => { + if v.is_empty() { + Ok(None) + } else { + // Parse the datetime with a fixed offset, then convert it to UTC + + let dt_with_offset = if v.ends_with('Z') { + DateTime::parse_from_rfc3339(&v).map_err(serde::de::Error::custom)? + } else { + DateTime::parse_from_str(&v, "%Y-%m-%d %H:%M:%S %z") + .map_err(serde::de::Error::custom)? + }; + Ok(Some(dt_with_offset.with_timezone(&Utc))) + } + } + None => Ok(None), } } diff --git a/src/native/site_native.rs b/src/native/site_native.rs index 463548e..2a4bf20 100644 --- a/src/native/site_native.rs +++ b/src/native/site_native.rs @@ -28,7 +28,7 @@ pub struct Site { pub creator: String, pub number_of_forms: usize, - #[serde(rename = "form")] + #[serde(alias = "form")] pub forms: Option>, } @@ -98,7 +98,8 @@ impl Site { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SiteNative { - #[serde(rename = "site", default)] + #[serde(alias = "site")] + // #[serde(alias = "site")] pub sites: Vec, } @@ -108,6 +109,541 @@ pub struct SiteNative { #[serde(rename_all = "camelCase")] #[pyclass(get_all)] pub struct SiteNative { - #[serde(rename = "site", default)] + #[serde(rename = "site")] pub sites: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_site_native_json() { + let json_str = r#"{ + "sites": [ + { + "name": "Some Site", + "uniqueId": "1681574834910", + "numberOfPatients": 4, + "countOfRandomizedPatients": 0, + "whenCreated": "2023-04-15T16:08:19Z", + "creator": "Paul Sanders", + "numberOfForms": 1, + "forms": [ + { + "name": "demographic.form.name.site.demographics", + "lastModified": "2023-04-15T16:08:19Z", + "whoLastModifiedName": "Paul Sanders", + "whoLastModifiedRole": "Project Manager", + "whenCreated": 1681574834930, + "hasErrors": false, + "hasWarnings": false, + "locked": false, + "user": null, + "dateTimeChanged": null, + "formTitle": "Site Demographics", + "formIndex": 1, + "formGroup": "Demographic", + "formState": "In-Work", + "states": [ + { + "value": "form.state.in.work", + "signer": "Paul Sanders - Project Manager", + "signerUniqueId": "1681162687395", + "dateSigned": "2023-04-15T16:08:19Z" + } + ], + "categories": [ + { + "name": "Demographics", + "categoryType": "normal", + "highestIndex": 0, + "fields": [ + { + "name": "address", + "fieldType": "text", + "dataType": "string", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:07:14Z", + "keepHistory": true, + "entry": null + }, + { + "name": "company", + "fieldType": "text", + "dataType": "string", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:07:14Z", + "keepHistory": true, + "entries": [ + { + "entryId": "1", + "value": { + "by": "Paul Sanders", + "byUniqueId": "1681162687395", + "role": "Project Manager", + "when": "2023-04-15T16:08:19Z", + "$value": "Some Company" + }, + "reason": null + } + ] + }, + { + "name": "site_code_name", + "fieldType": "hidden", + "dataType": "string", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:07:14Z", + "keepHistory": true, + "entry": [ + { + "entryId": "1", + "value": { + "by": "set from calculation", + "byUniqueId": null, + "role": "System", + "when": "2023-04-15T16:08:19Z", + "$value": "ABC-Some Site" + }, + "reason": { + "by": "set from calculation", + "byUniqueId": null, + "role": "System", + "when": "2023-04-15T16:08:19Z", + "$value": "calculated value" + } + }, + { + "entryId": "2", + "value": { + "by": "set from calculation", + "byUniqueId": null, + "role": "System", + "when": "2023-04-15T16:07:24Z", + "$value": "Some Site" + }, + "reason": { + "by": "set from calculation", + "byUniqueId": null, + "role": "System", + "when": "2023-04-15T16:07:24Z", + "$value": "calculated value" + } + } + ] + } + ] + }, + { + "name": "Enrollment", + "categoryType": "normal", + "highestIndex": 0, + "field": [ + { + "name": "enrollment_closed_date", + "fieldType": "popUpCalendar", + "dataType": "date", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:07:14Z", + "keepHistory": true, + "entry": null + }, + { + "name": "enrollment_open", + "fieldType": "radio", + "dataType": "string", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:07:14Z", + "keepHistory": true, + "entry": [ + { + "entryId": "1", + "value": { + "by": "Paul Sanders", + "byUniqueId": "1681162687395", + "role": "Project Manager", + "when": "2023-04-15T16:08:19Z", + "$value": "Yes" + }, + "reason": null + } + ] + }, + { + "name": "enrollment_open_date", + "fieldType": "popUpCalendar", + "dataType": "date", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:07:14Z", + "keepHistory": true, + "entry": null + } + ] + } + ] + } + ] + }, + { + "name": "Artemis", + "uniqueId": "1691420994591", + "numberOfPatients": 0, + "countOfRandomizedPatients": 0, + "whenCreated": "2023-08-07T15:14:23Z", + "creator": "Paul Sanders", + "numberOfForms": 1, + "forms": [ + { + "name": "demographic.form.name.site.demographics", + "lastModified": "2023-08-07T15:14:23Z", + "whoLastModifiedName": "Paul Sanders", + "whoLastModifiedRole": "Project Manager", + "whenCreated": 1691420994611, + "hasErrors": false, + "hasWarnings": false, + "locked": false, + "user": null, + "dateTimeChanged": null, + "formTitle": "Site Demographics", + "formIndex": 1, + "formGroup": "Demographic", + "formState": "In-Work", + "states": [ + { + "value": "form.state.in.work", + "signer": "Paul Sanders - Project Manager", + "signerUniqueId": "1681162687395", + "dateSigned": "2023-08-07T15:14:23Z" + } + ], + "categories": [ + { + "name": "Demographics", + "categoryType": "normal", + "highestIndex": 0, + "fields": [ + { + "name": "address", + "fieldType": "text", + "dataType": "string", + "errorCode": "valid", + "whenCreated": "2023-08-07T15:09:54Z", + "keepHistory": true, + "entries": [ + { + "entryId": "1", + "value": { + "by": "Paul Sanders", + "byUniqueId": "1681162687395", + "role": "Project Manager", + "when": "2023-08-07T15:14:21Z", + "$value": "1111 Moon Drive" + }, + "reason": null + } + ] + } + ] + } + ] + } + ] + } + ] +} + "#; + + let expected = SiteNative { + sites: vec![ + Site { + name: "Some Site".to_string(), + unique_id: "1681574834910".to_string(), + number_of_patients: 4, + count_of_randomized_patients: 0, + when_created: DateTime::parse_from_rfc3339("2023-04-15T16:08:19Z") + .unwrap() + .with_timezone(&Utc), + creator: "Paul Sanders".to_string(), + number_of_forms: 1, + forms: Some(vec![Form { + name: "demographic.form.name.site.demographics".to_string(), + last_modified: Some( + DateTime::parse_from_rfc3339("2023-04-15T16:08:19Z") + .unwrap() + .with_timezone(&Utc), + ), + who_last_modified_name: Some("Paul Sanders".to_string()), + who_last_modified_role: Some("Project Manager".to_string()), + when_created: 1681574834930, + has_errors: false, + has_warnings: false, + locked: false, + user: None, + date_time_changed: None, + form_title: "Site Demographics".to_string(), + form_index: 1, + form_group: Some("Demographic".to_string()), + form_state: "In-Work".to_string(), + states: Some(vec![State { + value: "form.state.in.work".to_string(), + signer: "Paul Sanders - Project Manager".to_string(), + signer_unique_id: "1681162687395".to_string(), + date_signed: Some( + DateTime::parse_from_rfc3339("2023-04-15T16:08:19Z") + .unwrap() + .with_timezone(&Utc), + ), + }]), + categories: Some(vec![ + Category { + name: "Demographics".to_string(), + category_type: "normal".to_string(), + highest_index: 0, + fields: Some(vec![ + Field { + name: "address".to_string(), + field_type: "text".to_string(), + data_type: Some("string".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:14Z", + ) + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: None, + }, + Field { + name: "company".to_string(), + field_type: "text".to_string(), + data_type: Some("string".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:14Z", + ) + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "Paul Sanders".to_string(), + by_unique_id: Some("1681162687395".to_string()), + role: "Project Manager".to_string(), + when: DateTime::parse_from_rfc3339( + "2023-04-15T16:08:19Z", + ) + .unwrap() + .with_timezone(&Utc), + value: "Some Company".to_string(), + }), + reason: None, + }]), + }, + Field { + name: "site_code_name".to_string(), + field_type: "hidden".to_string(), + data_type: Some("string".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:14Z", + ) + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![ + Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "set from calculation".to_string(), + by_unique_id: None, + role: "System".to_string(), + when: DateTime::parse_from_rfc3339( + "2023-04-15T16:08:19Z", + ) + .unwrap() + .with_timezone(&Utc), + value: "ABC-Some Site".to_string(), + }), + reason: Some(Reason { + by: "set from calculation".to_string(), + by_unique_id: None, + role: "System".to_string(), + when: DateTime::parse_from_rfc3339( + "2023-04-15T16:08:19Z", + ) + .unwrap() + .with_timezone(&Utc), + value: "calculated value".to_string(), + }), + }, + Entry { + entry_id: "2".to_string(), + value: Some(Value { + by: "set from calculation".to_string(), + by_unique_id: None, + role: "System".to_string(), + when: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:24Z", + ) + .unwrap() + .with_timezone(&Utc), + value: "Some Site".to_string(), + }), + reason: Some(Reason { + by: "set from calculation".to_string(), + by_unique_id: None, + role: "System".to_string(), + when: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:24Z", + ) + .unwrap() + .with_timezone(&Utc), + value: "calculated value".to_string(), + }), + }, + ]), + }, + ]), + }, + Category { + name: "Enrollment".to_string(), + category_type: "normal".to_string(), + highest_index: 0, + fields: Some(vec![ + Field { + name: "enrollment_closed_date".to_string(), + field_type: "popUpCalendar".to_string(), + data_type: Some("date".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:14Z", + ) + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: None, + }, + Field { + name: "enrollment_open".to_string(), + field_type: "radio".to_string(), + data_type: Some("string".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:14Z", + ) + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "Paul Sanders".to_string(), + by_unique_id: Some("1681162687395".to_string()), + role: "Project Manager".to_string(), + when: DateTime::parse_from_rfc3339( + "2023-04-15T16:08:19Z", + ) + .unwrap() + .with_timezone(&Utc), + value: "Yes".to_string(), + }), + reason: None, + }]), + }, + Field { + name: "enrollment_open_date".to_string(), + field_type: "popUpCalendar".to_string(), + data_type: Some("date".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339( + "2023-04-15T16:07:14Z", + ) + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: None, + }, + ]), + }, + ]), + }]), + }, + Site { + name: "Artemis".to_string(), + unique_id: "1691420994591".to_string(), + number_of_patients: 0, + count_of_randomized_patients: 0, + when_created: DateTime::parse_from_rfc3339("2023-08-07T15:14:23Z") + .unwrap() + .with_timezone(&Utc), + creator: "Paul Sanders".to_string(), + number_of_forms: 1, + forms: Some(vec![Form { + name: "demographic.form.name.site.demographics".to_string(), + last_modified: Some( + DateTime::parse_from_rfc3339("2023-08-07T15:14:23Z") + .unwrap() + .with_timezone(&Utc), + ), + who_last_modified_name: Some("Paul Sanders".to_string()), + who_last_modified_role: Some("Project Manager".to_string()), + when_created: 1691420994611, + has_errors: false, + has_warnings: false, + locked: false, + user: None, + date_time_changed: None, + form_title: "Site Demographics".to_string(), + form_index: 1, + form_group: Some("Demographic".to_string()), + form_state: "In-Work".to_string(), + states: Some(vec![State { + value: "form.state.in.work".to_string(), + signer: "Paul Sanders - Project Manager".to_string(), + signer_unique_id: "1681162687395".to_string(), + date_signed: Some( + DateTime::parse_from_rfc3339("2023-08-07T15:14:23Z") + .unwrap() + .with_timezone(&Utc), + ), + }]), + categories: Some(vec![Category { + name: "Demographics".to_string(), + category_type: "normal".to_string(), + highest_index: 0, + fields: Some(vec![Field { + name: "address".to_string(), + field_type: "text".to_string(), + data_type: Some("string".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339("2023-08-07T15:09:54Z") + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "Paul Sanders".to_string(), + by_unique_id: Some("1681162687395".to_string()), + role: "Project Manager".to_string(), + when: DateTime::parse_from_rfc3339("2023-08-07T15:14:21Z") + .unwrap() + .with_timezone(&Utc), + value: "1111 Moon Drive".to_string(), + }), + reason: None, + }]), + }]), + }]), + }]), + }, + ], + }; + + let result: SiteNative = serde_json::from_str(json_str).unwrap(); + + assert_eq!(result, expected); + } +} diff --git a/src/native/subject_native.rs b/src/native/subject_native.rs index b82c94a..22bcc06 100644 --- a/src/native/subject_native.rs +++ b/src/native/subject_native.rs @@ -35,7 +35,7 @@ pub struct Patient { pub number_of_forms: usize, - #[serde(rename = "form")] + #[serde(alias = "form")] pub forms: Option>, } @@ -59,7 +59,7 @@ pub struct Patient { pub number_of_forms: usize, - #[serde(rename = "form")] + #[serde(alias = "form")] pub forms: Option>, } @@ -117,7 +117,7 @@ impl Patient { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SubjectNative { - #[serde(rename = "patient", default)] + #[serde(alias = "patient")] pub patients: Vec, } @@ -127,6 +127,301 @@ pub struct SubjectNative { #[serde(rename_all = "camelCase")] #[pyclass(get_all)] pub struct SubjectNative { - #[serde(rename = "patient", default)] + #[serde(alias = "patient")] pub patients: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_subject_native_json() { + let json_str = r#"{ + "patients": [ + { + "patientId": "ABC-001", + "uniqueId": "1681574905819", + "whenCreated": "2023-04-15T16:09:02Z", + "creator": "Paul Sanders", + "siteName": "Some Site", + "siteUniqueId": "1681574834910", + "lastLanguage": "English", + "numberOfForms": 6, + "forms": [ + { + "name": "day.0.form.name.demographics", + "lastModified": "2023-04-15T16:09:15Z", + "whoLastModifiedName": "Paul Sanders", + "whoLastModifiedRole": "Project Manager", + "whenCreated": 1681574905839, + "hasErrors": false, + "hasWarnings": false, + "locked": false, + "user": null, + "dateTimeChanged": null, + "formTitle": "Demographics", + "formIndex": 1, + "formGroup": "Day 0", + "formState": "In-Work", + "states": [ + { + "value": "form.state.in.work", + "signer": "Paul Sanders - Project Manager", + "signerUniqueId": "1681162687395", + "dateSigned": "2023-04-15T16:09:02Z" + } + ], + "categories": [ + { + "name": "Demographics", + "categoryType": "normal", + "highestIndex": 0, + "fields": [ + { + "name": "breed", + "fieldType": "combo-box", + "dataType": "string", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:08:26Z", + "keepHistory": true, + "entries": [ + { + "entryId": "1", + "value": { + "by": "Paul Sanders", + "byUniqueId": "1681162687395", + "role": "Project Manager", + "when": "2023-04-15T16:09:02Z", + "value": "Labrador" + }, + "reason": null + } + ] + } + ] + } + ] + } + ] + }, + { + "patientId": "DEF-002", + "uniqueId": "1681574905820", + "whenCreated": "2023-04-16T16:10:02Z", + "creator": "Wade Watts", + "siteName": "Another Site", + "siteUniqueId": "1681574834911", + "lastLanguage": null, + "numberOfForms": 8, + "forms": [ + { + "name": "day.0.form.name.demographics", + "lastModified": "2023-04-16T16:10:15Z", + "whoLastModifiedName": "Barney Rubble", + "whoLastModifiedRole": "Technician", + "whenCreated": 1681574905838, + "hasErrors": false, + "hasWarnings": false, + "locked": false, + "user": null, + "dateTimeChanged": null, + "formTitle": "Demographics", + "formIndex": 1, + "formGroup": "Day 0", + "formState": "In-Work", + "states": [ + { + "value": "form.state.in.work", + "signer": "Paul Sanders - Project Manager", + "signerUniqueId": "1681162687395", + "dateSigned": "2023-04-16T16:10:02Z" + } + ], + "categories": [ + { + "name": "Demographics", + "categoryType": "normal", + "highestIndex": 0, + "fields": [ + { + "name": "breed", + "fieldType": "combo-box", + "dataType": "string", + "errorCode": "valid", + "whenCreated": "2023-04-15T16:08:26Z", + "keepHistory": true, + "entries": [ + { + "entryId": "1", + "value": { + "by": "Paul Sanders", + "byUniqueId": "1681162687395", + "role": "Project Manager", + "when": "2023-04-15T16:09:02Z", + "value": "Labrador" + }, + "reason": null + } + ] + } + ] + } + ] + } + ] + } + ] +} + "#; + + let expected = SubjectNative { + patients: vec![ + Patient { + patient_id: "ABC-001".to_string(), + unique_id: "1681574905819".to_string(), + when_created: DateTime::parse_from_rfc3339("2023-04-15T16:09:02Z") + .unwrap() + .with_timezone(&Utc), + creator: "Paul Sanders".to_string(), + site_name: "Some Site".to_string(), + site_unique_id: "1681574834910".to_string(), + last_language: Some("English".to_string()), + number_of_forms: 6, + forms: Some(vec![Form { + name: "day.0.form.name.demographics".to_string(), + last_modified: Some( + DateTime::parse_from_rfc3339("2023-04-15T16:09:15Z") + .unwrap() + .with_timezone(&Utc), + ), + who_last_modified_name: Some("Paul Sanders".to_string()), + who_last_modified_role: Some("Project Manager".to_string()), + when_created: 1681574905839, + has_errors: false, + has_warnings: false, + locked: false, + user: None, + date_time_changed: None, + form_title: "Demographics".to_string(), + form_index: 1, + form_group: Some("Day 0".to_string()), + form_state: "In-Work".to_string(), + states: Some(vec![State { + value: "form.state.in.work".to_string(), + signer: "Paul Sanders - Project Manager".to_string(), + signer_unique_id: "1681162687395".to_string(), + date_signed: Some( + DateTime::parse_from_rfc3339("2023-04-15T16:09:02Z") + .unwrap() + .with_timezone(&Utc), + ), + }]), + categories: Some(vec![Category { + name: "Demographics".to_string(), + category_type: "normal".to_string(), + highest_index: 0, + fields: Some(vec![Field { + name: "breed".to_string(), + field_type: "combo-box".to_string(), + data_type: Some("string".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339("2023-04-15T16:08:26Z") + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "Paul Sanders".to_string(), + by_unique_id: Some("1681162687395".to_string()), + role: "Project Manager".to_string(), + when: DateTime::parse_from_rfc3339("2023-04-15T16:09:02Z") + .unwrap() + .with_timezone(&Utc), + value: "Labrador".to_string(), + }), + reason: None, + }]), + }]), + }]), + }]), + }, + Patient { + patient_id: "DEF-002".to_string(), + unique_id: "1681574905820".to_string(), + when_created: DateTime::parse_from_rfc3339("2023-04-16T16:10:02Z") + .unwrap() + .with_timezone(&Utc), + creator: "Wade Watts".to_string(), + site_name: "Another Site".to_string(), + site_unique_id: "1681574834911".to_string(), + last_language: None, + number_of_forms: 8, + forms: Some(vec![Form { + name: "day.0.form.name.demographics".to_string(), + last_modified: Some( + DateTime::parse_from_rfc3339("2023-04-16T16:10:15Z") + .unwrap() + .with_timezone(&Utc), + ), + who_last_modified_name: Some("Barney Rubble".to_string()), + who_last_modified_role: Some("Technician".to_string()), + when_created: 1681574905838, + has_errors: false, + has_warnings: false, + locked: false, + user: None, + date_time_changed: None, + form_title: "Demographics".to_string(), + form_index: 1, + form_group: Some("Day 0".to_string()), + form_state: "In-Work".to_string(), + states: Some(vec![State { + value: "form.state.in.work".to_string(), + signer: "Paul Sanders - Project Manager".to_string(), + signer_unique_id: "1681162687395".to_string(), + date_signed: Some( + DateTime::parse_from_rfc3339("2023-04-16T16:10:02Z") + .unwrap() + .with_timezone(&Utc), + ), + }]), + categories: Some(vec![Category { + name: "Demographics".to_string(), + category_type: "normal".to_string(), + highest_index: 0, + fields: Some(vec![Field { + name: "breed".to_string(), + field_type: "combo-box".to_string(), + data_type: Some("string".to_string()), + error_code: "valid".to_string(), + when_created: DateTime::parse_from_rfc3339("2023-04-15T16:08:26Z") + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "Paul Sanders".to_string(), + by_unique_id: Some("1681162687395".to_string()), + role: "Project Manager".to_string(), + when: DateTime::parse_from_rfc3339("2023-04-15T16:09:02Z") + .unwrap() + .with_timezone(&Utc), + value: "Labrador".to_string(), + }), + reason: None, + }]), + }]), + }]), + }]), + }, + ], + }; + + let result: SubjectNative = serde_json::from_str(json_str).unwrap(); + + assert_eq!(result, expected); + } +} diff --git a/src/native/user_native.rs b/src/native/user_native.rs index bc8f3e3..0c278aa 100644 --- a/src/native/user_native.rs +++ b/src/native/user_native.rs @@ -25,7 +25,7 @@ pub struct User { pub creator: String, pub number_of_forms: usize, - #[serde(rename = "form")] + #[serde(alias = "form")] pub forms: Option>, } @@ -44,7 +44,7 @@ pub struct User { pub creator: String, pub number_of_forms: usize, - #[serde(rename = "form")] + #[serde(alias = "form")] pub forms: Option>, } @@ -53,7 +53,7 @@ pub struct User { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UserNative { - #[serde(rename = "user", default)] + #[serde(alias = "user")] pub users: Vec, } @@ -63,6 +63,253 @@ pub struct UserNative { #[serde(rename_all = "camelCase")] #[pyclass(get_all)] pub struct UserNative { - #[serde(rename = "user", default)] + #[serde(alias = "user")] pub users: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + use chrono::{DateTime, Utc}; + + #[test] + fn deserialize_user_native_json() { + let json_str = r#"{ + "users": [ + { + "uniqueId": "1691421275437", + "lastLanguage": null, + "creator": "Paul Sanders(1681162687395)", + "numberOfForms": 1, + "forms": [ + { + "name": "form.name.demographics", + "lastModified": "2023-08-07T15:15:41Z", + "whoLastModifiedName": "Paul Sanders", + "whoLastModifiedRole": "Project Manager", + "whenCreated": 1691421341578, + "hasErrors": false, + "hasWarnings": false, + "locked": false, + "user": null, + "dateTimeChanged": null, + "formTitle": "User Demographics", + "formIndex": 1, + "formGroup": null, + "formState": "In-Work", + "states": [ + { + "value": "form.state.in.work", + "signer": "Paul Sanders - Project Manager", + "signerUniqueId": "1681162687395", + "dateSigned": "2023-08-07T15:15:41Z" + } + ], + "categories": [ + { + "name": "demographics", + "categoryType": "normal", + "highestIndex": 0, + "fields": [ + { + "name": "address", + "fieldType": "text", + "dataType": "string", + "errorCode": "undefined", + "whenCreated": "2024-01-12T20:14:09Z", + "keepHistory": true, + "entries": null + }, + { + "name": "email", + "fieldType": "text", + "dataType": "string", + "errorCode": "undefined", + "whenCreated": "2023-08-07T15:15:41Z", + "keepHistory": true, + "entries": [ + { + "entryId": "1", + "value": { + "by": "Paul Sanders", + "byUniqueId": "1681162687395", + "role": "Project Manager", + "when": "2023-08-07T15:15:41Z", + "value": "jazz@artemis.com" + }, + "reason": null + } + ] + } + ] + }, + { + "name": "Administrative", + "categoryType": "normal", + "highestIndex": 0, + "fields": [ + { + "name": "study_assignment", + "fieldType": "text", + "dataType": null, + "errorCode": "undefined", + "whenCreated": "2023-08-07T15:15:41Z", + "keepHistory": true, + "entries": [ + { + "entryId": "1", + "value": { + "by": "set from calculation", + "byUniqueId": null, + "role": "System", + "when": "2023-08-07T15:15:41Z", + "value": "On 07-Aug-2023 10:15 -0500, Paul Sanders assigned user from another study" + }, + "reason": { + "by": "set from calculation", + "byUniqueId": null, + "role": "System", + "when": "2023-08-07T15:15:41Z", + "value": "calculated value" + } + } + ] + } + ] + } + ] + } + ] + } + ] +} + + "#; + + let expected = UserNative { + users: vec![User { + unique_id: "1691421275437".to_string(), + last_language: None, + creator: "Paul Sanders(1681162687395)".to_string(), + number_of_forms: 1, + forms: Some(vec![Form { + name: "form.name.demographics".to_string(), + last_modified: Some( + DateTime::parse_from_rfc3339("2023-08-07T15:15:41Z") + .unwrap() + .with_timezone(&Utc), + ), + who_last_modified_name: Some("Paul Sanders".to_string()), + who_last_modified_role: Some("Project Manager".to_string()), + when_created: 1691421341578, + has_errors: false, + has_warnings: false, + locked: false, + user: None, + date_time_changed: None, + form_title: "User Demographics".to_string(), + form_index: 1, + form_group: None, + form_state: "In-Work".to_string(), + states: Some(vec![State { + value: "form.state.in.work".to_string(), + signer: "Paul Sanders - Project Manager".to_string(), + signer_unique_id: "1681162687395".to_string(), + date_signed: Some( + DateTime::parse_from_rfc3339("2023-08-07T15:15:41Z") + .unwrap() + .with_timezone(&Utc), + ), + }]), + categories: Some(vec![ + Category { + name: "demographics".to_string(), + category_type: "normal".to_string(), + highest_index: 0, + fields: Some(vec![ + Field { + name: "address".to_string(), + field_type: "text".to_string(), + data_type: Some("string".to_string()), + error_code: "undefined".to_string(), + when_created: DateTime::parse_from_rfc3339("2024-01-12T20:14:09Z") + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: None, + }, + Field { + name: "email".to_string(), + field_type: "text".to_string(), + data_type: Some("string".to_string()), + error_code: "undefined".to_string(), + when_created: DateTime::parse_from_rfc3339("2023-08-07T15:15:41Z") + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "Paul Sanders".to_string(), + by_unique_id: Some("1681162687395".to_string()), + role: "Project Manager".to_string(), + when: DateTime::parse_from_rfc3339("2023-08-07T15:15:41Z") + .unwrap() + .with_timezone(&Utc), + value: "jazz@artemis.com".to_string(), + }), + reason: None, + }]), + }, + ]), + }, + Category { + name: "Administrative".to_string(), + category_type: "normal".to_string(), + highest_index: 0, + fields: Some(vec![ + Field { + name: "study_assignment".to_string(), + field_type: "text".to_string(), + data_type: None, + error_code: "undefined".to_string(), + when_created: DateTime::parse_from_rfc3339("2023-08-07T15:15:41Z") + .unwrap() + .with_timezone(&Utc), + keep_history: true, + entries: Some(vec![ + Entry { + entry_id: "1".to_string(), + value: Some(Value { + by: "set from calculation".to_string(), + by_unique_id: None, + role: "System".to_string(), + when: DateTime::parse_from_rfc3339("2023-08-07T15:15:41Z") + .unwrap() + .with_timezone(&Utc), + value: "On 07-Aug-2023 10:15 -0500, Paul Sanders assigned user from another study".to_string(), + }), + reason: Some(Reason { + by: "set from calculation".to_string(), + by_unique_id: None, + role: "System".to_string(), + when: DateTime::parse_from_rfc3339("2023-08-07T15:15:41Z") + .unwrap() + .with_timezone(&Utc), + value: "calculated value".to_string(), + }), + }, + ]), + }, + ]), + }, + ]), + }]), + }], + }; + + let result: UserNative = serde_json::from_str(json_str).unwrap(); + + assert_eq!(result, expected); + } +}