use chrono::{DateTime, Utc};
#[cfg(feature = "python")]
use chrono::{Datelike, Timelike};
use serde::{Deserialize, Deserializer};
#[cfg(feature = "python")]
use pyo3::{prelude::*, types::PyDateTime};
fn deserialize_empty_string_as_none_datetime<'de, D>(
deserializer: D,
) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
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)))
}
}
fn deserialize_empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = Option::<String>::deserialize(deserializer)?;
match s {
Some(v) if v.is_empty() => Ok(None),
Some(v) => Ok(Some(v)),
None => Ok(None),
}
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Value {
pub by: String,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub by_unique_id: Option<String>,
pub role: String,
pub when: DateTime<Utc>,
#[serde(rename = "$value")]
pub value: String,
}
#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass]
pub struct Value {
pub by: String,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub by_unique_id: Option<String>,
pub role: String,
pub when: DateTime<Utc>,
#[serde(rename = "$value")]
pub value: String,
}
#[cfg(feature = "python")]
#[pymethods]
impl Value {
#[getter]
fn by(&self) -> PyResult<String> {
Ok(self.by.clone())
}
#[getter]
fn by_unique_id(&self) -> PyResult<Option<String>> {
Ok(self.by_unique_id.clone())
}
#[getter]
fn role(&self) -> PyResult<String> {
Ok(self.role.clone())
}
#[getter]
fn when<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDateTime>> {
to_py_datetime(py, &self.when)
}
#[getter]
fn value(&self) -> PyResult<String> {
Ok(self.value.clone())
}
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Entry {
pub id: String,
pub value: Option<Value>,
}
#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass(get_all)]
pub struct Entry {
pub id: String,
pub value: Option<Value>,
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Field {
pub name: String,
#[serde(alias = "type")]
pub field_type: String,
pub data_type: String,
pub error_code: String,
pub when_created: DateTime<Utc>,
pub keep_history: bool,
#[serde(rename = "entry")]
pub entries: Option<Vec<Entry>>,
}
#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass]
pub struct Field {
pub name: String,
#[serde(alias = "type")]
pub field_type: String,
pub data_type: String,
pub error_code: String,
pub when_created: DateTime<Utc>,
pub keep_history: bool,
#[serde(rename = "entry")]
pub entries: Option<Vec<Entry>>,
}
#[cfg(feature = "python")]
#[pymethods]
impl Field {
#[getter]
fn name(&self) -> PyResult<String> {
Ok(self.name.clone())
}
#[getter]
fn field_type(&self) -> PyResult<String> {
Ok(self.field_type.clone())
}
#[getter]
fn data_type(&self) -> PyResult<String> {
Ok(self.data_type.clone())
}
#[getter]
fn error_code(&self) -> PyResult<String> {
Ok(self.error_code.clone())
}
#[getter]
fn when_created<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDateTime>> {
to_py_datetime(py, &self.when_created)
}
#[getter]
fn keep_history(&self) -> PyResult<bool> {
Ok(self.keep_history)
}
#[getter]
fn entries(&self) -> PyResult<Option<Vec<Entry>>> {
Ok(self.entries.clone())
}
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Category {
pub name: String,
#[serde(alias = "type")]
pub category_type: String,
pub highest_index: usize,
#[serde(rename = "field", default)]
pub fields: Vec<Field>,
}
#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass(get_all)]
pub struct Category {
pub name: String,
#[serde(alias = "type")]
pub category_type: String,
pub highest_index: usize,
#[serde(rename = "field", default)]
pub fields: Vec<Field>,
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct State {
pub value: String,
pub signer: String,
pub signer_unique_id: String,
#[serde(
default = "default_datetime_none",
deserialize_with = "deserialize_empty_string_as_none_datetime"
)]
pub date_signed: Option<DateTime<Utc>>,
}
#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass]
pub struct State {
pub value: String,
pub signer: String,
pub signer_unique_id: String,
#[serde(
default = "default_datetime_none",
deserialize_with = "deserialize_empty_string_as_none_datetime"
)]
pub date_signed: Option<DateTime<Utc>>,
}
#[cfg(feature = "python")]
#[pymethods]
impl State {
#[getter]
fn value(&self) -> PyResult<String> {
Ok(self.value.clone())
}
#[getter]
fn signer(&self) -> PyResult<String> {
Ok(self.signer.clone())
}
#[getter]
fn signer_unique_id(&self) -> PyResult<String> {
Ok(self.signer_unique_id.clone())
}
#[getter]
fn date_signed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
to_py_datetime_option(py, &self.date_signed)
}
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Form {
pub name: String,
#[serde(
default = "default_datetime_none",
deserialize_with = "deserialize_empty_string_as_none_datetime"
)]
pub last_modified: Option<DateTime<Utc>>,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub who_last_modified_name: Option<String>,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub who_last_modified_role: Option<String>,
pub when_created: usize,
pub has_errors: bool,
pub has_warnings: bool,
pub locked: bool,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub user: Option<String>,
#[serde(
default = "default_datetime_none",
deserialize_with = "deserialize_empty_string_as_none_datetime"
)]
pub date_time_changed: Option<DateTime<Utc>>,
pub form_title: String,
pub form_index: usize,
pub form_group: String,
pub form_state: String,
#[serde(rename = "state", default)]
pub states: Option<Vec<State>>,
#[serde(rename = "category", default)]
pub categories: Option<Vec<Category>>,
}
#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass]
pub struct Form {
pub name: String,
#[serde(
default = "default_datetime_none",
deserialize_with = "deserialize_empty_string_as_none_datetime"
)]
pub last_modified: Option<DateTime<Utc>>,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub who_last_modified_name: Option<String>,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub who_last_modified_role: Option<String>,
pub when_created: usize,
pub has_errors: bool,
pub has_warnings: bool,
pub locked: bool,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub user: Option<String>,
#[serde(
default = "default_datetime_none",
deserialize_with = "deserialize_empty_string_as_none_datetime"
)]
pub date_time_changed: Option<DateTime<Utc>>,
pub form_title: String,
pub form_index: usize,
pub form_group: String,
pub form_state: String,
#[serde(rename = "state", default)]
pub states: Option<Vec<State>>,
#[serde(rename = "category", default)]
pub categories: Option<Vec<Category>>,
}
#[cfg(feature = "python")]
#[pymethods]
impl Form {
#[getter]
fn name(&self) -> PyResult<String> {
Ok(self.name.clone())
}
#[getter]
fn last_modified<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
to_py_datetime_option(py, &self.last_modified)
}
#[getter]
fn who_last_modified_name(&self) -> PyResult<Option<String>> {
Ok(self.who_last_modified_name.clone())
}
#[getter]
fn who_last_modified_role(&self) -> PyResult<Option<String>> {
Ok(self.who_last_modified_role.clone())
}
#[getter]
fn when_created(&self) -> PyResult<usize> {
Ok(self.when_created)
}
#[getter]
fn has_errors(&self) -> PyResult<bool> {
Ok(self.has_errors)
}
#[getter]
fn has_warnings(&self) -> PyResult<bool> {
Ok(self.has_warnings)
}
#[getter]
fn locked(&self) -> PyResult<bool> {
Ok(self.locked)
}
#[getter]
fn user(&self) -> PyResult<Option<String>> {
Ok(self.user.clone())
}
#[getter]
fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
to_py_datetime_option(py, &self.date_time_changed)
}
#[getter]
fn form_title(&self) -> PyResult<String> {
Ok(self.form_title.clone())
}
#[getter]
fn form_index(&self) -> PyResult<usize> {
Ok(self.form_index)
}
#[getter]
fn form_group(&self) -> PyResult<String> {
Ok(self.form_group.clone())
}
#[getter]
fn form_state(&self) -> PyResult<String> {
Ok(self.form_state.clone())
}
#[getter]
fn state(&self) -> PyResult<Option<Vec<State>>> {
if let Some(states) = &self.states {
Ok(Some(states.clone()))
} else {
Ok(None)
}
}
#[getter]
fn category(&self) -> PyResult<Option<Vec<Category>>> {
Ok(self.categories.clone())
}
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Patient {
pub patient_id: String,
pub unique_id: String,
pub when_created: DateTime<Utc>,
pub creator: String,
pub site_name: String,
pub site_unique_id: String,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub last_language: Option<String>,
pub number_of_forms: usize,
#[serde(rename = "form")]
pub forms: Option<Vec<Form>>,
}
#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass]
pub struct Patient {
pub patient_id: String,
pub unique_id: String,
pub when_created: DateTime<Utc>,
pub creator: String,
pub site_name: String,
pub site_unique_id: String,
#[serde(
default = "default_string_none",
deserialize_with = "deserialize_empty_string_as_none"
)]
pub last_language: Option<String>,
pub number_of_forms: usize,
#[serde(rename = "form")]
pub forms: Option<Vec<Form>>,
}
#[cfg(feature = "python")]
#[pymethods]
impl Patient {
#[getter]
fn patient_id(&self) -> PyResult<String> {
Ok(self.patient_id.clone())
}
#[getter]
fn unique_id(&self) -> PyResult<String> {
Ok(self.unique_id.clone())
}
#[getter]
fn when_created<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDateTime>> {
to_py_datetime(py, &self.when_created)
}
#[getter]
fn creator(&self) -> PyResult<String> {
Ok(self.creator.clone())
}
#[getter]
fn site_name(&self) -> PyResult<String> {
Ok(self.site_name.clone())
}
#[getter]
fn site_unique_id(&self) -> PyResult<String> {
Ok(self.site_unique_id.clone())
}
#[getter]
fn last_language(&self) -> PyResult<Option<String>> {
Ok(self.last_language.clone())
}
#[getter]
fn number_of_forms(&self) -> PyResult<usize> {
Ok(self.number_of_forms)
}
#[getter]
fn forms(&self) -> PyResult<Option<Vec<Form>>> {
Ok(self.forms.clone())
}
}
#[cfg(not(feature = "python"))]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SubjectNative {
#[serde(rename = "patient", default)]
pub patients: Vec<Patient>,
}
#[cfg(feature = "python")]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[pyclass(get_all)]
pub struct SubjectNative {
#[serde(rename = "patient", default)]
pub patients: Vec<Patient>,
}
#[cfg(feature = "python")]
fn to_py_datetime<'py>(
py: Python<'py>,
date_time: &DateTime<Utc>,
) -> PyResult<Bound<'py, PyDateTime>> {
let py_datetime = PyDateTime::new_bound(
py,
date_time.year(),
date_time.month() as u8,
date_time.day() as u8,
date_time.hour() as u8,
date_time.minute() as u8,
date_time.second() as u8,
date_time.timestamp_subsec_micros(),
None,
)?;
Ok(py_datetime)
}
#[cfg(feature = "python")]
fn to_py_datetime_option<'py>(
py: Python<'py>,
date_time: &Option<DateTime<Utc>>,
) -> PyResult<Option<Bound<'py, PyDateTime>>> {
if let Some(d) = date_time {
let py_datetime = Some(PyDateTime::new_bound(
py,
d.year(),
d.month() as u8,
d.day() as u8,
d.hour() as u8,
d.minute() as u8,
d.second() as u8,
d.timestamp_subsec_micros(),
None,
)?);
Ok(py_datetime)
} else {
Ok(None)
}
}
fn default_datetime_none() -> Option<DateTime<Utc>> {
None
}
fn default_string_none() -> Option<String> {
None
}