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