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) => {
469 EvaluationResult::Date(d.value.clone().unwrap_or_default(), None)
470 }
471 ViewDefinitionConstantValue::DateTime(dt) => {
472 let value = dt.value.clone().unwrap_or_default();
473 // Ensure DateTime values have the "@" prefix for FHIRPath
474 let prefixed = if value.starts_with("@") {
475 value
476 } else {
477 format!("@{}", value)
478 };
479 EvaluationResult::DateTime(
480 prefixed,
481 Some(TypeInfoResult::new("FHIR", "dateTime")),
482 )
483 }
484 ViewDefinitionConstantValue::Time(t) => {
485 let value = t.value.clone().unwrap_or_default();
486 // Ensure Time values have the "@T" prefix for FHIRPath
487 let prefixed = if value.starts_with("@T") {
488 value
489 } else {
490 format!("@T{}", value)
491 };
492 EvaluationResult::Time(prefixed, None)
493 }
494 ViewDefinitionConstantValue::Code(c) => {
495 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
496 }
497 ViewDefinitionConstantValue::Base64Binary(b) => {
498 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
499 }
500 ViewDefinitionConstantValue::Id(i) => {
501 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
502 }
503 ViewDefinitionConstantValue::Instant(i) => {
504 let value = i.value.clone().unwrap_or_default();
505 // Ensure Instant values have the "@" prefix for FHIRPath
506 let prefixed = if value.starts_with("@") {
507 value
508 } else {
509 format!("@{}", value)
510 };
511 EvaluationResult::DateTime(
512 prefixed,
513 Some(TypeInfoResult::new("FHIR", "instant")),
514 )
515 }
516 ViewDefinitionConstantValue::Oid(o) => {
517 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
518 }
519 ViewDefinitionConstantValue::PositiveInt(p) => {
520 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
521 }
522 ViewDefinitionConstantValue::UnsignedInt(u) => {
523 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
524 }
525 ViewDefinitionConstantValue::Uri(u) => {
526 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
527 }
528 ViewDefinitionConstantValue::Url(u) => {
529 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
530 }
531 ViewDefinitionConstantValue::Uuid(u) => {
532 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
533 }
534 ViewDefinitionConstantValue::Canonical(c) => {
535 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
536 }
537 };
538
539 Ok(eval_result)
540 } else {
541 Err(SofError::InvalidViewDefinition(format!(
542 "Constant '{}' must have a value",
543 name
544 )))
545 }
546 }
547 }
548
549 impl BundleTrait for Bundle {
550 type Resource = Resource;
551
552 fn entries(&self) -> Vec<&Self::Resource> {
553 self.entry
554 .as_ref()
555 .map(|entries| entries.iter().filter_map(|e| e.resource.as_ref()).collect())
556 .unwrap_or_default()
557 }
558 }
559
560 impl ResourceTrait for Resource {
561 fn resource_name(&self) -> &str {
562 self.resource_name()
563 }
564
565 fn to_fhir_resource(&self) -> FhirResource {
566 FhirResource::R4(Box::new(self.clone()))
567 }
568
569 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
570 self.get_last_updated()
571 }
572 }
573}
574
575/// R4B (FHIR 4.3.0) trait implementations.
576///
577/// This module implements all abstraction traits for FHIR R4B resources,
578/// providing the mapping between R4B-specific ViewDefinition structures
579/// and the version-agnostic trait interfaces.
580#[cfg(feature = "R4B")]
581mod r4b_impl {
582 use super::*;
583 use helios_fhir::r4b::*;
584
585 impl ViewDefinitionTrait for ViewDefinition {
586 type Select = ViewDefinitionSelect;
587 type Where = ViewDefinitionWhere;
588 type Constant = ViewDefinitionConstant;
589
590 fn resource(&self) -> Option<&str> {
591 self.resource.value.as_deref()
592 }
593
594 fn select(&self) -> Option<&[Self::Select]> {
595 self.select.as_deref()
596 }
597
598 fn where_clauses(&self) -> Option<&[Self::Where]> {
599 self.r#where.as_deref()
600 }
601
602 fn constants(&self) -> Option<&[Self::Constant]> {
603 self.constant.as_deref()
604 }
605 }
606
607 impl ViewDefinitionSelectTrait for ViewDefinitionSelect {
608 type Column = ViewDefinitionSelectColumn;
609 type Select = ViewDefinitionSelect;
610
611 fn column(&self) -> Option<&[Self::Column]> {
612 self.column.as_deref()
613 }
614
615 fn select(&self) -> Option<&[Self::Select]> {
616 self.select.as_deref()
617 }
618
619 fn for_each(&self) -> Option<&str> {
620 self.for_each.as_ref()?.value.as_deref()
621 }
622
623 fn for_each_or_null(&self) -> Option<&str> {
624 self.for_each_or_null.as_ref()?.value.as_deref()
625 }
626
627 fn union_all(&self) -> Option<&[Self::Select]> {
628 self.union_all.as_deref()
629 }
630 }
631
632 impl ViewDefinitionColumnTrait for ViewDefinitionSelectColumn {
633 fn name(&self) -> Option<&str> {
634 self.name.value.as_deref()
635 }
636
637 fn path(&self) -> Option<&str> {
638 self.path.value.as_deref()
639 }
640
641 fn collection(&self) -> Option<bool> {
642 self.collection.as_ref()?.value
643 }
644 }
645
646 impl ViewDefinitionWhereTrait for ViewDefinitionWhere {
647 fn path(&self) -> Option<&str> {
648 self.path.value.as_deref()
649 }
650 }
651
652 impl ViewDefinitionConstantTrait for ViewDefinitionConstant {
653 fn name(&self) -> Option<&str> {
654 self.name.value.as_deref()
655 }
656
657 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError> {
658 let name = self.name().unwrap_or("unknown");
659
660 if let Some(value) = &self.value {
661 let eval_result = match value {
662 ViewDefinitionConstantValue::String(s) => {
663 EvaluationResult::String(s.value.clone().unwrap_or_default(), None)
664 }
665 ViewDefinitionConstantValue::Boolean(b) => {
666 EvaluationResult::Boolean(b.value.unwrap_or(false), None)
667 }
668 ViewDefinitionConstantValue::Integer(i) => {
669 EvaluationResult::Integer(i.value.unwrap_or(0) as i64, None)
670 }
671 ViewDefinitionConstantValue::Decimal(d) => {
672 if let Some(precise_decimal) = &d.value {
673 match precise_decimal.original_string().parse() {
674 Ok(decimal_value) => EvaluationResult::Decimal(decimal_value, None),
675 Err(_) => {
676 return Err(SofError::InvalidViewDefinition(format!(
677 "Invalid decimal value for constant '{}'",
678 name
679 )));
680 }
681 }
682 } else {
683 EvaluationResult::Decimal("0".parse().unwrap(), None)
684 }
685 }
686 ViewDefinitionConstantValue::Date(d) => {
687 EvaluationResult::Date(d.value.clone().unwrap_or_default(), None)
688 }
689 ViewDefinitionConstantValue::DateTime(dt) => {
690 let value = dt.value.clone().unwrap_or_default();
691 // Ensure DateTime values have the "@" prefix for FHIRPath
692 let prefixed = if value.starts_with("@") {
693 value
694 } else {
695 format!("@{}", value)
696 };
697 EvaluationResult::DateTime(
698 prefixed,
699 Some(TypeInfoResult::new("FHIR", "dateTime")),
700 )
701 }
702 ViewDefinitionConstantValue::Time(t) => {
703 let value = t.value.clone().unwrap_or_default();
704 // Ensure Time values have the "@T" prefix for FHIRPath
705 let prefixed = if value.starts_with("@T") {
706 value
707 } else {
708 format!("@T{}", value)
709 };
710 EvaluationResult::Time(prefixed, None)
711 }
712 ViewDefinitionConstantValue::Code(c) => {
713 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
714 }
715 ViewDefinitionConstantValue::Base64Binary(b) => {
716 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
717 }
718 ViewDefinitionConstantValue::Id(i) => {
719 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
720 }
721 ViewDefinitionConstantValue::Instant(i) => {
722 let value = i.value.clone().unwrap_or_default();
723 // Ensure Instant values have the "@" prefix for FHIRPath
724 let prefixed = if value.starts_with("@") {
725 value
726 } else {
727 format!("@{}", value)
728 };
729 EvaluationResult::DateTime(
730 prefixed,
731 Some(TypeInfoResult::new("FHIR", "instant")),
732 )
733 }
734 ViewDefinitionConstantValue::Oid(o) => {
735 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
736 }
737 ViewDefinitionConstantValue::PositiveInt(p) => {
738 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
739 }
740 ViewDefinitionConstantValue::UnsignedInt(u) => {
741 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
742 }
743 ViewDefinitionConstantValue::Uri(u) => {
744 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
745 }
746 ViewDefinitionConstantValue::Url(u) => {
747 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
748 }
749 ViewDefinitionConstantValue::Uuid(u) => {
750 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
751 }
752 ViewDefinitionConstantValue::Canonical(c) => {
753 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
754 }
755 };
756
757 Ok(eval_result)
758 } else {
759 Err(SofError::InvalidViewDefinition(format!(
760 "Constant '{}' must have a value",
761 name
762 )))
763 }
764 }
765 }
766
767 impl BundleTrait for Bundle {
768 type Resource = Resource;
769
770 fn entries(&self) -> Vec<&Self::Resource> {
771 self.entry
772 .as_ref()
773 .map(|entries| entries.iter().filter_map(|e| e.resource.as_ref()).collect())
774 .unwrap_or_default()
775 }
776 }
777
778 impl ResourceTrait for Resource {
779 fn resource_name(&self) -> &str {
780 self.resource_name()
781 }
782
783 fn to_fhir_resource(&self) -> FhirResource {
784 FhirResource::R4B(Box::new(self.clone()))
785 }
786
787 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
788 self.get_last_updated()
789 }
790 }
791}
792
793/// R5 (FHIR 5.0.0) trait implementations.
794///
795/// This module implements all abstraction traits for FHIR R5 resources,
796/// providing the mapping between R5-specific ViewDefinition structures
797/// and the version-agnostic trait interfaces. R5 introduces the Integer64
798/// data type for constant values.
799#[cfg(feature = "R5")]
800mod r5_impl {
801 use super::*;
802 use helios_fhir::r5::*;
803
804 impl ViewDefinitionTrait for ViewDefinition {
805 type Select = ViewDefinitionSelect;
806 type Where = ViewDefinitionWhere;
807 type Constant = ViewDefinitionConstant;
808
809 fn resource(&self) -> Option<&str> {
810 self.resource.value.as_deref()
811 }
812
813 fn select(&self) -> Option<&[Self::Select]> {
814 self.select.as_deref()
815 }
816
817 fn where_clauses(&self) -> Option<&[Self::Where]> {
818 self.r#where.as_deref()
819 }
820
821 fn constants(&self) -> Option<&[Self::Constant]> {
822 self.constant.as_deref()
823 }
824 }
825
826 impl ViewDefinitionSelectTrait for ViewDefinitionSelect {
827 type Column = ViewDefinitionSelectColumn;
828 type Select = ViewDefinitionSelect;
829
830 fn column(&self) -> Option<&[Self::Column]> {
831 self.column.as_deref()
832 }
833
834 fn select(&self) -> Option<&[Self::Select]> {
835 self.select.as_deref()
836 }
837
838 fn for_each(&self) -> Option<&str> {
839 self.for_each.as_ref()?.value.as_deref()
840 }
841
842 fn for_each_or_null(&self) -> Option<&str> {
843 self.for_each_or_null.as_ref()?.value.as_deref()
844 }
845
846 fn union_all(&self) -> Option<&[Self::Select]> {
847 self.union_all.as_deref()
848 }
849 }
850
851 impl ViewDefinitionColumnTrait for ViewDefinitionSelectColumn {
852 fn name(&self) -> Option<&str> {
853 self.name.value.as_deref()
854 }
855
856 fn path(&self) -> Option<&str> {
857 self.path.value.as_deref()
858 }
859
860 fn collection(&self) -> Option<bool> {
861 self.collection.as_ref()?.value
862 }
863 }
864
865 impl ViewDefinitionWhereTrait for ViewDefinitionWhere {
866 fn path(&self) -> Option<&str> {
867 self.path.value.as_deref()
868 }
869 }
870
871 impl ViewDefinitionConstantTrait for ViewDefinitionConstant {
872 fn name(&self) -> Option<&str> {
873 self.name.value.as_deref()
874 }
875
876 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError> {
877 let name = self.name().unwrap_or("unknown");
878
879 if let Some(value) = &self.value {
880 // R5 implementation identical to R4
881 let eval_result = match value {
882 ViewDefinitionConstantValue::String(s) => {
883 EvaluationResult::String(s.value.clone().unwrap_or_default(), None)
884 }
885 ViewDefinitionConstantValue::Boolean(b) => {
886 EvaluationResult::Boolean(b.value.unwrap_or(false), None)
887 }
888 ViewDefinitionConstantValue::Integer(i) => {
889 EvaluationResult::Integer(i.value.unwrap_or(0) as i64, None)
890 }
891 ViewDefinitionConstantValue::Decimal(d) => {
892 if let Some(precise_decimal) = &d.value {
893 match precise_decimal.original_string().parse() {
894 Ok(decimal_value) => EvaluationResult::Decimal(decimal_value, None),
895 Err(_) => {
896 return Err(SofError::InvalidViewDefinition(format!(
897 "Invalid decimal value for constant '{}'",
898 name
899 )));
900 }
901 }
902 } else {
903 EvaluationResult::Decimal("0".parse().unwrap(), None)
904 }
905 }
906 ViewDefinitionConstantValue::Date(d) => {
907 EvaluationResult::Date(d.value.clone().unwrap_or_default(), None)
908 }
909 ViewDefinitionConstantValue::DateTime(dt) => {
910 let value = dt.value.clone().unwrap_or_default();
911 // Ensure DateTime values have the "@" prefix for FHIRPath
912 let prefixed = if value.starts_with("@") {
913 value
914 } else {
915 format!("@{}", value)
916 };
917 EvaluationResult::DateTime(
918 prefixed,
919 Some(TypeInfoResult::new("FHIR", "dateTime")),
920 )
921 }
922 ViewDefinitionConstantValue::Time(t) => {
923 let value = t.value.clone().unwrap_or_default();
924 // Ensure Time values have the "@T" prefix for FHIRPath
925 let prefixed = if value.starts_with("@T") {
926 value
927 } else {
928 format!("@T{}", value)
929 };
930 EvaluationResult::Time(prefixed, None)
931 }
932 ViewDefinitionConstantValue::Code(c) => {
933 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
934 }
935 ViewDefinitionConstantValue::Base64Binary(b) => {
936 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
937 }
938 ViewDefinitionConstantValue::Id(i) => {
939 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
940 }
941 ViewDefinitionConstantValue::Instant(i) => {
942 let value = i.value.clone().unwrap_or_default();
943 // Ensure Instant values have the "@" prefix for FHIRPath
944 let prefixed = if value.starts_with("@") {
945 value
946 } else {
947 format!("@{}", value)
948 };
949 EvaluationResult::DateTime(
950 prefixed,
951 Some(TypeInfoResult::new("FHIR", "instant")),
952 )
953 }
954 ViewDefinitionConstantValue::Oid(o) => {
955 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
956 }
957 ViewDefinitionConstantValue::PositiveInt(p) => {
958 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
959 }
960 ViewDefinitionConstantValue::UnsignedInt(u) => {
961 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
962 }
963 ViewDefinitionConstantValue::Uri(u) => {
964 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
965 }
966 ViewDefinitionConstantValue::Url(u) => {
967 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
968 }
969 ViewDefinitionConstantValue::Uuid(u) => {
970 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
971 }
972 ViewDefinitionConstantValue::Canonical(c) => {
973 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
974 }
975 ViewDefinitionConstantValue::Integer64(i) => {
976 EvaluationResult::Integer64(i.value.unwrap_or(0), None)
977 }
978 };
979
980 Ok(eval_result)
981 } else {
982 Err(SofError::InvalidViewDefinition(format!(
983 "Constant '{}' must have a value",
984 name
985 )))
986 }
987 }
988 }
989
990 impl BundleTrait for Bundle {
991 type Resource = Resource;
992
993 fn entries(&self) -> Vec<&Self::Resource> {
994 self.entry
995 .as_ref()
996 .map(|entries| {
997 entries
998 .iter()
999 .filter_map(|e| e.resource.as_deref()) // Note: R5 uses Box<Resource>
1000 .collect()
1001 })
1002 .unwrap_or_default()
1003 }
1004 }
1005
1006 impl ResourceTrait for Resource {
1007 fn resource_name(&self) -> &str {
1008 self.resource_name()
1009 }
1010
1011 fn to_fhir_resource(&self) -> FhirResource {
1012 FhirResource::R5(Box::new(self.clone()))
1013 }
1014
1015 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
1016 self.get_last_updated()
1017 }
1018 }
1019}
1020
1021/// R6 (FHIR 6.0.0) trait implementations.
1022///
1023/// This module implements all abstraction traits for FHIR R6 resources,
1024/// providing the mapping between R6-specific ViewDefinition structures
1025/// and the version-agnostic trait interfaces. R6 continues to support
1026/// the Integer64 data type introduced in R5.
1027#[cfg(feature = "R6")]
1028mod r6_impl {
1029 use super::*;
1030 use helios_fhir::r6::*;
1031
1032 impl ViewDefinitionTrait for ViewDefinition {
1033 type Select = ViewDefinitionSelect;
1034 type Where = ViewDefinitionWhere;
1035 type Constant = ViewDefinitionConstant;
1036
1037 fn resource(&self) -> Option<&str> {
1038 self.resource.value.as_deref()
1039 }
1040
1041 fn select(&self) -> Option<&[Self::Select]> {
1042 self.select.as_deref()
1043 }
1044
1045 fn where_clauses(&self) -> Option<&[Self::Where]> {
1046 self.r#where.as_deref()
1047 }
1048
1049 fn constants(&self) -> Option<&[Self::Constant]> {
1050 self.constant.as_deref()
1051 }
1052 }
1053
1054 impl ViewDefinitionSelectTrait for ViewDefinitionSelect {
1055 type Column = ViewDefinitionSelectColumn;
1056 type Select = ViewDefinitionSelect;
1057
1058 fn column(&self) -> Option<&[Self::Column]> {
1059 self.column.as_deref()
1060 }
1061
1062 fn select(&self) -> Option<&[Self::Select]> {
1063 self.select.as_deref()
1064 }
1065
1066 fn for_each(&self) -> Option<&str> {
1067 self.for_each.as_ref()?.value.as_deref()
1068 }
1069
1070 fn for_each_or_null(&self) -> Option<&str> {
1071 self.for_each_or_null.as_ref()?.value.as_deref()
1072 }
1073
1074 fn union_all(&self) -> Option<&[Self::Select]> {
1075 self.union_all.as_deref()
1076 }
1077 }
1078
1079 impl ViewDefinitionColumnTrait for ViewDefinitionSelectColumn {
1080 fn name(&self) -> Option<&str> {
1081 self.name.value.as_deref()
1082 }
1083
1084 fn path(&self) -> Option<&str> {
1085 self.path.value.as_deref()
1086 }
1087
1088 fn collection(&self) -> Option<bool> {
1089 self.collection.as_ref()?.value
1090 }
1091 }
1092
1093 impl ViewDefinitionWhereTrait for ViewDefinitionWhere {
1094 fn path(&self) -> Option<&str> {
1095 self.path.value.as_deref()
1096 }
1097 }
1098
1099 impl ViewDefinitionConstantTrait for ViewDefinitionConstant {
1100 fn name(&self) -> Option<&str> {
1101 self.name.value.as_deref()
1102 }
1103
1104 fn to_evaluation_result(&self) -> Result<EvaluationResult, SofError> {
1105 let name = self.name().unwrap_or("unknown");
1106
1107 if let Some(value) = &self.value {
1108 // R5 implementation identical to R4
1109 let eval_result = match value {
1110 ViewDefinitionConstantValue::String(s) => {
1111 EvaluationResult::String(s.value.clone().unwrap_or_default(), None)
1112 }
1113 ViewDefinitionConstantValue::Boolean(b) => {
1114 EvaluationResult::Boolean(b.value.unwrap_or(false), None)
1115 }
1116 ViewDefinitionConstantValue::Integer(i) => {
1117 EvaluationResult::Integer(i.value.unwrap_or(0) as i64, None)
1118 }
1119 ViewDefinitionConstantValue::Decimal(d) => {
1120 if let Some(precise_decimal) = &d.value {
1121 match precise_decimal.original_string().parse() {
1122 Ok(decimal_value) => EvaluationResult::Decimal(decimal_value, None),
1123 Err(_) => {
1124 return Err(SofError::InvalidViewDefinition(format!(
1125 "Invalid decimal value for constant '{}'",
1126 name
1127 )));
1128 }
1129 }
1130 } else {
1131 EvaluationResult::Decimal("0".parse().unwrap(), None)
1132 }
1133 }
1134 ViewDefinitionConstantValue::Date(d) => {
1135 EvaluationResult::Date(d.value.clone().unwrap_or_default(), None)
1136 }
1137 ViewDefinitionConstantValue::DateTime(dt) => {
1138 let value = dt.value.clone().unwrap_or_default();
1139 // Ensure DateTime values have the "@" prefix for FHIRPath
1140 let prefixed = if value.starts_with("@") {
1141 value
1142 } else {
1143 format!("@{}", value)
1144 };
1145 EvaluationResult::DateTime(
1146 prefixed,
1147 Some(TypeInfoResult::new("FHIR", "dateTime")),
1148 )
1149 }
1150 ViewDefinitionConstantValue::Time(t) => {
1151 let value = t.value.clone().unwrap_or_default();
1152 // Ensure Time values have the "@T" prefix for FHIRPath
1153 let prefixed = if value.starts_with("@T") {
1154 value
1155 } else {
1156 format!("@T{}", value)
1157 };
1158 EvaluationResult::Time(prefixed, None)
1159 }
1160 ViewDefinitionConstantValue::Code(c) => {
1161 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
1162 }
1163 ViewDefinitionConstantValue::Base64Binary(b) => {
1164 EvaluationResult::String(b.value.clone().unwrap_or_default(), None)
1165 }
1166 ViewDefinitionConstantValue::Id(i) => {
1167 EvaluationResult::String(i.value.clone().unwrap_or_default(), None)
1168 }
1169 ViewDefinitionConstantValue::Instant(i) => {
1170 let value = i.value.clone().unwrap_or_default();
1171 // Ensure Instant values have the "@" prefix for FHIRPath
1172 let prefixed = if value.starts_with("@") {
1173 value
1174 } else {
1175 format!("@{}", value)
1176 };
1177 EvaluationResult::DateTime(
1178 prefixed,
1179 Some(TypeInfoResult::new("FHIR", "instant")),
1180 )
1181 }
1182 ViewDefinitionConstantValue::Oid(o) => {
1183 EvaluationResult::String(o.value.clone().unwrap_or_default(), None)
1184 }
1185 ViewDefinitionConstantValue::PositiveInt(p) => {
1186 EvaluationResult::Integer(p.value.unwrap_or(1) as i64, None)
1187 }
1188 ViewDefinitionConstantValue::UnsignedInt(u) => {
1189 EvaluationResult::Integer(u.value.unwrap_or(0) as i64, None)
1190 }
1191 ViewDefinitionConstantValue::Uri(u) => {
1192 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
1193 }
1194 ViewDefinitionConstantValue::Url(u) => {
1195 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
1196 }
1197 ViewDefinitionConstantValue::Uuid(u) => {
1198 EvaluationResult::String(u.value.clone().unwrap_or_default(), None)
1199 }
1200 ViewDefinitionConstantValue::Canonical(c) => {
1201 EvaluationResult::String(c.value.clone().unwrap_or_default(), None)
1202 }
1203 ViewDefinitionConstantValue::Integer64(i) => {
1204 EvaluationResult::Integer(i.value.unwrap_or(0), None)
1205 }
1206 };
1207
1208 Ok(eval_result)
1209 } else {
1210 Err(SofError::InvalidViewDefinition(format!(
1211 "Constant '{}' must have a value",
1212 name
1213 )))
1214 }
1215 }
1216 }
1217
1218 impl BundleTrait for Bundle {
1219 type Resource = Resource;
1220
1221 fn entries(&self) -> Vec<&Self::Resource> {
1222 self.entry
1223 .as_ref()
1224 .map(|entries| {
1225 entries
1226 .iter()
1227 .filter_map(|e| e.resource.as_deref()) // Note: R6 uses Box<Resource>
1228 .collect()
1229 })
1230 .unwrap_or_default()
1231 }
1232 }
1233
1234 impl ResourceTrait for Resource {
1235 fn resource_name(&self) -> &str {
1236 self.resource_name()
1237 }
1238
1239 fn to_fhir_resource(&self) -> FhirResource {
1240 FhirResource::R6(Box::new(self.clone()))
1241 }
1242
1243 fn get_last_updated(&self) -> Option<chrono::DateTime<chrono::Utc>> {
1244 self.get_last_updated()
1245 }
1246 }
1247}