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}