1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::Error;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct ViewDefinition {
18 pub resource_type: Option<String>,
23
24 pub url: Option<String>,
26
27 pub name: Option<String>,
29
30 pub status: Option<String>,
32
33 pub resource: String,
35
36 pub description: Option<String>,
38
39 #[serde(default)]
41 pub profile: Vec<String>,
42
43 #[serde(default)]
45 pub fhir_version: Vec<String>,
46
47 #[serde(default)]
49 pub select: Vec<SelectColumn>,
50
51 #[serde(default, rename = "where")]
54 pub where_: Vec<WhereClause>,
55
56 #[serde(default)]
58 pub constant: Vec<Constant>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct SelectColumn {
65 pub path: Option<String>,
67
68 pub alias: Option<String>,
70
71 #[serde(default)]
73 pub collection: bool,
74
75 #[serde(default)]
77 pub select: Vec<SelectColumn>,
78
79 pub column: Option<Vec<Column>>,
81
82 pub for_each: Option<String>,
84
85 pub for_each_or_null: Option<String>,
87
88 #[serde(default)]
90 pub repeat: Vec<String>,
91
92 pub union_all: Option<Vec<SelectColumn>>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct Column {
100 pub name: String,
102
103 pub path: String,
105
106 #[serde(rename = "type")]
108 pub col_type: Option<String>,
109
110 pub collection: Option<bool>,
112
113 pub description: Option<String>,
115
116 #[serde(default)]
118 pub tag: Vec<Tag>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct Tag {
125 pub name: String,
127
128 pub value: Option<String>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct WhereClause {
135 pub path: String,
137
138 pub description: Option<String>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct Constant {
146 pub name: String,
148
149 pub value_string: Option<String>,
151
152 pub value_integer: Option<i64>,
154
155 pub value_boolean: Option<bool>,
157
158 pub value_decimal: Option<f64>,
160
161 #[serde(flatten)]
166 pub values: serde_json::Map<String, Value>,
167}
168
169impl ViewDefinition {
170 pub fn from_json(value: &Value) -> Result<Self, Error> {
176 serde_json::from_value(value.clone())
177 .map_err(|e| Error::InvalidViewDefinition(e.to_string()))
178 }
179
180 pub fn parse(s: &str) -> Result<Self, Error> {
186 serde_json::from_str(s).map_err(|e| Error::InvalidViewDefinition(e.to_string()))
187 }
188
189 pub fn column_names(&self) -> Vec<String> {
191 let mut names = Vec::new();
192 collect_column_names(&self.select, &mut names);
193 names
194 }
195}
196
197fn collect_column_names(selects: &[SelectColumn], names: &mut Vec<String>) {
199 for select in selects {
200 if let Some(columns) = &select.column {
201 for col in columns {
202 names.push(col.name.clone());
203 }
204 }
205 collect_column_names(&select.select, names);
206
207 if let Some(union_selects) = &select.union_all {
208 collect_column_names(union_selects, names);
209 }
210 }
211}
212
213impl Constant {
214 pub fn value(&self) -> Value {
216 if let Some(s) = &self.value_string {
217 Value::String(s.clone())
218 } else if let Some(i) = self.value_integer {
219 Value::Number(i.into())
220 } else if let Some(b) = self.value_boolean {
221 Value::Bool(b)
222 } else if let Some(d) = self.value_decimal {
223 serde_json::Number::from_f64(d)
224 .map(Value::Number)
225 .unwrap_or(Value::Null)
226 } else {
227 Value::Null
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use serde_json::json;
236
237 #[test]
238 fn test_parse_simple_view_definition() {
239 let json = json!({
240 "resourceType": "ViewDefinition",
241 "name": "patient_demographics",
242 "status": "active",
243 "resource": "Patient",
244 "select": [{
245 "column": [{
246 "name": "id",
247 "path": "id"
248 }, {
249 "name": "gender",
250 "path": "gender"
251 }]
252 }]
253 });
254
255 let view = ViewDefinition::from_json(&json).unwrap();
256 assert_eq!(view.name.as_deref(), Some("patient_demographics"));
257 assert_eq!(view.resource, "Patient");
258 assert_eq!(view.select.len(), 1);
259
260 let columns = view.select[0].column.as_ref().unwrap();
261 assert_eq!(columns.len(), 2);
262 assert_eq!(columns[0].name, "id");
263 assert_eq!(columns[1].name, "gender");
264 }
265
266 #[test]
267 fn test_parse_view_with_foreach() {
268 let json = json!({
269 "resourceType": "ViewDefinition",
270 "name": "patient_names",
271 "status": "active",
272 "resource": "Patient",
273 "select": [{
274 "forEach": "name",
275 "column": [{
276 "name": "family",
277 "path": "family"
278 }, {
279 "name": "given",
280 "path": "given.first()"
281 }]
282 }]
283 });
284
285 let view = ViewDefinition::from_json(&json).unwrap();
286 assert_eq!(view.select[0].for_each, Some("name".to_string()));
287 }
288
289 #[test]
290 fn test_parse_view_with_where() {
291 let json = json!({
292 "resourceType": "ViewDefinition",
293 "name": "active_patients",
294 "status": "active",
295 "resource": "Patient",
296 "select": [{
297 "column": [{
298 "name": "id",
299 "path": "id"
300 }]
301 }],
302 "where": [{
303 "path": "active = true"
304 }]
305 });
306
307 let view = ViewDefinition::from_json(&json).unwrap();
308 assert_eq!(view.where_.len(), 1);
309 assert_eq!(view.where_[0].path, "active = true");
310 }
311
312 #[test]
313 fn test_parse_view_with_constants() {
314 let json = json!({
315 "resourceType": "ViewDefinition",
316 "name": "test_view",
317 "status": "active",
318 "resource": "Patient",
319 "constant": [{
320 "name": "statusFilter",
321 "valueString": "active"
322 }, {
323 "name": "maxAge",
324 "valueInteger": 65
325 }],
326 "select": [{
327 "column": [{
328 "name": "id",
329 "path": "id"
330 }]
331 }]
332 });
333
334 let view = ViewDefinition::from_json(&json).unwrap();
335 assert_eq!(view.constant.len(), 2);
336 assert_eq!(view.constant[0].name, "statusFilter");
337 assert_eq!(view.constant[0].value_string, Some("active".to_string()));
338 assert_eq!(view.constant[1].value_integer, Some(65));
339 }
340
341 #[test]
342 fn test_column_names() {
343 let json = json!({
344 "resourceType": "ViewDefinition",
345 "name": "test_view",
346 "status": "active",
347 "resource": "Patient",
348 "select": [{
349 "column": [{
350 "name": "id",
351 "path": "id"
352 }, {
353 "name": "gender",
354 "path": "gender"
355 }]
356 }, {
357 "forEach": "name",
358 "column": [{
359 "name": "family",
360 "path": "family"
361 }]
362 }]
363 });
364
365 let view = ViewDefinition::from_json(&json).unwrap();
366 let names = view.column_names();
367 assert_eq!(names, vec!["id", "gender", "family"]);
368 }
369
370 #[test]
371 fn test_constant_values() {
372 let string_const = Constant {
373 name: "s".to_string(),
374 value_string: Some("test".to_string()),
375 value_integer: None,
376 value_boolean: None,
377 value_decimal: None,
378 values: Default::default(),
379 };
380 assert_eq!(string_const.value(), Value::String("test".to_string()));
381
382 let int_const = Constant {
383 name: "i".to_string(),
384 value_string: None,
385 value_integer: Some(42),
386 value_boolean: None,
387 value_decimal: None,
388 values: Default::default(),
389 };
390 assert_eq!(int_const.value(), json!(42));
391
392 let bool_const = Constant {
393 name: "b".to_string(),
394 value_string: None,
395 value_integer: None,
396 value_boolean: Some(true),
397 value_decimal: None,
398 values: Default::default(),
399 };
400 assert_eq!(bool_const.value(), Value::Bool(true));
401 }
402}