helios_sof/traits.rs
1//! # Version-Agnostic FHIR Abstraction Traits
2//!
3//! This module provides trait abstractions that enable the SOF crate to work
4//! with ViewDefinitions and Bundles across multiple FHIR versions without
5//! duplicating transformation logic. Each FHIR version implements these traits
6//! to provide uniform access to their specific data structures.
7//!
8//! ## Architecture
9//!
10//! The trait system follows a hierarchical pattern:
11//! - Top-level container traits ([`ViewDefinitionTrait`], [`BundleTrait`])
12//! - Component traits ([`ViewDefinitionSelectTrait`], [`ViewDefinitionColumnTrait`], etc.)
13//! - Version-specific implementations for R4, R4B, R5, and R6
14//!
15//! ## Design Benefits
16//!
17//! - **Version Independence**: Core processing logic works with any FHIR version
18//! - **Type Safety**: Compile-time verification of trait implementations
19//! - **Extensibility**: Easy addition of new FHIR versions or features
20//! - **Code Reuse**: Single implementation handles all supported versions
21
22use crate::SofError;
23use helios_fhir::FhirResource;
24use helios_fhirpath::EvaluationResult;
25use helios_fhirpath_support::TypeInfoResult;
26
27/// Trait for abstracting ViewDefinition across FHIR versions.
28///
29/// This trait provides version-agnostic access to ViewDefinition components,
30/// enabling the core processing logic to work uniformly across R4, R4B, R5,
31/// and R6 specifications. Each FHIR version implements this trait to expose
32/// its ViewDefinition structure through a common interface.
33///
34/// # Associated Types
35///
36/// - [`Select`](Self::Select): The select statement type for this FHIR version
37/// - [`Where`](Self::Where): The where clause type for this FHIR version
38/// - [`Constant`](Self::Constant): The constant definition type for this FHIR version
39///
40/// # Examples
41///
42/// ```rust
43/// use helios_sof::traits::ViewDefinitionTrait;
44///
45/// fn process_any_version<T: ViewDefinitionTrait>(vd: &T) {
46/// if let Some(resource_type) = vd.resource() {
47/// println!("Processing {} resources", resource_type);
48/// }
49///
50/// if let Some(selects) = vd.select() {
51/// println!("Found {} select statements", selects.len());
52/// }
53/// }
54/// ```
55pub trait ViewDefinitionTrait {
56 /// The select statement type for this FHIR version
57 type Select: ViewDefinitionSelectTrait;
58 /// The where clause type for this FHIR version
59 type Where: ViewDefinitionWhereTrait;
60 /// The constant definition type for this FHIR version
61 type Constant: ViewDefinitionConstantTrait;
62
63 /// Returns the FHIR resource type this ViewDefinition processes
64 fn resource(&self) -> Option<&str>;
65 /// Returns the select statements that define output columns and structure
66 fn select(&self) -> Option<&[Self::Select]>;
67 /// Returns the where clauses that filter resources before processing
68 fn where_clauses(&self) -> Option<&[Self::Where]>;
69 /// Returns the constants/variables available for use in expressions
70 fn constants(&self) -> Option<&[Self::Constant]>;
71}
72
73/// Trait for abstracting ViewDefinitionSelect across FHIR versions.
74///
75/// This trait provides version-agnostic access to select statement components,
76/// including columns, nested selects, iteration constructs, and union operations.
77/// Select statements define the structure and content of the output table.
78///
79/// # Associated Types
80///
81/// - [`Column`](Self::Column): The column definition type for this FHIR version
82/// - [`Select`](Self::Select): Recursive select type for nested structures
83///
84/// # Key Features
85///
86/// - **Column Definitions**: Direct column mappings from FHIRPath to output
87/// - **Nested Selects**: Hierarchical select structures for complex transformations
88/// - **Iteration**: `forEach` and `forEachOrNull` for processing collections
89/// - **Union Operations**: `unionAll` for combining multiple select results
90///
91/// # Examples
92///
93/// ```rust
94/// use helios_sof::traits::ViewDefinitionSelectTrait;
95///
96/// fn analyze_select<T: ViewDefinitionSelectTrait>(select: &T) {
97/// if let Some(columns) = select.column() {
98/// println!("Found {} columns", columns.len());
99/// }
100///
101/// if let Some(for_each) = select.for_each() {
102/// println!("Iterating over: {}", for_each);
103/// }
104///
105/// if let Some(union_selects) = select.union_all() {
106/// println!("Union with {} other selects", union_selects.len());
107/// }
108/// }
109/// ```
110pub trait ViewDefinitionSelectTrait {
111 /// The column definition type for this FHIR version
112 type Column: ViewDefinitionColumnTrait;
113 /// Recursive select type for nested structures
114 type Select: ViewDefinitionSelectTrait;
115
116 /// Returns the column definitions for this select statement
117 fn column(&self) -> Option<&[Self::Column]>;
118 /// Returns nested select statements for hierarchical processing
119 fn select(&self) -> Option<&[Self::Select]>;
120 /// Returns the FHIRPath expression for forEach iteration (filters out empty collections)
121 fn for_each(&self) -> Option<&str>;
122 /// Returns the FHIRPath expression for forEachOrNull iteration (includes null rows for empty collections)
123 fn for_each_or_null(&self) -> Option<&str>;
124 /// Returns FHIRPath expressions for recursive traversal with the repeat directive
125 fn repeat(&self) -> Option<Vec<&str>>;
126 /// Returns select statements to union with this one (all results combined)
127 fn union_all(&self) -> Option<&[Self::Select]>;
128}
129
130/// Trait for abstracting ViewDefinitionColumn across FHIR versions.
131///
132/// This trait provides version-agnostic access to column definitions,
133/// which specify how to extract data from FHIR resources and map it
134/// to output table columns. Columns are the fundamental building blocks
135/// of ViewDefinition output structure.
136///
137/// # Key Properties
138///
139/// - **Name**: The output column name in the result table
140/// - **Path**: The FHIRPath expression to extract the value
141/// - **Collection**: Whether this column contains array/collection values
142///
143/// # Examples
144///
145/// ```rust
146/// use helios_sof::traits::ViewDefinitionColumnTrait;
147///
148/// fn describe_column<T: ViewDefinitionColumnTrait>(col: &T) {
149/// if let Some(name) = col.name() {
150/// print!("Column '{}'", name);
151///
152/// if let Some(path) = col.path() {
153/// print!(" from path '{}'", path);
154/// }
155///
156/// if col.collection() == Some(true) {
157/// print!(" (collection)");
158/// }
159///
160/// println!();
161/// }
162/// }
163/// ```
164pub trait ViewDefinitionColumnTrait {
165 /// Returns the name of this column in the output table
166 fn name(&self) -> Option<&str>;
167 /// Returns the FHIRPath expression to extract the column value
168 fn path(&self) -> Option<&str>;
169 /// Returns whether this column should contain collection/array values
170 fn collection(&self) -> Option<bool>;
171}
172
173/// Trait for abstracting ViewDefinitionWhere across FHIR versions.
174///
175/// This trait provides version-agnostic access to where clause definitions,
176/// which filter resources before processing. Where clauses use FHIRPath
177/// expressions that must evaluate to boolean or boolean-coercible values.
178///
179/// # Filtering Logic
180///
181/// Resources are included in processing only if ALL where clauses evaluate to:
182/// - `true` (boolean)
183/// - Non-empty collections
184/// - Any other "truthy" value
185///
186/// Resources are excluded if ANY where clause evaluates to:
187/// - `false` (boolean)
188/// - Empty collections
189/// - Empty/null results
190///
191/// # Examples
192///
193/// ```rust
194/// use helios_sof::traits::ViewDefinitionWhereTrait;
195///
196/// fn check_where_clause<T: ViewDefinitionWhereTrait>(where_clause: &T) {
197/// if let Some(path) = where_clause.path() {
198/// println!("Filter condition: {}", path);
199///
200/// // Example paths:
201/// // "active = true" // Boolean condition
202/// // "name.exists()" // Existence check
203/// // "birthDate >= @1990-01-01" // Date comparison
204/// // "telecom.where(system='email')" // Collection filtering
205/// }
206/// }
207/// ```
208pub trait ViewDefinitionWhereTrait {
209 /// Returns the FHIRPath expression that must evaluate to true for resource inclusion
210 fn path(&self) -> Option<&str>;
211}
212
213/// Trait for abstracting ViewDefinitionConstant across FHIR versions.
214///
215/// This trait provides version-agnostic access to constant definitions,
216/// which define reusable values that can be referenced in FHIRPath expressions
217/// throughout the ViewDefinition. Constants improve maintainability and
218/// readability of complex transformations.
219///
220/// # Constant Usage
221///
222/// Constants are referenced in FHIRPath expressions using the `%` prefix:
223/// ```fhirpath
224/// // Define constant: name="baseUrl", valueString="http://example.org"
225/// // Use in path: "identifier.where(system = %baseUrl)"
226/// ```
227///
228/// # Supported Types
229///
230/// Constants can hold various FHIR primitive types:
231/// - String values
232/// - Boolean values
233/// - Integer and decimal numbers
234/// - Date, dateTime, and time values
235/// - Coded values and URIs
236///
237/// # Examples
238///
239/// ```rust
240/// use helios_sof::traits::ViewDefinitionConstantTrait;
241/// use helios_fhirpath::EvaluationResult;
242///
243/// fn process_constant<T: ViewDefinitionConstantTrait>(constant: &T) -> Result<(), Box<dyn std::error::Error>> {
244/// if let Some(name) = constant.name() {
245/// let eval_result = constant.to_evaluation_result()?;
246///
247/// match eval_result {
248/// EvaluationResult::String(s, _) => {
249/// println!("String constant '{}' = '{}'", name, s);
250/// },
251/// EvaluationResult::Integer(i, _) => {
252/// println!("Integer constant '{}' = {}", name, i);
253/// },
254/// EvaluationResult::Boolean(b, _) => {
255/// println!("Boolean constant '{}' = {}", name, b);
256/// },
257/// _ => {
258/// println!("Other constant '{}'", name);
259/// }
260/// }
261/// }
262/// Ok(())
263/// }
264/// ```
265pub trait ViewDefinitionConstantTrait {
266 /// Returns the name of this constant for use in FHIRPath expressions (referenced as %name)
267 fn name(&self) -> Option<&str>;
268 /// Converts this constant to an EvaluationResult for use in FHIRPath evaluation
269 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError>;
270}
271
272/// Trait for abstracting Bundle across FHIR versions.
273///
274/// This trait provides version-agnostic access to Bundle contents,
275/// specifically the collection of resources contained within bundle entries.
276/// Bundles serve as the input data source for ViewDefinition processing.
277///
278/// # Bundle Structure
279///
280/// FHIR Bundles contain:
281/// - Bundle metadata (type, id, etc.)
282/// - Array of bundle entries
283/// - Each entry optionally contains a resource
284///
285/// This trait focuses on extracting the resources for processing,
286/// filtering out entries that don't contain resources.
287///
288/// # Associated Types
289///
290/// - [`Resource`](Self::Resource): The resource type for this FHIR version
291///
292/// # Examples
293///
294/// ```rust
295/// use helios_sof::traits::{BundleTrait, ResourceTrait};
296///
297/// fn analyze_bundle<B: BundleTrait>(bundle: &B)
298/// where
299/// B::Resource: ResourceTrait
300/// {
301/// let resources = bundle.entries();
302/// println!("Bundle contains {} resources", resources.len());
303///
304/// for resource in resources {
305/// println!("- {} resource", resource.resource_name());
306/// }
307/// }
308/// ```
309pub trait BundleTrait {
310 /// The resource type for this FHIR version
311 type Resource: ResourceTrait;
312
313 /// Returns references to all resources contained in this bundle's entries
314 fn entries(&self) -> Vec<&Self::Resource>;
315}
316
317/// Trait for abstracting Resource across FHIR versions.
318///
319/// This trait provides version-agnostic access to FHIR resource functionality,
320/// enabling the core processing logic to work with resources from any supported
321/// FHIR version. Resources are the primary data objects processed by ViewDefinitions.
322///
323/// # Key Functionality
324///
325/// - **Type Identification**: Determine the resource type (Patient, Observation, etc.)
326/// - **Version Wrapping**: Convert to version-agnostic containers for FHIRPath evaluation
327///
328/// # Examples
329///
330/// ```rust
331/// use helios_sof::traits::ResourceTrait;
332/// use helios_fhir::FhirResource;
333///
334/// fn process_resource<R: ResourceTrait>(resource: &R) {
335/// println!("Processing {} resource", resource.resource_name());
336///
337/// // Convert to FhirResource for FHIRPath evaluation
338/// let fhir_resource = resource.to_fhir_resource();
339///
340/// // Now can be used with FHIRPath evaluation context
341/// // let context = EvaluationContext::new(vec![fhir_resource]);
342/// }
343/// ```
344pub trait ResourceTrait: Clone {
345 /// Returns the FHIR resource type name (e.g., "Patient", "Observation")
346 fn resource_name(&self) -> &str;
347 /// Converts this resource to a version-agnostic FhirResource for FHIRPath evaluation
348 fn to_fhir_resource(&self) -> FhirResource;
349 /// Returns the lastUpdated timestamp from the resource's metadata if available
350 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>>;
351}
352
353// ===== FHIR Version Implementations =====
354//
355// The following modules provide concrete implementations of the abstraction
356// traits for each supported FHIR version. Each implementation maps the
357// version-specific FHIR structures to the common trait interface.
358
359/// R4 (FHIR 4.0.1) trait implementations.
360///
361/// This module implements all abstraction traits for FHIR R4 resources,
362/// providing the mapping between R4-specific ViewDefinition structures
363/// and the version-agnostic trait interfaces.
364#[cfg(feature = "R4")]
365mod r4_impl {
366 use super::*;
367 use helios_fhir::r4::*;
368
369 impl ViewDefinitionTrait for ViewDefinition {
370 type Select = ViewDefinitionSelect;
371 type Where = ViewDefinitionWhere;
372 type Constant = ViewDefinitionConstant;
373
374 fn resource(&self) -> Option<&str> {
375 self.resource.value.as_deref()
376 }
377
378 fn select(&self) -> Option<&[Self::Select]> {
379 self.select.as_deref()
380 }
381
382 fn where_clauses(&self) -> Option<&[Self::Where]> {
383 self.r#where.as_deref()
384 }
385
386 fn constants(&self) -> Option<&[Self::Constant]> {
387 self.constant.as_deref()
388 }
389 }
390
391 impl ViewDefinitionSelectTrait for ViewDefinitionSelect {
392 type Column = ViewDefinitionSelectColumn;
393 type Select = ViewDefinitionSelect;
394
395 fn column(&self) -> Option<&[Self::Column]> {
396 self.column.as_deref()
397 }
398
399 fn select(&self) -> Option<&[Self::Select]> {
400 self.select.as_deref()
401 }
402
403 fn for_each(&self) -> Option<&str> {
404 self.for_each.as_ref()?.value.as_deref()
405 }
406
407 fn for_each_or_null(&self) -> Option<&str> {
408 self.for_each_or_null.as_ref()?.value.as_deref()
409 }
410
411 fn repeat(&self) -> Option<Vec<&str>> {
412 self.repeat
413 .as_ref()
414 .map(|paths| paths.iter().filter_map(|p| p.value.as_deref()).collect())
415 }
416
417 fn union_all(&self) -> Option<&[Self::Select]> {
418 self.union_all.as_deref()
419 }
420 }
421
422 impl ViewDefinitionColumnTrait for ViewDefinitionSelectColumn {
423 fn name(&self) -> Option<&str> {
424 self.name.value.as_deref()
425 }
426
427 fn path(&self) -> Option<&str> {
428 self.path.value.as_deref()
429 }
430
431 fn collection(&self) -> Option<bool> {
432 self.collection.as_ref()?.value
433 }
434 }
435
436 impl ViewDefinitionWhereTrait for ViewDefinitionWhere {
437 fn path(&self) -> Option<&str> {
438 self.path.value.as_deref()
439 }
440 }
441
442 impl ViewDefinitionConstantTrait for ViewDefinitionConstant {
443 fn name(&self) -> Option<&str> {
444 self.name.value.as_deref()
445 }
446
447 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError> {
448 let name = self.name().unwrap_or("unknown");
449
450 if let Some(value) = &self.value {
451 let eval_result = match value {
452 ViewDefinitionConstantValue::String(s) => {
453 EvaluationResult::String(s.value.clone().unwrap_or_default(), None)
454 }
455 ViewDefinitionConstantValue::Boolean(b) => {
456 EvaluationResult::Boolean(b.value.unwrap_or(false), None)
457 }
458 ViewDefinitionConstantValue::Integer(i) => {
459 EvaluationResult::Integer(i.value.unwrap_or(0) as i64, None)
460 }
461 ViewDefinitionConstantValue::Decimal(d) => {
462 if let Some(precise_decimal) = &d.value {
463 match precise_decimal.original_string().parse() {
464 Ok(decimal_value) => EvaluationResult::Decimal(decimal_value, None),
465 Err(_) => {
466 return Err(SofError::InvalidViewDefinition(format!(
467 "Invalid decimal value for constant '{}'",
468 name
469 )));
470 }
471 }
472 } else {
473 EvaluationResult::Decimal("0".parse().unwrap(), None)
474 }
475 }
476 ViewDefinitionConstantValue::Date(d) => EvaluationResult::Date(
477 d.value.clone().unwrap_or_default().to_string(),
478 None,
479 ),
480 ViewDefinitionConstantValue::DateTime(dt) => {
481 let value_str = dt.value.clone().unwrap_or_default().to_string();
482 // Ensure DateTime values have the "@" prefix for FHIRPath
483 let prefixed = if value_str.starts_with("@") {
484 value_str
485 } else {
486 format!("@{}", value_str)
487 };
488 EvaluationResult::DateTime(
489 prefixed,
490 Some(TypeInfoResult::new("FHIR", "dateTime")),
491 )
492 }
493 ViewDefinitionConstantValue::Time(t) => {
494 let value_str = t.value.clone().unwrap_or_default().to_string();
495 // Ensure Time values have the "@T" prefix for FHIRPath
496 let prefixed = if value_str.starts_with("@T") {
497 value_str
498 } else {
499 format!("@T{}", value_str)
500 };
501 EvaluationResult::Time(prefixed, None)
502 }
503 ViewDefinitionConstantValue::Code(c) => {
504 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
505 }
506 ViewDefinitionConstantValue::Base64Binary(b) => {
507 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
508 }
509 ViewDefinitionConstantValue::Id(i) => {
510 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
511 }
512 ViewDefinitionConstantValue::Instant(i) => {
513 let value_str = i.value.clone().unwrap_or_default().to_string();
514 // Ensure Instant values have the "@" prefix for FHIRPath
515 let prefixed = if value_str.starts_with("@") {
516 value_str
517 } else {
518 format!("@{}", value_str)
519 };
520 EvaluationResult::DateTime(
521 prefixed,
522 Some(TypeInfoResult::new("FHIR", "instant")),
523 )
524 }
525 ViewDefinitionConstantValue::Oid(o) => {
526 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
527 }
528 ViewDefinitionConstantValue::PositiveInt(p) => {
529 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
530 }
531 ViewDefinitionConstantValue::UnsignedInt(u) => {
532 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
533 }
534 ViewDefinitionConstantValue::Uri(u) => {
535 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
536 }
537 ViewDefinitionConstantValue::Url(u) => {
538 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
539 }
540 ViewDefinitionConstantValue::Uuid(u) => {
541 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
542 }
543 ViewDefinitionConstantValue::Canonical(c) => {
544 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
545 }
546 };
547
548 Ok(eval_result)
549 } else {
550 Err(SofError::InvalidViewDefinition(format!(
551 "Constant '{}' must have a value",
552 name
553 )))
554 }
555 }
556 }
557
558 impl BundleTrait for Bundle {
559 type Resource = Resource;
560
561 fn entries(&self) -> Vec<&Self::Resource> {
562 self.entry
563 .as_ref()
564 .map(|entries| entries.iter().filter_map(|e| e.resource.as_ref()).collect())
565 .unwrap_or_default()
566 }
567 }
568
569 impl ResourceTrait for Resource {
570 fn resource_name(&self) -> &str {
571 self.resource_name()
572 }
573
574 fn to_fhir_resource(&self) -> FhirResource {
575 FhirResource::R4(Box::new(self.clone()))
576 }
577
578 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
579 self.get_last_updated()
580 }
581 }
582}
583
584/// R4B (FHIR 4.3.0) trait implementations.
585///
586/// This module implements all abstraction traits for FHIR R4B resources,
587/// providing the mapping between R4B-specific ViewDefinition structures
588/// and the version-agnostic trait interfaces.
589#[cfg(feature = "R4B")]
590mod r4b_impl {
591 use super::*;
592 use helios_fhir::r4b::*;
593
594 impl ViewDefinitionTrait for ViewDefinition {
595 type Select = ViewDefinitionSelect;
596 type Where = ViewDefinitionWhere;
597 type Constant = ViewDefinitionConstant;
598
599 fn resource(&self) -> Option<&str> {
600 self.resource.value.as_deref()
601 }
602
603 fn select(&self) -> Option<&[Self::Select]> {
604 self.select.as_deref()
605 }
606
607 fn where_clauses(&self) -> Option<&[Self::Where]> {
608 self.r#where.as_deref()
609 }
610
611 fn constants(&self) -> Option<&[Self::Constant]> {
612 self.constant.as_deref()
613 }
614 }
615
616 impl ViewDefinitionSelectTrait for ViewDefinitionSelect {
617 type Column = ViewDefinitionSelectColumn;
618 type Select = ViewDefinitionSelect;
619
620 fn column(&self) -> Option<&[Self::Column]> {
621 self.column.as_deref()
622 }
623
624 fn select(&self) -> Option<&[Self::Select]> {
625 self.select.as_deref()
626 }
627
628 fn for_each(&self) -> Option<&str> {
629 self.for_each.as_ref()?.value.as_deref()
630 }
631
632 fn for_each_or_null(&self) -> Option<&str> {
633 self.for_each_or_null.as_ref()?.value.as_deref()
634 }
635
636 fn repeat(&self) -> Option<Vec<&str>> {
637 self.repeat
638 .as_ref()
639 .map(|paths| paths.iter().filter_map(|p| p.value.as_deref()).collect())
640 }
641
642 fn union_all(&self) -> Option<&[Self::Select]> {
643 self.union_all.as_deref()
644 }
645 }
646
647 impl ViewDefinitionColumnTrait for ViewDefinitionSelectColumn {
648 fn name(&self) -> Option<&str> {
649 self.name.value.as_deref()
650 }
651
652 fn path(&self) -> Option<&str> {
653 self.path.value.as_deref()
654 }
655
656 fn collection(&self) -> Option<bool> {
657 self.collection.as_ref()?.value
658 }
659 }
660
661 impl ViewDefinitionWhereTrait for ViewDefinitionWhere {
662 fn path(&self) -> Option<&str> {
663 self.path.value.as_deref()
664 }
665 }
666
667 impl ViewDefinitionConstantTrait for ViewDefinitionConstant {
668 fn name(&self) -> Option<&str> {
669 self.name.value.as_deref()
670 }
671
672 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError> {
673 let name = self.name().unwrap_or("unknown");
674
675 if let Some(value) = &self.value {
676 let eval_result = match value {
677 ViewDefinitionConstantValue::String(s) => {
678 EvaluationResult::String(s.value.clone().unwrap_or_default(), None)
679 }
680 ViewDefinitionConstantValue::Boolean(b) => {
681 EvaluationResult::Boolean(b.value.unwrap_or(false), None)
682 }
683 ViewDefinitionConstantValue::Integer(i) => {
684 EvaluationResult::Integer(i.value.unwrap_or(0) as i64, None)
685 }
686 ViewDefinitionConstantValue::Decimal(d) => {
687 if let Some(precise_decimal) = &d.value {
688 match precise_decimal.original_string().parse() {
689 Ok(decimal_value) => EvaluationResult::Decimal(decimal_value, None),
690 Err(_) => {
691 return Err(SofError::InvalidViewDefinition(format!(
692 "Invalid decimal value for constant '{}'",
693 name
694 )));
695 }
696 }
697 } else {
698 EvaluationResult::Decimal("0".parse().unwrap(), None)
699 }
700 }
701 ViewDefinitionConstantValue::Date(d) => EvaluationResult::Date(
702 d.value.clone().unwrap_or_default().to_string(),
703 None,
704 ),
705 ViewDefinitionConstantValue::DateTime(dt) => {
706 let value_str = dt.value.clone().unwrap_or_default().to_string();
707 // Ensure DateTime values have the "@" prefix for FHIRPath
708 let prefixed = if value_str.starts_with("@") {
709 value_str
710 } else {
711 format!("@{}", value_str)
712 };
713 EvaluationResult::DateTime(
714 prefixed,
715 Some(TypeInfoResult::new("FHIR", "dateTime")),
716 )
717 }
718 ViewDefinitionConstantValue::Time(t) => {
719 let value_str = t.value.clone().unwrap_or_default().to_string();
720 // Ensure Time values have the "@T" prefix for FHIRPath
721 let prefixed = if value_str.starts_with("@T") {
722 value_str
723 } else {
724 format!("@T{}", value_str)
725 };
726 EvaluationResult::Time(prefixed, None)
727 }
728 ViewDefinitionConstantValue::Code(c) => {
729 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
730 }
731 ViewDefinitionConstantValue::Base64Binary(b) => {
732 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
733 }
734 ViewDefinitionConstantValue::Id(i) => {
735 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
736 }
737 ViewDefinitionConstantValue::Instant(i) => {
738 let value_str = i.value.clone().unwrap_or_default().to_string();
739 // Ensure Instant values have the "@" prefix for FHIRPath
740 let prefixed = if value_str.starts_with("@") {
741 value_str
742 } else {
743 format!("@{}", value_str)
744 };
745 EvaluationResult::DateTime(
746 prefixed,
747 Some(TypeInfoResult::new("FHIR", "instant")),
748 )
749 }
750 ViewDefinitionConstantValue::Oid(o) => {
751 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
752 }
753 ViewDefinitionConstantValue::PositiveInt(p) => {
754 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
755 }
756 ViewDefinitionConstantValue::UnsignedInt(u) => {
757 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
758 }
759 ViewDefinitionConstantValue::Uri(u) => {
760 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
761 }
762 ViewDefinitionConstantValue::Url(u) => {
763 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
764 }
765 ViewDefinitionConstantValue::Uuid(u) => {
766 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
767 }
768 ViewDefinitionConstantValue::Canonical(c) => {
769 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
770 }
771 };
772
773 Ok(eval_result)
774 } else {
775 Err(SofError::InvalidViewDefinition(format!(
776 "Constant '{}' must have a value",
777 name
778 )))
779 }
780 }
781 }
782
783 impl BundleTrait for Bundle {
784 type Resource = Resource;
785
786 fn entries(&self) -> Vec<&Self::Resource> {
787 self.entry
788 .as_ref()
789 .map(|entries| entries.iter().filter_map(|e| e.resource.as_ref()).collect())
790 .unwrap_or_default()
791 }
792 }
793
794 impl ResourceTrait for Resource {
795 fn resource_name(&self) -> &str {
796 self.resource_name()
797 }
798
799 fn to_fhir_resource(&self) -> FhirResource {
800 FhirResource::R4B(Box::new(self.clone()))
801 }
802
803 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
804 self.get_last_updated()
805 }
806 }
807}
808
809/// R5 (FHIR 5.0.0) trait implementations.
810///
811/// This module implements all abstraction traits for FHIR R5 resources,
812/// providing the mapping between R5-specific ViewDefinition structures
813/// and the version-agnostic trait interfaces. R5 introduces the Integer64
814/// data type for constant values.
815#[cfg(feature = "R5")]
816mod r5_impl {
817 use super::*;
818 use helios_fhir::r5::*;
819
820 impl ViewDefinitionTrait for ViewDefinition {
821 type Select = ViewDefinitionSelect;
822 type Where = ViewDefinitionWhere;
823 type Constant = ViewDefinitionConstant;
824
825 fn resource(&self) -> Option<&str> {
826 self.resource.value.as_deref()
827 }
828
829 fn select(&self) -> Option<&[Self::Select]> {
830 self.select.as_deref()
831 }
832
833 fn where_clauses(&self) -> Option<&[Self::Where]> {
834 self.r#where.as_deref()
835 }
836
837 fn constants(&self) -> Option<&[Self::Constant]> {
838 self.constant.as_deref()
839 }
840 }
841
842 impl ViewDefinitionSelectTrait for ViewDefinitionSelect {
843 type Column = ViewDefinitionSelectColumn;
844 type Select = ViewDefinitionSelect;
845
846 fn column(&self) -> Option<&[Self::Column]> {
847 self.column.as_deref()
848 }
849
850 fn select(&self) -> Option<&[Self::Select]> {
851 self.select.as_deref()
852 }
853
854 fn for_each(&self) -> Option<&str> {
855 self.for_each.as_ref()?.value.as_deref()
856 }
857
858 fn for_each_or_null(&self) -> Option<&str> {
859 self.for_each_or_null.as_ref()?.value.as_deref()
860 }
861
862 fn repeat(&self) -> Option<Vec<&str>> {
863 self.repeat
864 .as_ref()
865 .map(|paths| paths.iter().filter_map(|p| p.value.as_deref()).collect())
866 }
867
868 fn union_all(&self) -> Option<&[Self::Select]> {
869 self.union_all.as_deref()
870 }
871 }
872
873 impl ViewDefinitionColumnTrait for ViewDefinitionSelectColumn {
874 fn name(&self) -> Option<&str> {
875 self.name.value.as_deref()
876 }
877
878 fn path(&self) -> Option<&str> {
879 self.path.value.as_deref()
880 }
881
882 fn collection(&self) -> Option<bool> {
883 self.collection.as_ref()?.value
884 }
885 }
886
887 impl ViewDefinitionWhereTrait for ViewDefinitionWhere {
888 fn path(&self) -> Option<&str> {
889 self.path.value.as_deref()
890 }
891 }
892
893 impl ViewDefinitionConstantTrait for ViewDefinitionConstant {
894 fn name(&self) -> Option<&str> {
895 self.name.value.as_deref()
896 }
897
898 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError> {
899 let name = self.name().unwrap_or("unknown");
900
901 if let Some(value) = &self.value {
902 // R5 implementation identical to R4
903 let eval_result = match value {
904 ViewDefinitionConstantValue::String(s) => {
905 EvaluationResult::String(s.value.clone().unwrap_or_default(), None)
906 }
907 ViewDefinitionConstantValue::Boolean(b) => {
908 EvaluationResult::Boolean(b.value.unwrap_or(false), None)
909 }
910 ViewDefinitionConstantValue::Integer(i) => {
911 EvaluationResult::Integer(i.value.unwrap_or(0) as i64, None)
912 }
913 ViewDefinitionConstantValue::Decimal(d) => {
914 if let Some(precise_decimal) = &d.value {
915 match precise_decimal.original_string().parse() {
916 Ok(decimal_value) => EvaluationResult::Decimal(decimal_value, None),
917 Err(_) => {
918 return Err(SofError::InvalidViewDefinition(format!(
919 "Invalid decimal value for constant '{}'",
920 name
921 )));
922 }
923 }
924 } else {
925 EvaluationResult::Decimal("0".parse().unwrap(), None)
926 }
927 }
928 ViewDefinitionConstantValue::Date(d) => EvaluationResult::Date(
929 d.value.clone().unwrap_or_default().to_string(),
930 None,
931 ),
932 ViewDefinitionConstantValue::DateTime(dt) => {
933 let value_str = dt.value.clone().unwrap_or_default().to_string();
934 // Ensure DateTime values have the "@" prefix for FHIRPath
935 let prefixed = if value_str.starts_with("@") {
936 value_str
937 } else {
938 format!("@{}", value_str)
939 };
940 EvaluationResult::DateTime(
941 prefixed,
942 Some(TypeInfoResult::new("FHIR", "dateTime")),
943 )
944 }
945 ViewDefinitionConstantValue::Time(t) => {
946 let value_str = t.value.clone().unwrap_or_default().to_string();
947 // Ensure Time values have the "@T" prefix for FHIRPath
948 let prefixed = if value_str.starts_with("@T") {
949 value_str
950 } else {
951 format!("@T{}", value_str)
952 };
953 EvaluationResult::Time(prefixed, None)
954 }
955 ViewDefinitionConstantValue::Code(c) => {
956 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
957 }
958 ViewDefinitionConstantValue::Base64Binary(b) => {
959 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
960 }
961 ViewDefinitionConstantValue::Id(i) => {
962 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
963 }
964 ViewDefinitionConstantValue::Instant(i) => {
965 let value_str = i.value.clone().unwrap_or_default().to_string();
966 // Ensure Instant values have the "@" prefix for FHIRPath
967 let prefixed = if value_str.starts_with("@") {
968 value_str
969 } else {
970 format!("@{}", value_str)
971 };
972 EvaluationResult::DateTime(
973 prefixed,
974 Some(TypeInfoResult::new("FHIR", "instant")),
975 )
976 }
977 ViewDefinitionConstantValue::Oid(o) => {
978 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
979 }
980 ViewDefinitionConstantValue::PositiveInt(p) => {
981 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
982 }
983 ViewDefinitionConstantValue::UnsignedInt(u) => {
984 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
985 }
986 ViewDefinitionConstantValue::Uri(u) => {
987 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
988 }
989 ViewDefinitionConstantValue::Url(u) => {
990 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
991 }
992 ViewDefinitionConstantValue::Uuid(u) => {
993 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
994 }
995 ViewDefinitionConstantValue::Canonical(c) => {
996 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
997 }
998 ViewDefinitionConstantValue::Integer64(i) => {
999 EvaluationResult::Integer64(i.value.unwrap_or(0), None)
1000 }
1001 };
1002
1003 Ok(eval_result)
1004 } else {
1005 Err(SofError::InvalidViewDefinition(format!(
1006 "Constant '{}' must have a value",
1007 name
1008 )))
1009 }
1010 }
1011 }
1012
1013 impl BundleTrait for Bundle {
1014 type Resource = Resource;
1015
1016 fn entries(&self) -> Vec<&Self::Resource> {
1017 self.entry
1018 .as_ref()
1019 .map(|entries| {
1020 entries
1021 .iter()
1022 .filter_map(|e| e.resource.as_deref()) // Note: R5 uses Box<Resource>
1023 .collect()
1024 })
1025 .unwrap_or_default()
1026 }
1027 }
1028
1029 impl ResourceTrait for Resource {
1030 fn resource_name(&self) -> &str {
1031 self.resource_name()
1032 }
1033
1034 fn to_fhir_resource(&self) -> FhirResource {
1035 FhirResource::R5(Box::new(self.clone()))
1036 }
1037
1038 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
1039 self.get_last_updated()
1040 }
1041 }
1042}
1043
1044/// R6 (FHIR 6.0.0) trait implementations.
1045///
1046/// This module implements all abstraction traits for FHIR R6 resources,
1047/// providing the mapping between R6-specific ViewDefinition structures
1048/// and the version-agnostic trait interfaces. R6 continues to support
1049/// the Integer64 data type introduced in R5.
1050#[cfg(feature = "R6")]
1051mod r6_impl {
1052 use super::*;
1053 use helios_fhir::r6::*;
1054
1055 impl ViewDefinitionTrait for ViewDefinition {
1056 type Select = ViewDefinitionSelect;
1057 type Where = ViewDefinitionWhere;
1058 type Constant = ViewDefinitionConstant;
1059
1060 fn resource(&self) -> Option<&str> {
1061 self.resource.value.as_deref()
1062 }
1063
1064 fn select(&self) -> Option<&[Self::Select]> {
1065 self.select.as_deref()
1066 }
1067
1068 fn where_clauses(&self) -> Option<&[Self::Where]> {
1069 self.r#where.as_deref()
1070 }
1071
1072 fn constants(&self) -> Option<&[Self::Constant]> {
1073 self.constant.as_deref()
1074 }
1075 }
1076
1077 impl ViewDefinitionSelectTrait for ViewDefinitionSelect {
1078 type Column = ViewDefinitionSelectColumn;
1079 type Select = ViewDefinitionSelect;
1080
1081 fn column(&self) -> Option<&[Self::Column]> {
1082 self.column.as_deref()
1083 }
1084
1085 fn select(&self) -> Option<&[Self::Select]> {
1086 self.select.as_deref()
1087 }
1088
1089 fn for_each(&self) -> Option<&str> {
1090 self.for_each.as_ref()?.value.as_deref()
1091 }
1092
1093 fn for_each_or_null(&self) -> Option<&str> {
1094 self.for_each_or_null.as_ref()?.value.as_deref()
1095 }
1096
1097 fn repeat(&self) -> Option<Vec<&str>> {
1098 self.repeat
1099 .as_ref()
1100 .map(|paths| paths.iter().filter_map(|p| p.value.as_deref()).collect())
1101 }
1102
1103 fn union_all(&self) -> Option<&[Self::Select]> {
1104 self.union_all.as_deref()
1105 }
1106 }
1107
1108 impl ViewDefinitionColumnTrait for ViewDefinitionSelectColumn {
1109 fn name(&self) -> Option<&str> {
1110 self.name.value.as_deref()
1111 }
1112
1113 fn path(&self) -> Option<&str> {
1114 self.path.value.as_deref()
1115 }
1116
1117 fn collection(&self) -> Option<bool> {
1118 self.collection.as_ref()?.value
1119 }
1120 }
1121
1122 impl ViewDefinitionWhereTrait for ViewDefinitionWhere {
1123 fn path(&self) -> Option<&str> {
1124 self.path.value.as_deref()
1125 }
1126 }
1127
1128 impl ViewDefinitionConstantTrait for ViewDefinitionConstant {
1129 fn name(&self) -> Option<&str> {
1130 self.name.value.as_deref()
1131 }
1132
1133 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError> {
1134 let name = self.name().unwrap_or("unknown");
1135
1136 if let Some(value) = &self.value {
1137 // R5 implementation identical to R4
1138 let eval_result = match value {
1139 ViewDefinitionConstantValue::String(s) => {
1140 EvaluationResult::String(s.value.clone().unwrap_or_default(), None)
1141 }
1142 ViewDefinitionConstantValue::Boolean(b) => {
1143 EvaluationResult::Boolean(b.value.unwrap_or(false), None)
1144 }
1145 ViewDefinitionConstantValue::Integer(i) => {
1146 EvaluationResult::Integer(i.value.unwrap_or(0) as i64, None)
1147 }
1148 ViewDefinitionConstantValue::Decimal(d) => {
1149 if let Some(precise_decimal) = &d.value {
1150 match precise_decimal.original_string().parse() {
1151 Ok(decimal_value) => EvaluationResult::Decimal(decimal_value, None),
1152 Err(_) => {
1153 return Err(SofError::InvalidViewDefinition(format!(
1154 "Invalid decimal value for constant '{}'",
1155 name
1156 )));
1157 }
1158 }
1159 } else {
1160 EvaluationResult::Decimal("0".parse().unwrap(), None)
1161 }
1162 }
1163 ViewDefinitionConstantValue::Date(d) => EvaluationResult::Date(
1164 d.value.clone().unwrap_or_default().to_string(),
1165 None,
1166 ),
1167 ViewDefinitionConstantValue::DateTime(dt) => {
1168 let value_str = dt.value.clone().unwrap_or_default().to_string();
1169 // Ensure DateTime values have the "@" prefix for FHIRPath
1170 let prefixed = if value_str.starts_with("@") {
1171 value_str
1172 } else {
1173 format!("@{}", value_str)
1174 };
1175 EvaluationResult::DateTime(
1176 prefixed,
1177 Some(TypeInfoResult::new("FHIR", "dateTime")),
1178 )
1179 }
1180 ViewDefinitionConstantValue::Time(t) => {
1181 let value_str = t.value.clone().unwrap_or_default().to_string();
1182 // Ensure Time values have the "@T" prefix for FHIRPath
1183 let prefixed = if value_str.starts_with("@T") {
1184 value_str
1185 } else {
1186 format!("@T{}", value_str)
1187 };
1188 EvaluationResult::Time(prefixed, None)
1189 }
1190 ViewDefinitionConstantValue::Code(c) => {
1191 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
1192 }
1193 ViewDefinitionConstantValue::Base64Binary(b) => {
1194 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
1195 }
1196 ViewDefinitionConstantValue::Id(i) => {
1197 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
1198 }
1199 ViewDefinitionConstantValue::Instant(i) => {
1200 let value_str = i.value.clone().unwrap_or_default().to_string();
1201 // Ensure Instant values have the "@" prefix for FHIRPath
1202 let prefixed = if value_str.starts_with("@") {
1203 value_str
1204 } else {
1205 format!("@{}", value_str)
1206 };
1207 EvaluationResult::DateTime(
1208 prefixed,
1209 Some(TypeInfoResult::new("FHIR", "instant")),
1210 )
1211 }
1212 ViewDefinitionConstantValue::Oid(o) => {
1213 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
1214 }
1215 ViewDefinitionConstantValue::PositiveInt(p) => {
1216 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
1217 }
1218 ViewDefinitionConstantValue::UnsignedInt(u) => {
1219 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
1220 }
1221 ViewDefinitionConstantValue::Uri(u) => {
1222 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
1223 }
1224 ViewDefinitionConstantValue::Url(u) => {
1225 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
1226 }
1227 ViewDefinitionConstantValue::Uuid(u) => {
1228 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
1229 }
1230 ViewDefinitionConstantValue::Canonical(c) => {
1231 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
1232 }
1233 ViewDefinitionConstantValue::Integer64(i) => {
1234 EvaluationResult::Integer(i.value.unwrap_or(0), None)
1235 }
1236 };
1237
1238 Ok(eval_result)
1239 } else {
1240 Err(SofError::InvalidViewDefinition(format!(
1241 "Constant '{}' must have a value",
1242 name
1243 )))
1244 }
1245 }
1246 }
1247
1248 impl BundleTrait for Bundle {
1249 type Resource = Resource;
1250
1251 fn entries(&self) -> Vec<&Self::Resource> {
1252 self.entry
1253 .as_ref()
1254 .map(|entries| {
1255 entries
1256 .iter()
1257 .filter_map(|e| e.resource.as_deref()) // Note: R6 uses Box<Resource>
1258 .collect()
1259 })
1260 .unwrap_or_default()
1261 }
1262 }
1263
1264 impl ResourceTrait for Resource {
1265 fn resource_name(&self) -> &str {
1266 self.resource_name()
1267 }
1268
1269 fn to_fhir_resource(&self) -> FhirResource {
1270 FhirResource::R6(Box::new(self.clone()))
1271 }
1272
1273 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
1274 self.get_last_updated()
1275 }
1276 }
1277}