use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::Error;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ViewDefinition {
pub resource_type: Option<String>,
pub url: Option<String>,
pub name: Option<String>,
pub status: Option<String>,
pub resource: String,
pub description: Option<String>,
#[serde(default)]
pub profile: Vec<String>,
#[serde(default)]
pub fhir_version: Vec<String>,
#[serde(default)]
pub select: Vec<SelectColumn>,
#[serde(default, rename = "where")]
pub where_: Vec<WhereClause>,
#[serde(default)]
pub constant: Vec<Constant>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SelectColumn {
pub path: Option<String>,
pub alias: Option<String>,
#[serde(default)]
pub collection: bool,
#[serde(default)]
pub select: Vec<SelectColumn>,
pub column: Option<Vec<Column>>,
pub for_each: Option<String>,
pub for_each_or_null: Option<String>,
#[serde(default)]
pub repeat: Vec<String>,
pub union_all: Option<Vec<SelectColumn>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Column {
pub name: String,
pub path: String,
#[serde(rename = "type")]
pub col_type: Option<String>,
pub collection: Option<bool>,
pub description: Option<String>,
#[serde(default)]
pub tag: Vec<Tag>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tag {
pub name: String,
pub value: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhereClause {
pub path: String,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Constant {
pub name: String,
pub value_string: Option<String>,
pub value_integer: Option<i64>,
pub value_boolean: Option<bool>,
pub value_decimal: Option<f64>,
#[serde(flatten)]
pub values: serde_json::Map<String, Value>,
}
impl ViewDefinition {
pub fn from_json(value: &Value) -> Result<Self, Error> {
serde_json::from_value(value.clone())
.map_err(|e| Error::InvalidViewDefinition(e.to_string()))
}
pub fn parse(s: &str) -> Result<Self, Error> {
serde_json::from_str(s).map_err(|e| Error::InvalidViewDefinition(e.to_string()))
}
pub fn column_names(&self) -> Vec<String> {
let mut names = Vec::new();
collect_column_names(&self.select, &mut names);
names
}
}
fn collect_column_names(selects: &[SelectColumn], names: &mut Vec<String>) {
for select in selects {
if let Some(columns) = &select.column {
for col in columns {
names.push(col.name.clone());
}
}
collect_column_names(&select.select, names);
if let Some(union_selects) = &select.union_all {
collect_column_names(union_selects, names);
}
}
}
impl Constant {
pub fn value(&self) -> Value {
if let Some(s) = &self.value_string {
Value::String(s.clone())
} else if let Some(i) = self.value_integer {
Value::Number(i.into())
} else if let Some(b) = self.value_boolean {
Value::Bool(b)
} else if let Some(d) = self.value_decimal {
serde_json::Number::from_f64(d)
.map(Value::Number)
.unwrap_or(Value::Null)
} else {
Value::Null
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_parse_simple_view_definition() {
let json = json!({
"resourceType": "ViewDefinition",
"name": "patient_demographics",
"status": "active",
"resource": "Patient",
"select": [{
"column": [{
"name": "id",
"path": "id"
}, {
"name": "gender",
"path": "gender"
}]
}]
});
let view = ViewDefinition::from_json(&json).unwrap();
assert_eq!(view.name.as_deref(), Some("patient_demographics"));
assert_eq!(view.resource, "Patient");
assert_eq!(view.select.len(), 1);
let columns = view.select[0].column.as_ref().unwrap();
assert_eq!(columns.len(), 2);
assert_eq!(columns[0].name, "id");
assert_eq!(columns[1].name, "gender");
}
#[test]
fn test_parse_view_with_foreach() {
let json = json!({
"resourceType": "ViewDefinition",
"name": "patient_names",
"status": "active",
"resource": "Patient",
"select": [{
"forEach": "name",
"column": [{
"name": "family",
"path": "family"
}, {
"name": "given",
"path": "given.first()"
}]
}]
});
let view = ViewDefinition::from_json(&json).unwrap();
assert_eq!(view.select[0].for_each, Some("name".to_string()));
}
#[test]
fn test_parse_view_with_where() {
let json = json!({
"resourceType": "ViewDefinition",
"name": "active_patients",
"status": "active",
"resource": "Patient",
"select": [{
"column": [{
"name": "id",
"path": "id"
}]
}],
"where": [{
"path": "active = true"
}]
});
let view = ViewDefinition::from_json(&json).unwrap();
assert_eq!(view.where_.len(), 1);
assert_eq!(view.where_[0].path, "active = true");
}
#[test]
fn test_parse_view_with_constants() {
let json = json!({
"resourceType": "ViewDefinition",
"name": "test_view",
"status": "active",
"resource": "Patient",
"constant": [{
"name": "statusFilter",
"valueString": "active"
}, {
"name": "maxAge",
"valueInteger": 65
}],
"select": [{
"column": [{
"name": "id",
"path": "id"
}]
}]
});
let view = ViewDefinition::from_json(&json).unwrap();
assert_eq!(view.constant.len(), 2);
assert_eq!(view.constant[0].name, "statusFilter");
assert_eq!(view.constant[0].value_string, Some("active".to_string()));
assert_eq!(view.constant[1].value_integer, Some(65));
}
#[test]
fn test_column_names() {
let json = json!({
"resourceType": "ViewDefinition",
"name": "test_view",
"status": "active",
"resource": "Patient",
"select": [{
"column": [{
"name": "id",
"path": "id"
}, {
"name": "gender",
"path": "gender"
}]
}, {
"forEach": "name",
"column": [{
"name": "family",
"path": "family"
}]
}]
});
let view = ViewDefinition::from_json(&json).unwrap();
let names = view.column_names();
assert_eq!(names, vec!["id", "gender", "family"]);
}
#[test]
fn test_constant_values() {
let string_const = Constant {
name: "s".to_string(),
value_string: Some("test".to_string()),
value_integer: None,
value_boolean: None,
value_decimal: None,
values: Default::default(),
};
assert_eq!(string_const.value(), Value::String("test".to_string()));
let int_const = Constant {
name: "i".to_string(),
value_string: None,
value_integer: Some(42),
value_boolean: None,
value_decimal: None,
values: Default::default(),
};
assert_eq!(int_const.value(), json!(42));
let bool_const = Constant {
name: "b".to_string(),
value_string: None,
value_integer: None,
value_boolean: Some(true),
value_decimal: None,
values: Default::default(),
};
assert_eq!(bool_const.value(), Value::Bool(true));
}
}