Skip to main content

helios_fhirpath/
models.rs

1//! Data models for FHIRPath server request and response handling
2//!
3//! This module defines the structures used for the FHIRPath server API,
4//! following the specification in server-api.md for the fhirpath-lab
5//! integration.
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10// Use the ParameterValueAccessor trait from helios_fhir
11use helios_fhir::ParameterValueAccessor;
12
13/// Type alias for the version-independent Parameters container.
14///
15/// This alias provides backward compatibility while using the unified
16/// VersionIndependentParameters from the helios_fhir crate.
17pub type FhirPathParameters = helios_fhir::VersionIndependentParameters;
18
19/// Individual parameter in the Parameters resource
20#[derive(Debug, Deserialize, Serialize)]
21pub struct Parameter {
22    /// Name of the parameter
23    pub name: String,
24
25    /// String value (for simple parameters)
26    #[serde(rename = "valueString", skip_serializing_if = "Option::is_none")]
27    pub value_string: Option<String>,
28
29    /// Boolean value
30    #[serde(rename = "valueBoolean", skip_serializing_if = "Option::is_none")]
31    pub value_boolean: Option<bool>,
32
33    /// Resource value
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub resource: Option<Value>,
36
37    /// Multi-part parameters (for variables)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub part: Option<Vec<ParameterPart>>,
40}
41
42/// Part of a multi-part parameter
43#[derive(Debug, Deserialize, Serialize)]
44pub struct ParameterPart {
45    /// Name of the part
46    pub name: String,
47
48    /// String value
49    #[serde(rename = "valueString", skip_serializing_if = "Option::is_none")]
50    pub value_string: Option<String>,
51
52    /// Any other value type
53    #[serde(flatten)]
54    pub value: Option<Value>,
55}
56
57/// Extracted parameters for processing
58#[derive(Debug, Default)]
59pub struct ExtractedParameters {
60    /// The context expression to execute first
61    pub context: Option<String>,
62
63    /// The FHIRPath expression to execute
64    pub expression: Option<String>,
65
66    /// Whether to validate the expression
67    pub validate: bool,
68
69    /// Variables to pass to the expression
70    pub variables: Vec<Variable>,
71
72    /// The resource to execute against
73    pub resource: Option<Value>,
74
75    /// Terminology server URL
76    pub terminology_server: Option<String>,
77}
78
79/// Variable definition
80#[derive(Debug, Clone)]
81pub struct Variable {
82    /// Variable name
83    pub name: String,
84
85    /// Variable value
86    pub value: Value,
87}
88
89/// Output result part
90#[derive(Debug, Serialize)]
91pub struct ResultPart {
92    /// Name of the part (data type or "trace")
93    pub name: String,
94
95    /// String value for context path
96    #[serde(rename = "valueString", skip_serializing_if = "Option::is_none")]
97    pub value_string: Option<String>,
98
99    /// Parts for complex results
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub part: Option<Vec<ResultValue>>,
102
103    /// Extension for non-representable values
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub extension: Option<Vec<Extension>>,
106}
107
108/// Result value within a part
109#[derive(Debug, Serialize)]
110pub struct ResultValue {
111    /// Name (data type)
112    pub name: String,
113
114    /// Various value types
115    #[serde(rename = "valueString", skip_serializing_if = "Option::is_none")]
116    pub value_string: Option<String>,
117
118    #[serde(rename = "valueBoolean", skip_serializing_if = "Option::is_none")]
119    pub value_boolean: Option<bool>,
120
121    #[serde(rename = "valueInteger", skip_serializing_if = "Option::is_none")]
122    pub value_integer: Option<i64>,
123
124    #[serde(rename = "valueDecimal", skip_serializing_if = "Option::is_none")]
125    pub value_decimal: Option<f64>,
126
127    #[serde(rename = "valueDate", skip_serializing_if = "Option::is_none")]
128    pub value_date: Option<String>,
129
130    #[serde(rename = "valueDateTime", skip_serializing_if = "Option::is_none")]
131    pub value_date_time: Option<String>,
132
133    #[serde(rename = "valueTime", skip_serializing_if = "Option::is_none")]
134    pub value_time: Option<String>,
135
136    #[serde(rename = "valueQuantity", skip_serializing_if = "Option::is_none")]
137    pub value_quantity: Option<Value>,
138
139    #[serde(rename = "valueHumanName", skip_serializing_if = "Option::is_none")]
140    pub value_human_name: Option<Value>,
141
142    /// Extension for JSON representation of complex values
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub extension: Option<Vec<Extension>>,
145}
146
147/// Extension for JSON values that can't be represented as FHIR types
148#[derive(Debug, Serialize)]
149pub struct Extension {
150    /// Extension URL
151    pub url: String,
152
153    /// String value containing JSON
154    #[serde(rename = "valueString")]
155    pub value_string: String,
156}
157
158/// Helper to create JSON value extension
159pub fn create_json_extension(value: &Value) -> Extension {
160    Extension {
161        url: "http://fhir.forms-lab.com/StructureDefinition/json-value".to_string(),
162        value_string: serde_json::to_string_pretty(value).unwrap_or_default(),
163    }
164}
165
166/// Extract parameters from the input Parameters resource
167pub fn extract_parameters(params: FhirPathParameters) -> Result<ExtractedParameters, String> {
168    let mut extracted = ExtractedParameters::default();
169
170    // Process parameters based on version
171    match params {
172        #[cfg(feature = "R4")]
173        FhirPathParameters::R4(parameters) => {
174            extract_parameters_from_r4(parameters, &mut extracted)?;
175        }
176        #[cfg(feature = "R4B")]
177        FhirPathParameters::R4B(parameters) => {
178            extract_parameters_from_r4b(parameters, &mut extracted)?;
179        }
180        #[cfg(feature = "R5")]
181        FhirPathParameters::R5(parameters) => {
182            extract_parameters_from_r5(parameters, &mut extracted)?;
183        }
184        #[cfg(feature = "R6")]
185        FhirPathParameters::R6(parameters) => {
186            extract_parameters_from_r6(parameters, &mut extracted)?;
187        }
188        #[allow(unreachable_patterns)]
189        _ => {
190            return Err(
191                "FHIR version of Parameters resource is not enabled in this build".to_string(),
192            );
193        }
194    }
195
196    if extracted.expression.is_none() {
197        return Err("Missing required parameter: expression".to_string());
198    }
199
200    if extracted.resource.is_none() {
201        return Err("Missing required parameter: resource".to_string());
202    }
203
204    Ok(extracted)
205}
206
207#[cfg(feature = "R4")]
208fn extract_parameters_from_r4(
209    parameters: helios_fhir::r4::Parameters,
210    extracted: &mut ExtractedParameters,
211) -> Result<(), String> {
212    for param in parameters.parameter.unwrap_or_default() {
213        process_parameter_r4(&param, extracted)?;
214    }
215    Ok(())
216}
217
218#[cfg(feature = "R4")]
219fn process_parameter_r4(
220    param: &helios_fhir::r4::ParametersParameter,
221    extracted: &mut ExtractedParameters,
222) -> Result<(), String> {
223    let name = param.name.value.as_deref().unwrap_or("");
224
225    match name {
226        "context" => {
227            extracted.context = param
228                .value
229                .as_ref()
230                .and_then(|v| v.as_string())
231                .map(|s| s.to_string());
232        }
233        "expression" => {
234            extracted.expression = param
235                .value
236                .as_ref()
237                .and_then(|v| v.as_string())
238                .map(|s| s.to_string());
239        }
240        "validate" => {
241            extracted.validate = param
242                .value
243                .as_ref()
244                .and_then(|v| v.as_boolean())
245                .unwrap_or(false);
246        }
247        "variables" => {
248            if let Some(parts) = &param.part {
249                for part in parts {
250                    if let Some(name) = &part.name.value {
251                        let value = if let Some(val) = &part.value {
252                            // Convert parameter value to JSON
253                            parameter_value_to_json_r4(val)
254                        } else {
255                            Value::Null
256                        };
257
258                        extracted.variables.push(Variable {
259                            name: name.to_string(),
260                            value,
261                        });
262                    }
263                }
264            }
265        }
266        "resource" => {
267            extracted.resource = param
268                .resource
269                .as_ref()
270                .and_then(|r| serde_json::to_value(r).ok());
271        }
272        "terminologyServer" => {
273            extracted.terminology_server = param
274                .value
275                .as_ref()
276                .and_then(|v| v.as_string())
277                .map(|s| s.to_string());
278        }
279        _ => {
280            // Ignore unknown parameters
281        }
282    }
283
284    Ok(())
285}
286
287#[cfg(feature = "R4")]
288fn parameter_value_to_json_r4(value: &helios_fhir::r4::ParametersParameterValue) -> Value {
289    // Convert FHIR parameter value to JSON
290    // This is a simplified conversion - in production, you'd handle all value types
291    match value {
292        helios_fhir::r4::ParametersParameterValue::String(s) => s
293            .value
294            .as_ref()
295            .map(|v| Value::String(v.clone()))
296            .unwrap_or(Value::Null),
297        helios_fhir::r4::ParametersParameterValue::Boolean(b) => {
298            b.value.map(Value::Bool).unwrap_or(Value::Null)
299        }
300        helios_fhir::r4::ParametersParameterValue::Integer(i) => i
301            .value
302            .map(|v| Value::Number(serde_json::Number::from(v)))
303            .unwrap_or(Value::Null),
304        helios_fhir::r4::ParametersParameterValue::Decimal(d) => {
305            serde_json::to_value(d).unwrap_or(Value::Null)
306        }
307        _ => {
308            // For other types, serialize to JSON
309            serde_json::to_value(value).unwrap_or(Value::Null)
310        }
311    }
312}
313
314#[cfg(feature = "R4B")]
315fn extract_parameters_from_r4b(
316    parameters: helios_fhir::r4b::Parameters,
317    extracted: &mut ExtractedParameters,
318) -> Result<(), String> {
319    for param in parameters.parameter.unwrap_or_default() {
320        let name = param.name.value.as_deref().unwrap_or("");
321
322        match name {
323            "context" => {
324                extracted.context = param
325                    .value
326                    .as_ref()
327                    .and_then(|v| v.as_string())
328                    .map(|s| s.to_string());
329            }
330            "expression" => {
331                extracted.expression = param
332                    .value
333                    .as_ref()
334                    .and_then(|v| v.as_string())
335                    .map(|s| s.to_string());
336            }
337            "validate" => {
338                extracted.validate = param
339                    .value
340                    .as_ref()
341                    .and_then(|v| v.as_boolean())
342                    .unwrap_or(false);
343            }
344            "variables" => {
345                if let Some(parts) = &param.part {
346                    for part in parts {
347                        if let Some(name) = &part.name.value {
348                            let value = if let Some(val) = &part.value {
349                                parameter_value_to_json_r4b(val)
350                            } else {
351                                Value::Null
352                            };
353
354                            extracted.variables.push(Variable {
355                                name: name.to_string(),
356                                value,
357                            });
358                        }
359                    }
360                }
361            }
362            "resource" => {
363                extracted.resource = param
364                    .resource
365                    .as_ref()
366                    .and_then(|r| serde_json::to_value(r).ok());
367            }
368            "terminologyServer" => {
369                extracted.terminology_server = param
370                    .value
371                    .as_ref()
372                    .and_then(|v| v.as_string())
373                    .map(|s| s.to_string());
374            }
375            _ => {}
376        }
377    }
378    Ok(())
379}
380
381#[cfg(feature = "R4B")]
382fn parameter_value_to_json_r4b(value: &helios_fhir::r4b::ParametersParameterValue) -> Value {
383    match value {
384        helios_fhir::r4b::ParametersParameterValue::String(s) => s
385            .value
386            .as_ref()
387            .map(|v| Value::String(v.clone()))
388            .unwrap_or(Value::Null),
389        helios_fhir::r4b::ParametersParameterValue::Boolean(b) => {
390            b.value.map(Value::Bool).unwrap_or(Value::Null)
391        }
392        helios_fhir::r4b::ParametersParameterValue::Integer(i) => i
393            .value
394            .map(|v| Value::Number(serde_json::Number::from(v)))
395            .unwrap_or(Value::Null),
396        helios_fhir::r4b::ParametersParameterValue::Decimal(d) => {
397            serde_json::to_value(d).unwrap_or(Value::Null)
398        }
399        _ => serde_json::to_value(value).unwrap_or(Value::Null),
400    }
401}
402
403#[cfg(feature = "R5")]
404fn extract_parameters_from_r5(
405    parameters: helios_fhir::r5::Parameters,
406    extracted: &mut ExtractedParameters,
407) -> Result<(), String> {
408    for param in parameters.parameter.unwrap_or_default() {
409        let name = param.name.value.as_deref().unwrap_or("");
410
411        match name {
412            "context" => {
413                extracted.context = param
414                    .value
415                    .as_ref()
416                    .and_then(|v| v.as_string())
417                    .map(|s| s.to_string());
418            }
419            "expression" => {
420                extracted.expression = param
421                    .value
422                    .as_ref()
423                    .and_then(|v| v.as_string())
424                    .map(|s| s.to_string());
425            }
426            "validate" => {
427                extracted.validate = param
428                    .value
429                    .as_ref()
430                    .and_then(|v| v.as_boolean())
431                    .unwrap_or(false);
432            }
433            "variables" => {
434                if let Some(parts) = &param.part {
435                    for part in parts {
436                        if let Some(name) = &part.name.value {
437                            let value = if let Some(val) = &part.value {
438                                parameter_value_to_json_r5(val)
439                            } else {
440                                Value::Null
441                            };
442
443                            extracted.variables.push(Variable {
444                                name: name.to_string(),
445                                value,
446                            });
447                        }
448                    }
449                }
450            }
451            "resource" => {
452                extracted.resource = param
453                    .resource
454                    .as_ref()
455                    .and_then(|r| serde_json::to_value(r).ok());
456            }
457            "terminologyServer" => {
458                extracted.terminology_server = param
459                    .value
460                    .as_ref()
461                    .and_then(|v| v.as_string())
462                    .map(|s| s.to_string());
463            }
464            _ => {}
465        }
466    }
467    Ok(())
468}
469
470#[cfg(feature = "R5")]
471fn parameter_value_to_json_r5(value: &helios_fhir::r5::ParametersParameterValue) -> Value {
472    match value {
473        helios_fhir::r5::ParametersParameterValue::String(s) => s
474            .value
475            .as_ref()
476            .map(|v| Value::String(v.clone()))
477            .unwrap_or(Value::Null),
478        helios_fhir::r5::ParametersParameterValue::Boolean(b) => {
479            b.value.map(Value::Bool).unwrap_or(Value::Null)
480        }
481        helios_fhir::r5::ParametersParameterValue::Integer(i) => i
482            .value
483            .map(|v| Value::Number(serde_json::Number::from(v)))
484            .unwrap_or(Value::Null),
485        helios_fhir::r5::ParametersParameterValue::Decimal(d) => {
486            serde_json::to_value(d).unwrap_or(Value::Null)
487        }
488        _ => serde_json::to_value(value).unwrap_or(Value::Null),
489    }
490}
491
492#[cfg(feature = "R6")]
493fn extract_parameters_from_r6(
494    parameters: helios_fhir::r6::Parameters,
495    extracted: &mut ExtractedParameters,
496) -> Result<(), String> {
497    for param in parameters.parameter.unwrap_or_default() {
498        let name = param.name.value.as_deref().unwrap_or("");
499
500        match name {
501            "context" => {
502                extracted.context = param
503                    .value
504                    .as_ref()
505                    .and_then(|v| v.as_string())
506                    .map(|s| s.to_string());
507            }
508            "expression" => {
509                extracted.expression = param
510                    .value
511                    .as_ref()
512                    .and_then(|v| v.as_string())
513                    .map(|s| s.to_string());
514            }
515            "validate" => {
516                extracted.validate = param
517                    .value
518                    .as_ref()
519                    .and_then(|v| v.as_boolean())
520                    .unwrap_or(false);
521            }
522            "variables" => {
523                if let Some(parts) = &param.part {
524                    for part in parts {
525                        if let Some(name) = &part.name.value {
526                            let value = if let Some(val) = &part.value {
527                                parameter_value_to_json_r6(val)
528                            } else {
529                                Value::Null
530                            };
531
532                            extracted.variables.push(Variable {
533                                name: name.to_string(),
534                                value,
535                            });
536                        }
537                    }
538                }
539            }
540            "resource" => {
541                extracted.resource = param
542                    .resource
543                    .as_ref()
544                    .and_then(|r| serde_json::to_value(r).ok());
545            }
546            "terminologyServer" => {
547                extracted.terminology_server = param
548                    .value
549                    .as_ref()
550                    .and_then(|v| v.as_string())
551                    .map(|s| s.to_string());
552            }
553            _ => {}
554        }
555    }
556    Ok(())
557}
558
559#[cfg(feature = "R6")]
560fn parameter_value_to_json_r6(value: &helios_fhir::r6::ParametersParameterValue) -> Value {
561    match value {
562        helios_fhir::r6::ParametersParameterValue::String(s) => s
563            .value
564            .as_ref()
565            .map(|v| Value::String(v.clone()))
566            .unwrap_or(Value::Null),
567        helios_fhir::r6::ParametersParameterValue::Boolean(b) => {
568            b.value.map(Value::Bool).unwrap_or(Value::Null)
569        }
570        helios_fhir::r6::ParametersParameterValue::Integer(i) => i
571            .value
572            .map(|v| Value::Number(serde_json::Number::from(v)))
573            .unwrap_or(Value::Null),
574        helios_fhir::r6::ParametersParameterValue::Decimal(d) => {
575            serde_json::to_value(d).unwrap_or(Value::Null)
576        }
577        _ => serde_json::to_value(value).unwrap_or(Value::Null),
578    }
579}