Skip to content

Commit 825e906

Browse files
authored
Merge pull request #157 from pbs-data-solutions/lock-state
Add lock state
2 parents b953c3e + 8526535 commit 825e906

File tree

5 files changed

+173
-2
lines changed

5 files changed

+173
-2
lines changed

src/lib.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010

1111
use crate::errors::Error;
1212
use crate::native::{
13+
common::{Category, Comment, Entry, Field, LockState, Reason, State, Value},
1314
site_native::SiteNative,
1415
subject_native::{Form, Patient, SubjectNative},
1516
user_native::UserNative,
@@ -135,6 +136,7 @@ pub fn parse_site_native_file(xml_path: &Path) -> Result<SiteNative, Error> {
135136
/// form_index: 1,
136137
/// form_group: Some("Demographic".to_string()),
137138
/// form_state: "In-Work".to_string(),
139+
/// lock_state: None,
138140
/// states: Some(vec![State {
139141
/// value: "form.state.in.work".to_string(),
140142
/// signer: "Paul Sanders - Project Manager".to_string(),
@@ -355,6 +357,7 @@ pub fn parse_site_native_file(xml_path: &Path) -> Result<SiteNative, Error> {
355357
/// form_index: 1,
356358
/// form_group: Some("Demographic".to_string()),
357359
/// form_state: "In-Work".to_string(),
360+
/// lock_state: None,
358361
/// states: Some(vec![State {
359362
/// value: "form.state.in.work".to_string(),
360363
/// signer: "Paul Sanders - Project Manager".to_string(),
@@ -446,6 +449,7 @@ pub fn parse_subject_native_file(xml_path: &Path) -> Result<SubjectNative, Error
446449
/// ```
447450
/// use chrono::{DateTime, Utc};
448451
/// use prelude_xml_parser::parse_subject_native_string;
452+
/// use prelude_xml_parser::native::common::LockState;
449453
/// use prelude_xml_parser::native::subject_native::*;
450454
///
451455
/// let xml = r#"<export_from_vision_EDC date="30-May-2024 10:35 -0500" createdBy="Paul Sanders" role="Project Manager" numberSubjectsProcessed="4">
@@ -506,6 +510,7 @@ pub fn parse_subject_native_file(xml_path: &Path) -> Result<SubjectNative, Error
506510
/// form_index: 1,
507511
/// form_group: Some("Day 0".to_string()),
508512
/// form_state: "In-Work".to_string(),
513+
/// lock_state: None,
509514
/// states: Some(vec![State {
510515
/// value: "form.state.in.work".to_string(),
511516
/// signer: "Paul Sanders - Project Manager".to_string(),
@@ -575,6 +580,7 @@ pub fn parse_subject_native_file(xml_path: &Path) -> Result<SubjectNative, Error
575580
/// form_index: 1,
576581
/// form_group: Some("Day 0".to_string()),
577582
/// form_state: "In-Work".to_string(),
583+
/// lock_state: None,
578584
/// states: Some(vec![State {
579585
/// value: "form.state.in.work".to_string(),
580586
/// signer: "Paul Sanders - Project Manager".to_string(),
@@ -626,8 +632,6 @@ pub fn parse_subject_native_string(xml_str: &str) -> Result<SubjectNative, Error
626632
parse_subject_native_streaming(Cursor::new(xml_str.as_bytes()))
627633
}
628634

629-
use crate::native::common::{Category, Comment, Entry, Field, Reason, State, Value};
630-
631635
fn parse_subject_native_streaming<R: std::io::BufRead>(reader: R) -> Result<SubjectNative, Error> {
632636
let mut xml_reader = Reader::from_reader(reader);
633637
xml_reader.config_mut().trim_text(true);
@@ -837,6 +841,13 @@ fn parse_subject_native_streaming<R: std::io::BufRead>(reader: R) -> Result<Subj
837841
let state = State::from_attributes(attrs)?;
838842
current_states.push(state);
839843
}
844+
"lockState" if in_form => {
845+
let attrs = extract_attributes(e)?;
846+
let lock_state = LockState::from_attributes(attrs)?;
847+
if let Some(ref mut form) = current_form {
848+
form.lock_state = Some(lock_state);
849+
}
850+
}
840851
"value" if in_entry => {
841852
let attrs = extract_attributes(e)?;
842853
let value = Value::from_attributes(attrs)?;
@@ -964,6 +975,7 @@ pub fn parse_user_native_file(xml_path: &Path) -> Result<UserNative, Error> {
964975
/// form_index: 1,
965976
/// form_group: None,
966977
/// form_state: "In-Work".to_string(),
978+
/// lock_state: None,
967979
/// states: Some(vec![State {
968980
/// value: "form.state.in.work".to_string(),
969981
/// signer: "Paul Sanders - Project Manager".to_string(),

src/native/common.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ impl Form {
674674
form_group,
675675
form_state,
676676
states: None,
677+
lock_state: None,
677678
categories: None,
678679
})
679680
}
@@ -780,6 +781,116 @@ impl State {
780781
}
781782
}
782783

784+
#[cfg(not(feature = "python"))]
785+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
786+
pub 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]
823+
pub 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]
859+
impl 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+
783894
#[cfg(not(feature = "python"))]
784895
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
785896
pub struct Form {
@@ -876,6 +987,9 @@ pub struct Form {
876987
#[serde(alias = "state")]
877988
pub states: Option<Vec<State>>,
878989

990+
#[serde(alias = "lockState")]
991+
pub lock_state: Option<LockState>,
992+
879993
#[serde(alias = "category")]
880994
pub categories: Option<Vec<Category>>,
881995
}
@@ -977,6 +1091,9 @@ pub struct Form {
9771091
#[serde(alias = "state")]
9781092
pub states: Option<Vec<State>>,
9791093

1094+
#[serde(alias = "lockState")]
1095+
pub lock_state: Option<LockState>,
1096+
9801097
#[serde(alias = "category")]
9811098
pub categories: Option<Vec<Category>>,
9821099
}
@@ -1059,6 +1176,11 @@ impl Form {
10591176
Ok(self.states.clone())
10601177
}
10611178

1179+
#[getter]
1180+
fn lock_state(&self) -> PyResult<Option<LockState>> {
1181+
Ok(self.lock_state.clone())
1182+
}
1183+
10621184
#[getter]
10631185
fn categories(&self) -> PyResult<Option<Vec<Category>>> {
10641186
Ok(self.categories.clone())
@@ -1098,6 +1220,12 @@ impl Form {
10981220
dict.set_item("states", py.None())?;
10991221
}
11001222

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+
11011229
if let Some(categories) = &self.categories {
11021230
let mut category_dicts = Vec::new();
11031231
for category in categories {
@@ -1140,6 +1268,33 @@ impl State {
11401268
}
11411269
}
11421270

1271+
impl 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+
11431298
impl Category {
11441299
pub fn from_attributes(
11451300
attrs: std::collections::HashMap<String, String>,

src/native/snapshots/prelude_xml_parser__native__subject_native__tests__deserialize_subject_native_json.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ patients:
3131
signer: Paul Sanders - Project Manager
3232
signerUniqueId: "1681162687395"
3333
dateSigned: "2023-04-15T16:09:02Z"
34+
lock_state: ~
3435
categories:
3536
- name: Demographics
3637
categoryType: normal
@@ -80,6 +81,7 @@ patients:
8081
signer: Paul Sanders - Project Manager
8182
signerUniqueId: "1681162687395"
8283
dateSigned: "2023-04-16T16:10:02Z"
84+
lock_state: ~
8385
categories:
8486
- name: Demographics
8587
categoryType: normal

src/native/snapshots/prelude_xml_parser__native__user_native__tests__deserialize_user_native_json.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ users:
2727
signer: Paul Sanders - Project Manager
2828
signerUniqueId: "1681162687395"
2929
dateSigned: "2023-08-07T15:15:41Z"
30+
lock_state: ~
3031
categories:
3132
- name: demographics
3233
categoryType: normal

tests/assets/subject_native.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<patient patientId="ABC-001" uniqueId="1681574905819" whenCreated="2023-04-15 12:09:02 -0400" creator="Paul Sanders" siteName="Some Site" siteUniqueId="1681574834910" lastLanguage="" numberOfForms="6">
55
<form name="day.0.form.name.demographics" lastModified="2023-04-15 12:09:15 -0400" whoLastModifiedName="Paul Sanders" whoLastModifiedRole="Project Manager" whenCreated="1681574905839" hasErrors="false" hasWarnings="false" locked="false" user="" dateTimeChanged="" formTitle="Demographics" formIndex="1" formGroup="Day 0" formState="In-Work">
66
<state value="form.state.in.work" signer="Paul Sanders - Project Manager" signerUniqueId="1681162687395" dateSigned="2023-04-15 12:09:02 -0400" />
7+
<lockState locked="true" user="Some User" userUniqueId="1630429016609" dateTimeChanged="2024-10-31 09:49:15 -0500" />
78
<category name="Demographics" type="normal" highestIndex="0">
89
<field name="breed" type="combo-box" dataType="string" errorCode="valid" whenCreated="2023-04-15 12:08:26 -0400" keepHistory="true">
910
<entry id="1">

0 commit comments

Comments
 (0)