helios_fhir/lib.rs
1//! # FHIR Model Infrastructure
2//!
3//! This module provides the foundational types and infrastructure that support the
4//! generated FHIR specification implementations. It contains hand-coded types that
5//! enable the generated code to handle FHIR's complex requirements for precision,
6//! extensions, and cross-version compatibility.
7
8//!
9//! ## Architecture
10//!
11//! The FHIR crate is organized as follows:
12//! - **Generated modules** (`r4.rs`, `r4b.rs`, `r5.rs`, `r6.rs`): Complete FHIR type implementations
13//! - **Infrastructure module** (`lib.rs`): Foundational types used by generated code
14//! - **Test modules**: Validation against official FHIR examples
15//!
16//! ## Key Infrastructure Types
17//!
18//! - [`PreciseDecimal`] - High-precision decimal arithmetic preserving original string format
19//! - [`Element<T, Extension>`] - Base container for FHIR elements with extension support
20//! - [`DecimalElement<Extension>`] - Specialized element for decimal values
21//! - [`FhirVersion`] - Version enumeration for multi-version support
22//!
23//! ## Usage Example
24//!
25//! ```rust
26//! use helios_fhir::r4::{Patient, HumanName};
27//! use helios_fhir::PreciseDecimal;
28//! use rust_decimal::Decimal;
29//!
30//! // Create a patient with precise decimal handling
31//! let patient = Patient {
32//! name: Some(vec![HumanName {
33//! family: Some("Doe".to_string().into()),
34//! given: Some(vec!["John".to_string().into()]),
35//! ..Default::default()
36//! }]),
37//! ..Default::default()
38//! };
39//!
40//! // Work with precise decimals
41//! let precise = PreciseDecimal::from(Decimal::new(12340, 3)); // 12.340
42//! ```
43
44use helios_fhirpath_support::{EvaluationResult, IntoEvaluationResult};
45use rust_decimal::Decimal;
46use serde::{
47 Deserialize, Serialize,
48 de::{self, Deserializer, MapAccess, Visitor},
49 ser::{SerializeStruct, Serializer},
50};
51use std::marker::PhantomData;
52
53/// Custom deserializer that is more forgiving of null values in JSON.
54///
55/// This creates a custom `Option<T>` deserializer that will return None for null values
56/// but also for any deserialization errors. This makes it possible to skip over
57/// malformed or unexpected values in FHIR JSON.
58pub fn deserialize_forgiving_option<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
59where
60 T: Deserialize<'de>,
61 D: Deserializer<'de>,
62{
63 // Use the intermediate Value approach to check for null first
64 let json_value = serde_json::Value::deserialize(deserializer)?;
65
66 match json_value {
67 serde_json::Value::Null => Ok(None),
68 _ => {
69 // Try to deserialize the value, but return None if it fails
70 match T::deserialize(json_value) {
71 Ok(value) => Ok(Some(value)),
72 Err(_) => Ok(None), // Ignore errors and return None
73 }
74 }
75 }
76}
77
78/// High-precision decimal type that preserves original string representation.
79///
80/// FHIR requires that decimal values maintain their original precision and format
81/// when serialized back to JSON. This type stores both the parsed `Decimal` value
82/// for mathematical operations and the original string for serialization.
83///
84/// # FHIR Precision Requirements
85///
86/// FHIR decimal values must:
87/// - Preserve trailing zeros (e.g., "12.340" vs "12.34")
88/// - Maintain original precision during round-trip serialization
89/// - Support high-precision arithmetic without floating-point errors
90/// - Handle edge cases like very large or very small numbers
91///
92/// # Examples
93///
94/// ```rust
95/// use helios_fhir::PreciseDecimal;
96/// use rust_decimal::Decimal;
97///
98/// // Create from Decimal (derives string representation)
99/// let precise = PreciseDecimal::from(Decimal::new(12340, 3)); // 12.340
100/// assert_eq!(precise.original_string(), "12.340");
101///
102/// // Create with specific string format
103/// let precise = PreciseDecimal::from_parts(
104/// Some(Decimal::new(1000, 2)),
105/// "10.00".to_string()
106/// );
107/// assert_eq!(precise.original_string(), "10.00");
108/// ```
109#[derive(Debug, Clone)]
110pub struct PreciseDecimal {
111 /// The parsed decimal value, `None` if parsing failed (e.g., out of range)
112 value: Option<Decimal>,
113 /// The original string representation preserving format and precision
114 original_string: String,
115}
116
117/// Implements equality comparison based on the parsed decimal value.
118///
119/// Two `PreciseDecimal` values are equal if their parsed `Decimal` values are equal,
120/// regardless of their original string representations. This enables mathematical
121/// equality while preserving string format for serialization.
122///
123/// # Examples
124///
125/// ```rust
126/// use helios_fhir::PreciseDecimal;
127/// use rust_decimal::Decimal;
128///
129/// let a = PreciseDecimal::from_parts(Some(Decimal::new(100, 1)), "10.0".to_string());
130/// let b = PreciseDecimal::from_parts(Some(Decimal::new(1000, 2)), "10.00".to_string());
131/// assert_eq!(a, b); // Same decimal value (10.0 == 10.00)
132/// ```
133impl PartialEq for PreciseDecimal {
134 fn eq(&self, other: &Self) -> bool {
135 // Compare parsed decimal values for mathematical equality
136 self.value == other.value
137 }
138}
139
140/// Marker trait implementation indicating total equality for `PreciseDecimal`.
141impl Eq for PreciseDecimal {}
142
143/// Implements partial ordering based on the parsed decimal value.
144///
145/// Ordering is based on the mathematical value of the decimal, not the string
146/// representation. `None` values (unparseable decimals) are considered less than
147/// any valid decimal value.
148impl PartialOrd for PreciseDecimal {
149 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
150 Some(self.cmp(other))
151 }
152}
153
154/// Implements total ordering for `PreciseDecimal`.
155///
156/// Provides a consistent ordering for sorting operations. The ordering is based
157/// on the mathematical value: `None` < `Some(smaller_decimal)` < `Some(larger_decimal)`.
158impl Ord for PreciseDecimal {
159 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
160 self.value.cmp(&other.value)
161 }
162}
163
164// === PreciseDecimal Methods ===
165
166impl PreciseDecimal {
167 /// Creates a new `PreciseDecimal` from its constituent parts.
168 ///
169 /// This constructor allows explicit control over both the parsed value and the
170 /// original string representation. Use this when you need to preserve a specific
171 /// string format or when parsing has already been attempted.
172 ///
173 /// # Arguments
174 ///
175 /// * `value` - The parsed decimal value, or `None` if parsing failed
176 /// * `original_string` - The original string representation to preserve
177 ///
178 /// # Examples
179 ///
180 /// ```rust
181 /// use helios_fhir::PreciseDecimal;
182 /// use rust_decimal::Decimal;
183 ///
184 /// // Create with successful parsing
185 /// let precise = PreciseDecimal::from_parts(
186 /// Some(Decimal::new(12340, 3)),
187 /// "12.340".to_string()
188 /// );
189 ///
190 /// // Create with failed parsing (preserves original string)
191 /// let invalid = PreciseDecimal::from_parts(
192 /// None,
193 /// "invalid_decimal".to_string()
194 /// );
195 /// ```
196 pub fn from_parts(value: Option<Decimal>, original_string: String) -> Self {
197 Self {
198 value,
199 original_string,
200 }
201 }
202
203 /// Helper method to parse a decimal string with support for scientific notation.
204 ///
205 /// This method handles the complexity of parsing decimal strings that may be in
206 /// scientific notation (with 'E' or 'e' exponents) or regular decimal format.
207 /// It normalizes 'E' to 'e' for consistent parsing while preserving the original
208 /// string representation for serialization.
209 ///
210 /// # Arguments
211 ///
212 /// * `s` - The string to parse as a decimal
213 ///
214 /// # Returns
215 ///
216 /// `Some(Decimal)` if parsing succeeds, `None` if the string is not a valid decimal.
217 ///
218 /// # Examples
219 ///
220 /// ```ignore
221 /// use helios_fhir::PreciseDecimal;
222 /// use rust_decimal::Decimal;
223 ///
224 /// // Regular decimal format
225 /// assert!(PreciseDecimal::parse_decimal_string("123.45").is_some());
226 ///
227 /// // Scientific notation with 'e'
228 /// assert!(PreciseDecimal::parse_decimal_string("1.23e2").is_some());
229 ///
230 /// // Scientific notation with 'E' (normalized to 'e')
231 /// assert!(PreciseDecimal::parse_decimal_string("1.23E2").is_some());
232 ///
233 /// // Invalid format
234 /// assert!(PreciseDecimal::parse_decimal_string("invalid").is_none());
235 /// ```
236 fn parse_decimal_string(s: &str) -> Option<Decimal> {
237 // Normalize 'E' to 'e' for consistent parsing
238 let normalized = s.replace('E', "e");
239
240 if normalized.contains('e') {
241 // Use scientific notation parsing
242 Decimal::from_scientific(&normalized).ok()
243 } else {
244 // Use regular decimal parsing
245 normalized.parse::<Decimal>().ok()
246 }
247 }
248
249 /// Returns the parsed decimal value if parsing was successful.
250 ///
251 /// This method provides access to the mathematical value for arithmetic
252 /// operations and comparisons. Returns `None` if the original string
253 /// could not be parsed as a valid decimal.
254 ///
255 /// # Examples
256 ///
257 /// ```rust
258 /// use helios_fhir::PreciseDecimal;
259 /// use rust_decimal::Decimal;
260 ///
261 /// let precise = PreciseDecimal::from(Decimal::new(1234, 2)); // 12.34
262 /// assert_eq!(precise.value(), Some(Decimal::new(1234, 2)));
263 ///
264 /// let invalid = PreciseDecimal::from_parts(None, "invalid".to_string());
265 /// assert_eq!(invalid.value(), None);
266 /// ```
267 pub fn value(&self) -> Option<Decimal> {
268 self.value
269 }
270
271 /// Returns the original string representation.
272 ///
273 /// This method provides access to the exact string format that was used
274 /// to create this `PreciseDecimal`. This string is used during serialization
275 /// to maintain FHIR's precision requirements.
276 ///
277 /// # Examples
278 ///
279 /// ```rust
280 /// use helios_fhir::PreciseDecimal;
281 /// use rust_decimal::Decimal;
282 ///
283 /// let precise = PreciseDecimal::from_parts(
284 /// Some(Decimal::new(100, 2)),
285 /// "1.00".to_string()
286 /// );
287 /// assert_eq!(precise.original_string(), "1.00");
288 /// ```
289 pub fn original_string(&self) -> &str {
290 &self.original_string
291 }
292}
293
294/// Converts a `Decimal` to `PreciseDecimal` with derived string representation.
295///
296/// This implementation allows easy conversion from `rust_decimal::Decimal` values
297/// by automatically generating the string representation using the decimal's
298/// `Display` implementation.
299///
300/// # Examples
301///
302/// ```rust
303/// use helios_fhir::PreciseDecimal;
304/// use rust_decimal::Decimal;
305///
306/// let decimal = Decimal::new(12345, 3); // 12.345
307/// let precise: PreciseDecimal = decimal.into();
308/// assert_eq!(precise.value(), Some(decimal));
309/// assert_eq!(precise.original_string(), "12.345");
310/// ```
311impl From<Decimal> for PreciseDecimal {
312 fn from(value: Decimal) -> Self {
313 // Generate string representation from the decimal value
314 let original_string = value.to_string();
315 Self {
316 value: Some(value),
317 original_string,
318 }
319 }
320}
321
322/// Implements serialization for `PreciseDecimal` preserving original format.
323///
324/// This implementation ensures that the exact original string representation
325/// is preserved during JSON serialization, maintaining FHIR's precision
326/// requirements including trailing zeros and specific formatting.
327///
328/// # FHIR Compliance
329///
330/// FHIR requires that decimal values maintain their original precision when
331/// round-tripped through JSON. This implementation uses `serde_json::RawValue`
332/// to serialize the original string directly as a JSON number.
333///
334/// # Examples
335///
336/// ```rust
337/// use helios_fhir::PreciseDecimal;
338/// use rust_decimal::Decimal;
339/// use serde_json;
340///
341/// let precise = PreciseDecimal::from_parts(
342/// Some(Decimal::new(1230, 2)),
343/// "12.30".to_string()
344/// );
345///
346/// let json = serde_json::to_string(&precise).unwrap();
347/// assert_eq!(json, "12.30"); // Preserves trailing zero
348/// ```
349impl Serialize for PreciseDecimal {
350 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
351 where
352 S: Serializer,
353 {
354 // Use RawValue to preserve exact string format in JSON
355 match serde_json::value::RawValue::from_string(self.original_string.clone()) {
356 Ok(raw_value) => raw_value.serialize(serializer),
357 Err(e) => Err(serde::ser::Error::custom(format!(
358 "Failed to serialize PreciseDecimal '{}': {}",
359 self.original_string, e
360 ))),
361 }
362 }
363}
364
365/// Implements deserialization for `PreciseDecimal` preserving original format.
366///
367/// This implementation deserializes JSON numbers and strings into `PreciseDecimal`
368/// while preserving the exact original string representation. It handles various
369/// JSON formats including scientific notation and nested object structures.
370///
371/// # Supported Formats
372///
373/// - Direct numbers: `12.340`
374/// - String numbers: `"12.340"`
375/// - Scientific notation: `1.234e2` or `1.234E2`
376/// - Nested objects: `{"value": 12.340}` (for macro-generated structures)
377///
378/// # Examples
379///
380/// ```rust
381/// use helios_fhir::PreciseDecimal;
382/// use serde_json;
383///
384/// // Deserialize from JSON number (trailing zeros are normalized)
385/// let precise: PreciseDecimal = serde_json::from_str("12.340").unwrap();
386/// assert_eq!(precise.original_string(), "12.340"); // JSON number format
387///
388/// // Deserialize from JSON string (preserves exact format)
389/// let precise: PreciseDecimal = serde_json::from_str("\"12.340\"").unwrap();
390/// assert_eq!(precise.original_string(), "12.340"); // Preserves string format
391/// ```
392impl<'de> Deserialize<'de> for PreciseDecimal {
393 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
394 where
395 D: Deserializer<'de>,
396 {
397 // Use intermediate Value to capture exact string representation
398 let json_value = serde_json::Value::deserialize(deserializer)?;
399
400 match json_value {
401 serde_json::Value::Number(n) => {
402 // Extract string representation from JSON number
403 let original_string = n.to_string();
404 let parsed_value = Self::parse_decimal_string(&original_string);
405 Ok(PreciseDecimal::from_parts(parsed_value, original_string))
406 }
407 serde_json::Value::String(s) => {
408 // Use string value directly (preserves exact format)
409 let parsed_value = Self::parse_decimal_string(&s);
410 Ok(PreciseDecimal::from_parts(parsed_value, s))
411 }
412 // Handle nested object format (for macro-generated structures)
413 serde_json::Value::Object(map) => match map.get("value") {
414 Some(serde_json::Value::Number(n)) => {
415 let original_string = n.to_string();
416 let parsed_value = Self::parse_decimal_string(&original_string);
417 Ok(PreciseDecimal::from_parts(parsed_value, original_string))
418 }
419 Some(serde_json::Value::String(s)) => {
420 let original_string = s.clone();
421 let parsed_value = Self::parse_decimal_string(&original_string);
422 Ok(PreciseDecimal::from_parts(parsed_value, original_string))
423 }
424 Some(serde_json::Value::Null) => Err(de::Error::invalid_value(
425 de::Unexpected::Unit,
426 &"a number or string for decimal value",
427 )),
428 None => Err(de::Error::missing_field("value")),
429 _ => Err(de::Error::invalid_type(
430 de::Unexpected::Map,
431 &"a map with a 'value' field containing a number or string",
432 )),
433 },
434 // Handle remaining unexpected types
435 other => Err(de::Error::invalid_type(
436 match other {
437 serde_json::Value::Null => de::Unexpected::Unit, // Or Unexpected::Option if mapping null to None
438 serde_json::Value::Bool(b) => de::Unexpected::Bool(b),
439 serde_json::Value::Array(_) => de::Unexpected::Seq,
440 _ => de::Unexpected::Other("unexpected JSON type for PreciseDecimal"),
441 },
442 &"a number, string, or object with a 'value' field",
443 )),
444 }
445 }
446}
447
448// --- End PreciseDecimal ---
449
450// Removed DecimalElementObjectVisitor
451
452#[cfg(feature = "R4")]
453pub mod r4;
454#[cfg(feature = "R4B")]
455pub mod r4b;
456#[cfg(feature = "R5")]
457pub mod r5;
458#[cfg(feature = "R6")]
459pub mod r6;
460
461pub mod parameters;
462
463// Re-export commonly used types from parameters module
464pub use parameters::{ParameterValueAccessor, VersionIndependentParameters};
465
466// Removed the FhirSerde trait definition
467
468/// Multi-version FHIR resource container supporting version-agnostic operations.
469///
470/// This enum provides a unified interface for working with FHIR resources across
471/// different specification versions. It enables applications to handle multiple
472/// FHIR versions simultaneously while maintaining type safety and version-specific
473/// behavior where needed.
474///
475/// # Supported Versions
476///
477/// - **R4**: FHIR 4.0.1 (normative)
478/// - **R4B**: FHIR 4.3.0 (ballot)
479/// - **R5**: FHIR 5.0.0 (ballot)
480/// - **R6**: FHIR 6.0.0 (draft)
481///
482/// # Feature Flags
483///
484/// Each FHIR version is controlled by a corresponding Cargo feature flag.
485/// Only enabled versions will be available in the enum variants.
486///
487/// # Examples
488///
489/// ```rust
490/// use helios_fhir::{FhirResource, FhirVersion};
491/// # #[cfg(feature = "R4")]
492/// use helios_fhir::r4::{Patient, HumanName};
493///
494/// # #[cfg(feature = "R4")]
495/// {
496/// // Create an R4 patient
497/// let patient = Patient {
498/// name: Some(vec![HumanName {
499/// family: Some("Doe".to_string().into()),
500/// given: Some(vec!["John".to_string().into()]),
501/// ..Default::default()
502/// }]),
503/// ..Default::default()
504/// };
505///
506/// // Wrap in version-agnostic container
507/// let resource = FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(patient)));
508/// assert_eq!(resource.version(), FhirVersion::R4);
509/// }
510/// ```
511///
512/// # Version Detection
513///
514/// Use the `version()` method to determine which FHIR version a resource uses:
515///
516/// ```rust
517/// # use helios_fhir::{FhirResource, FhirVersion};
518/// # #[cfg(feature = "R4")]
519/// # {
520/// # let resource = FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(Default::default())));
521/// match resource.version() {
522/// #[cfg(feature = "R4")]
523/// FhirVersion::R4 => println!("This is an R4 resource"),
524/// #[cfg(feature = "R4B")]
525/// FhirVersion::R4B => println!("This is an R4B resource"),
526/// #[cfg(feature = "R5")]
527/// FhirVersion::R5 => println!("This is an R5 resource"),
528/// #[cfg(feature = "R6")]
529/// FhirVersion::R6 => println!("This is an R6 resource"),
530/// }
531/// # }
532/// ```
533#[derive(Debug)]
534pub enum FhirResource {
535 /// FHIR 4.0.1 (normative) resource
536 #[cfg(feature = "R4")]
537 R4(Box<r4::Resource>),
538 /// FHIR 4.3.0 (ballot) resource
539 #[cfg(feature = "R4B")]
540 R4B(Box<r4b::Resource>),
541 /// FHIR 5.0.0 (ballot) resource
542 #[cfg(feature = "R5")]
543 R5(Box<r5::Resource>),
544 /// FHIR 6.0.0 (draft) resource
545 #[cfg(feature = "R6")]
546 R6(Box<r6::Resource>),
547}
548
549impl FhirResource {
550 /// Returns the FHIR specification version of this resource.
551 ///
552 /// This method provides version detection for multi-version applications,
553 /// enabling version-specific processing logic and compatibility checks.
554 ///
555 /// # Returns
556 ///
557 /// The `FhirVersion` enum variant corresponding to this resource's specification.
558 ///
559 /// # Examples
560 ///
561 /// ```rust
562 /// use helios_fhir::{FhirResource, FhirVersion};
563 ///
564 /// # #[cfg(feature = "R5")]
565 /// # {
566 /// # let resource = FhirResource::R5(Box::new(helios_fhir::r5::Resource::Patient(Default::default())));
567 /// let version = resource.version();
568 /// assert_eq!(version, FhirVersion::R5);
569 ///
570 /// // Use version for conditional logic
571 /// match version {
572 /// FhirVersion::R5 => {
573 /// println!("Processing R5 resource with latest features");
574 /// },
575 /// FhirVersion::R4 => {
576 /// println!("Processing R4 resource with normative features");
577 /// },
578 /// _ => {
579 /// println!("Processing other FHIR version");
580 /// }
581 /// }
582 /// # }
583 /// ```
584 pub fn version(&self) -> FhirVersion {
585 match self {
586 #[cfg(feature = "R4")]
587 FhirResource::R4(_) => FhirVersion::R4,
588 #[cfg(feature = "R4B")]
589 FhirResource::R4B(_) => FhirVersion::R4B,
590 #[cfg(feature = "R5")]
591 FhirResource::R5(_) => FhirVersion::R5,
592 #[cfg(feature = "R6")]
593 FhirResource::R6(_) => FhirVersion::R6,
594 }
595 }
596}
597
598/// Enumeration of supported FHIR specification versions.
599///
600/// This enum represents the different versions of the FHIR (Fast Healthcare
601/// Interoperability Resources) specification that this library supports.
602/// Each version represents a specific release of the FHIR standard with
603/// its own set of features, resources, and compatibility requirements.
604///
605/// # Version Status
606///
607/// - **R4** (4.0.1): Normative version, widely adopted in production
608/// - **R4B** (4.3.0): Ballot version with additional features
609/// - **R5** (5.0.0): Ballot version with significant enhancements
610/// - **R6** (6.0.0): Draft version under active development
611///
612/// # Feature Flags
613///
614/// Each version is controlled by a corresponding Cargo feature flag:
615/// - `R4`: Enables FHIR R4 support
616/// - `R4B`: Enables FHIR R4B support
617/// - `R5`: Enables FHIR R5 support
618/// - `R6`: Enables FHIR R6 support
619///
620/// # Examples
621///
622/// ```rust
623/// use helios_fhir::FhirVersion;
624///
625/// // Version comparison
626/// # #[cfg(all(feature = "R4", feature = "R5"))]
627/// # {
628/// assert_ne!(FhirVersion::R4, FhirVersion::R5);
629/// # }
630///
631/// // String representation
632/// # #[cfg(feature = "R4")]
633/// # {
634/// let version = FhirVersion::R4;
635/// assert_eq!(version.as_str(), "R4");
636/// assert_eq!(version.to_string(), "R4");
637/// # }
638/// ```
639///
640/// # CLI Integration
641///
642/// This enum implements `clap::ValueEnum` for command-line argument parsing:
643///
644/// ```rust,no_run
645/// use clap::Parser;
646/// use helios_fhir::FhirVersion;
647///
648/// #[derive(Parser)]
649/// struct Args {
650/// #[arg(value_enum)]
651/// version: FhirVersion,
652/// }
653/// ```
654#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
655pub enum FhirVersion {
656 /// FHIR 4.0.1 (normative) - The current normative version
657 #[cfg(feature = "R4")]
658 R4,
659 /// FHIR 4.3.0 (ballot) - Intermediate version with additional features
660 #[cfg(feature = "R4B")]
661 R4B,
662 /// FHIR 5.0.0 (ballot) - Next major version with significant changes
663 #[cfg(feature = "R5")]
664 R5,
665 /// FHIR 6.0.0 (draft) - Future version under development
666 #[cfg(feature = "R6")]
667 R6,
668}
669
670impl FhirVersion {
671 /// Returns the string representation of the FHIR version.
672 ///
673 /// This method provides the standard version identifier as used in
674 /// FHIR documentation, URLs, and configuration files.
675 ///
676 /// # Returns
677 ///
678 /// A static string slice representing the version (e.g., "R4", "R5").
679 ///
680 /// # Examples
681 ///
682 /// ```rust
683 /// use helios_fhir::FhirVersion;
684 ///
685 /// # #[cfg(feature = "R4")]
686 /// assert_eq!(FhirVersion::R4.as_str(), "R4");
687 /// # #[cfg(feature = "R5")]
688 /// assert_eq!(FhirVersion::R5.as_str(), "R5");
689 /// ```
690 ///
691 /// # Usage
692 ///
693 /// This method is commonly used for:
694 /// - Logging and debugging output
695 /// - Configuration file parsing
696 /// - API endpoint construction
697 /// - Version-specific resource loading
698 pub fn as_str(&self) -> &'static str {
699 match self {
700 #[cfg(feature = "R4")]
701 FhirVersion::R4 => "R4",
702 #[cfg(feature = "R4B")]
703 FhirVersion::R4B => "R4B",
704 #[cfg(feature = "R5")]
705 FhirVersion::R5 => "R5",
706 #[cfg(feature = "R6")]
707 FhirVersion::R6 => "R6",
708 }
709 }
710}
711
712/// Implements `Display` trait for user-friendly output formatting.
713///
714/// This enables `FhirVersion` to be used in string formatting operations
715/// and provides consistent output across different contexts.
716///
717/// # Examples
718///
719/// ```rust
720/// use helios_fhir::FhirVersion;
721///
722/// # #[cfg(feature = "R5")]
723/// # {
724/// let version = FhirVersion::R5;
725/// println!("Using FHIR version: {}", version); // Prints: "Using FHIR version: R5"
726///
727/// let formatted = format!("fhir-{}.json", version);
728/// assert_eq!(formatted, "fhir-R5.json");
729/// # }
730/// ```
731impl std::fmt::Display for FhirVersion {
732 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
733 write!(f, "{}", self.as_str())
734 }
735}
736
737/// Provides a default FHIR version when R4 feature is enabled.
738///
739/// R4 is chosen as the default because it is the current normative version
740/// of the FHIR specification and is widely adopted in production systems.
741///
742/// # Examples
743///
744/// ```rust
745/// use helios_fhir::FhirVersion;
746///
747/// # #[cfg(feature = "R4")]
748/// # {
749/// let default_version = FhirVersion::default();
750/// assert_eq!(default_version, FhirVersion::R4);
751/// # }
752/// ```
753#[cfg(feature = "R4")]
754impl Default for FhirVersion {
755 fn default() -> Self {
756 FhirVersion::R4
757 }
758}
759
760/// Implements `clap::ValueEnum` for command-line argument parsing.
761///
762/// This implementation enables `FhirVersion` to be used directly as a command-line
763/// argument type with clap, providing automatic parsing, validation, and help text
764/// generation.
765///
766/// # Examples
767///
768/// ```rust,no_run
769/// use clap::Parser;
770/// use helios_fhir::FhirVersion;
771///
772/// #[derive(Parser)]
773/// struct Args {
774/// /// FHIR specification version to use
775/// #[arg(value_enum, default_value_t = FhirVersion::default())]
776/// version: FhirVersion,
777/// }
778///
779/// // Command line: my-app --version R5
780/// let args = Args::parse();
781/// println!("Using FHIR version: {}", args.version);
782/// ```
783///
784/// # Generated Help Text
785///
786/// When using this enum with clap, the help text will automatically include
787/// all available FHIR versions based on enabled feature flags.
788impl clap::ValueEnum for FhirVersion {
789 fn value_variants<'a>() -> &'a [Self] {
790 &[
791 #[cfg(feature = "R4")]
792 FhirVersion::R4,
793 #[cfg(feature = "R4B")]
794 FhirVersion::R4B,
795 #[cfg(feature = "R5")]
796 FhirVersion::R5,
797 #[cfg(feature = "R6")]
798 FhirVersion::R6,
799 ]
800 }
801
802 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
803 Some(clap::builder::PossibleValue::new(self.as_str()))
804 }
805}
806
807/// Trait for providing FHIR resource type information
808///
809/// This trait allows querying which resource types are available in a specific
810/// FHIR version without hardcoding resource type lists in multiple places.
811pub trait FhirResourceTypeProvider {
812 /// Returns a vector of all resource type names supported in this FHIR version
813 fn get_resource_type_names() -> Vec<&'static str>;
814
815 /// Checks if a given type name is a resource type in this FHIR version
816 fn is_resource_type(type_name: &str) -> bool {
817 Self::get_resource_type_names()
818 .iter()
819 .any(|&resource_type| resource_type.eq_ignore_ascii_case(type_name))
820 }
821}
822
823/// Trait for providing FHIR complex type information
824///
825/// This trait allows querying which complex data types are available in a specific
826/// FHIR version without hardcoding complex type lists in multiple places.
827pub trait FhirComplexTypeProvider {
828 /// Returns a vector of all complex type names supported in this FHIR version
829 fn get_complex_type_names() -> Vec<&'static str>;
830
831 /// Checks if a given type name is a complex type in this FHIR version
832 fn is_complex_type(type_name: &str) -> bool {
833 Self::get_complex_type_names()
834 .iter()
835 .any(|&complex_type| complex_type.eq_ignore_ascii_case(type_name))
836 }
837}
838
839// --- Internal Visitor for Element Object Deserialization ---
840
841/// Internal visitor struct for deserializing Element objects from JSON maps.
842///
843/// This visitor handles the complex deserialization logic for Element<V, E> when
844/// the JSON input is an object containing id, extension, and value fields.
845struct ElementObjectVisitor<V, E>(PhantomData<(V, E)>);
846
847impl<'de, V, E> Visitor<'de> for ElementObjectVisitor<V, E>
848where
849 V: Deserialize<'de>,
850 E: Deserialize<'de>,
851{
852 type Value = Element<V, E>;
853
854 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
855 formatter.write_str("an Element object")
856 }
857
858 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
859 where
860 A: MapAccess<'de>,
861 {
862 let mut id: Option<String> = None;
863 let mut extension: Option<Vec<E>> = None;
864 let mut value: Option<V> = None;
865
866 // Manually deserialize fields from the map
867 while let Some(key) = map.next_key::<String>()? {
868 match key.as_str() {
869 "id" => {
870 if id.is_some() {
871 return Err(de::Error::duplicate_field("id"));
872 }
873 id = Some(map.next_value()?);
874 }
875 "extension" => {
876 if extension.is_some() {
877 return Err(de::Error::duplicate_field("extension"));
878 }
879 extension = Some(map.next_value()?);
880 }
881 "value" => {
882 if value.is_some() {
883 return Err(de::Error::duplicate_field("value"));
884 }
885 // Deserialize directly into Option<V>
886 value = Some(map.next_value()?);
887 }
888 // Ignore any unknown fields encountered
889 _ => {
890 let _ = map.next_value::<de::IgnoredAny>()?;
891 }
892 }
893 }
894
895 Ok(Element {
896 id,
897 extension,
898 value,
899 })
900 }
901}
902
903/// Generic element container supporting FHIR's extension mechanism.
904///
905/// In FHIR, most primitive elements can be extended with additional metadata
906/// through the `id` and `extension` fields. This container type provides
907/// the infrastructure to support this pattern across all FHIR data types.
908///
909/// # Type Parameters
910///
911/// * `V` - The value type (e.g., `String`, `i32`, `PreciseDecimal`)
912/// * `E` - The extension type (typically the generated `Extension` struct)
913///
914/// # FHIR Element Structure
915///
916/// FHIR elements can appear in three forms:
917/// 1. **Primitive value**: Just the value itself (e.g., `"text"`, `42`)
918/// 2. **Extended primitive**: An object with `value`, `id`, and/or `extension` fields
919/// 3. **Extension-only**: An object with just `id` and/or `extension` (no value)
920///
921/// # Examples
922///
923/// ```rust
924/// use helios_fhir::{Element, r4::Extension};
925///
926/// // Simple primitive value
927/// let simple: Element<String, Extension> = Element {
928/// value: Some("Hello World".to_string()),
929/// id: None,
930/// extension: None,
931/// };
932///
933/// // Extended primitive with ID
934/// let with_id: Element<String, Extension> = Element {
935/// value: Some("Hello World".to_string()),
936/// id: Some("text-element-1".to_string()),
937/// extension: None,
938/// };
939///
940/// // Extension-only element (no value)
941/// let extension_only: Element<String, Extension> = Element {
942/// value: None,
943/// id: Some("disabled-element".to_string()),
944/// extension: Some(vec![/* extensions */]),
945/// };
946/// ```
947///
948/// # Serialization Behavior
949///
950/// - If only `value` is present: serializes as the primitive value directly
951/// - If `id` or `extension` are present: serializes as an object with all fields
952/// - If everything is `None`: serializes as `null`
953#[derive(Debug, PartialEq, Eq, Clone, Default)]
954pub struct Element<V, E> {
955 /// Optional element identifier for referencing within the resource
956 pub id: Option<String>,
957 /// Optional extensions providing additional metadata
958 pub extension: Option<Vec<E>>,
959 /// The actual primitive value
960 pub value: Option<V>,
961}
962
963// Custom Deserialize for Element<V, E>
964// Remove PartialEq/Eq bounds for V and E as they are not needed for deserialization itself
965impl<'de, V, E> Deserialize<'de> for Element<V, E>
966where
967 V: Deserialize<'de> + 'static, // Added 'static for TypeId comparisons
968 E: Deserialize<'de>, // Removed PartialEq
969{
970 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
971 where
972 D: Deserializer<'de>,
973 {
974 // Use the AnyValueVisitor approach to handle different JSON input types
975 struct AnyValueVisitor<V, E>(PhantomData<(V, E)>);
976
977 impl<'de, V, E> Visitor<'de> for AnyValueVisitor<V, E>
978 where
979 V: Deserialize<'de> + 'static,
980 E: Deserialize<'de>,
981 {
982 type Value = Element<V, E>;
983
984 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
985 formatter
986 .write_str("a primitive value (string, number, boolean), an object, or null")
987 }
988
989 // Handle primitive types by attempting to deserialize V and wrapping it
990 fn visit_bool<Er>(self, v: bool) -> Result<Self::Value, Er>
991 where
992 Er: de::Error,
993 {
994 V::deserialize(de::value::BoolDeserializer::new(v)).map(|value| Element {
995 id: None,
996 extension: None,
997 value: Some(value),
998 })
999 }
1000 fn visit_i64<Er>(self, v: i64) -> Result<Self::Value, Er>
1001 where
1002 Er: de::Error,
1003 {
1004 V::deserialize(de::value::I64Deserializer::new(v)).map(|value| Element {
1005 id: None,
1006 extension: None,
1007 value: Some(value),
1008 })
1009 }
1010 fn visit_u64<Er>(self, v: u64) -> Result<Self::Value, Er>
1011 where
1012 Er: de::Error,
1013 {
1014 V::deserialize(de::value::U64Deserializer::new(v)).map(|value| Element {
1015 id: None,
1016 extension: None,
1017 value: Some(value),
1018 })
1019 }
1020 fn visit_f64<Er>(self, v: f64) -> Result<Self::Value, Er>
1021 where
1022 Er: de::Error,
1023 {
1024 V::deserialize(de::value::F64Deserializer::new(v)).map(|value| Element {
1025 id: None,
1026 extension: None,
1027 value: Some(value),
1028 })
1029 }
1030 fn visit_str<Er>(self, v: &str) -> Result<Self::Value, Er>
1031 where
1032 Er: de::Error,
1033 {
1034 use std::any::TypeId;
1035
1036 // Try to handle numeric strings for integer types
1037 if TypeId::of::<V>() == TypeId::of::<i64>() {
1038 if let Ok(int_val) = v.parse::<i64>() {
1039 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
1040 |value| Element {
1041 id: None,
1042 extension: None,
1043 value: Some(value),
1044 },
1045 );
1046 }
1047 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
1048 if let Ok(int_val) = v.parse::<i32>() {
1049 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
1050 |value| Element {
1051 id: None,
1052 extension: None,
1053 value: Some(value),
1054 },
1055 );
1056 }
1057 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
1058 if let Ok(int_val) = v.parse::<u64>() {
1059 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
1060 |value| Element {
1061 id: None,
1062 extension: None,
1063 value: Some(value),
1064 },
1065 );
1066 }
1067 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
1068 if let Ok(int_val) = v.parse::<u32>() {
1069 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
1070 |value| Element {
1071 id: None,
1072 extension: None,
1073 value: Some(value),
1074 },
1075 );
1076 }
1077 }
1078
1079 // Fall back to normal string deserialization
1080 V::deserialize(de::value::StrDeserializer::new(v)).map(|value| Element {
1081 id: None,
1082 extension: None,
1083 value: Some(value),
1084 })
1085 }
1086 fn visit_string<Er>(self, v: String) -> Result<Self::Value, Er>
1087 where
1088 Er: de::Error,
1089 {
1090 use std::any::TypeId;
1091
1092 // Try to handle numeric strings for integer types
1093 if TypeId::of::<V>() == TypeId::of::<i64>() {
1094 if let Ok(int_val) = v.parse::<i64>() {
1095 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
1096 |value| Element {
1097 id: None,
1098 extension: None,
1099 value: Some(value),
1100 },
1101 );
1102 }
1103 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
1104 if let Ok(int_val) = v.parse::<i32>() {
1105 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
1106 |value| Element {
1107 id: None,
1108 extension: None,
1109 value: Some(value),
1110 },
1111 );
1112 }
1113 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
1114 if let Ok(int_val) = v.parse::<u64>() {
1115 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
1116 |value| Element {
1117 id: None,
1118 extension: None,
1119 value: Some(value),
1120 },
1121 );
1122 }
1123 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
1124 if let Ok(int_val) = v.parse::<u32>() {
1125 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
1126 |value| Element {
1127 id: None,
1128 extension: None,
1129 value: Some(value),
1130 },
1131 );
1132 }
1133 }
1134
1135 // Fall back to normal string deserialization
1136 V::deserialize(de::value::StringDeserializer::new(v.clone())).map(|value| Element {
1137 // Clone v for error message
1138 id: None,
1139 extension: None,
1140 value: Some(value),
1141 })
1142 }
1143 fn visit_borrowed_str<Er>(self, v: &'de str) -> Result<Self::Value, Er>
1144 where
1145 Er: de::Error,
1146 {
1147 use std::any::TypeId;
1148
1149 // Try to handle numeric strings for integer types
1150 if TypeId::of::<V>() == TypeId::of::<i64>() {
1151 if let Ok(int_val) = v.parse::<i64>() {
1152 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
1153 |value| Element {
1154 id: None,
1155 extension: None,
1156 value: Some(value),
1157 },
1158 );
1159 }
1160 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
1161 if let Ok(int_val) = v.parse::<i32>() {
1162 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
1163 |value| Element {
1164 id: None,
1165 extension: None,
1166 value: Some(value),
1167 },
1168 );
1169 }
1170 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
1171 if let Ok(int_val) = v.parse::<u64>() {
1172 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
1173 |value| Element {
1174 id: None,
1175 extension: None,
1176 value: Some(value),
1177 },
1178 );
1179 }
1180 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
1181 if let Ok(int_val) = v.parse::<u32>() {
1182 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
1183 |value| Element {
1184 id: None,
1185 extension: None,
1186 value: Some(value),
1187 },
1188 );
1189 }
1190 }
1191
1192 // Fall back to normal string deserialization
1193 V::deserialize(de::value::BorrowedStrDeserializer::new(v)).map(|value| Element {
1194 id: None,
1195 extension: None,
1196 value: Some(value),
1197 })
1198 }
1199 fn visit_bytes<Er>(self, v: &[u8]) -> Result<Self::Value, Er>
1200 where
1201 Er: de::Error,
1202 {
1203 V::deserialize(de::value::BytesDeserializer::new(v)).map(|value| Element {
1204 id: None,
1205 extension: None,
1206 value: Some(value),
1207 })
1208 }
1209 fn visit_byte_buf<Er>(self, v: Vec<u8>) -> Result<Self::Value, Er>
1210 where
1211 Er: de::Error,
1212 {
1213 // Use BytesDeserializer with a slice reference &v
1214 V::deserialize(de::value::BytesDeserializer::new(&v)).map(|value| Element {
1215 id: None,
1216 extension: None,
1217 value: Some(value),
1218 })
1219 }
1220
1221 // Handle null
1222 fn visit_none<Er>(self) -> Result<Self::Value, Er>
1223 where
1224 Er: de::Error,
1225 {
1226 Ok(Element {
1227 id: None,
1228 extension: None,
1229 value: None,
1230 })
1231 }
1232 fn visit_unit<Er>(self) -> Result<Self::Value, Er>
1233 where
1234 Er: de::Error,
1235 {
1236 Ok(Element {
1237 id: None,
1238 extension: None,
1239 value: None,
1240 })
1241 }
1242
1243 // Handle Option<T> by visiting Some
1244 fn visit_some<De>(self, deserializer: De) -> Result<Self::Value, De::Error>
1245 where
1246 De: Deserializer<'de>,
1247 {
1248 // Re-dispatch to deserialize_any to handle the inner type correctly
1249 deserializer.deserialize_any(self)
1250 }
1251
1252 // Handle object
1253 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
1254 where
1255 A: MapAccess<'de>,
1256 {
1257 // Deserialize the map using ElementObjectVisitor
1258 // Need to create a deserializer from the map access
1259 let map_deserializer = de::value::MapAccessDeserializer::new(map);
1260 map_deserializer.deserialize_map(ElementObjectVisitor(PhantomData))
1261 }
1262
1263 // We don't expect sequences for a single Element
1264 fn visit_seq<A>(self, _seq: A) -> Result<Self::Value, A::Error>
1265 where
1266 A: de::SeqAccess<'de>,
1267 {
1268 Err(de::Error::invalid_type(de::Unexpected::Seq, &self))
1269 }
1270 }
1271
1272 // Start deserialization using the visitor
1273 deserializer.deserialize_any(AnyValueVisitor(PhantomData))
1274 }
1275}
1276
1277// Custom Serialize for Element<V, E>
1278// Remove PartialEq/Eq bounds for V and E as they are not needed for serialization itself
1279impl<V, E> Serialize for Element<V, E>
1280where
1281 V: Serialize, // Removed PartialEq + Eq
1282 E: Serialize, // Removed PartialEq
1283{
1284 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1285 where
1286 S: Serializer,
1287 {
1288 // If id and extension are None, serialize value directly (or null)
1289 if self.id.is_none() && self.extension.is_none() {
1290 match &self.value {
1291 Some(val) => val.serialize(serializer),
1292 None => serializer.serialize_none(),
1293 }
1294 } else {
1295 // Otherwise, serialize as an object containing id, extension, value if present
1296 let mut len = 0;
1297 if self.id.is_some() {
1298 len += 1;
1299 }
1300 if self.extension.is_some() {
1301 len += 1;
1302 }
1303 if self.value.is_some() {
1304 len += 1;
1305 }
1306
1307 let mut state = serializer.serialize_struct("Element", len)?;
1308 if let Some(id) = &self.id {
1309 state.serialize_field("id", id)?;
1310 }
1311 if let Some(extension) = &self.extension {
1312 state.serialize_field("extension", extension)?;
1313 }
1314 // Restore value serialization for direct Element serialization
1315 if let Some(value) = &self.value {
1316 state.serialize_field("value", value)?;
1317 }
1318 state.end()
1319 }
1320 }
1321}
1322
1323/// Specialized element container for FHIR decimal values with precision preservation.
1324///
1325/// This type combines the generic `Element` pattern with `PreciseDecimal` to provide
1326/// a complete solution for FHIR decimal elements that require both extension support
1327/// and precision preservation during serialization round-trips.
1328///
1329/// # Type Parameters
1330///
1331/// * `E` - The extension type (typically the generated `Extension` struct)
1332///
1333/// # FHIR Decimal Requirements
1334///
1335/// FHIR decimal elements must:
1336/// - Preserve original string precision (e.g., "12.30" vs "12.3")
1337/// - Support mathematical operations using `Decimal` arithmetic
1338/// - Handle extension metadata through `id` and `extension` fields
1339/// - Serialize back to the exact original format when possible
1340///
1341/// # Examples
1342///
1343/// ```rust
1344/// use helios_fhir::{DecimalElement, PreciseDecimal, r4::Extension};
1345/// use rust_decimal::Decimal;
1346///
1347/// // Create from a Decimal value
1348/// let decimal_elem = DecimalElement::<Extension>::new(Decimal::new(1234, 2)); // 12.34
1349///
1350/// // Create with extensions
1351/// let extended_decimal: DecimalElement<Extension> = DecimalElement {
1352/// value: Some(PreciseDecimal::from_parts(
1353/// Some(Decimal::new(12300, 3)),
1354/// "12.300".to_string()
1355/// )),
1356/// id: Some("precision-example".to_string()),
1357/// extension: Some(vec![/* extensions */]),
1358/// };
1359///
1360/// // Access the mathematical value
1361/// if let Some(precise) = &extended_decimal.value {
1362/// if let Some(decimal_val) = precise.value() {
1363/// println!("Mathematical value: {}", decimal_val);
1364/// }
1365/// println!("Original format: {}", precise.original_string());
1366/// }
1367/// ```
1368///
1369/// # Serialization Behavior
1370///
1371/// - **Value only**: Serializes as a JSON number preserving original precision
1372/// - **With extensions**: Serializes as an object with `value`, `id`, and `extension` fields
1373/// - **No value**: Serializes as an object with just the extension fields, or `null` if empty
1374///
1375/// # Integration with FHIRPath
1376///
1377/// When used with FHIRPath evaluation, `DecimalElement` returns:
1378/// - The `Decimal` value for mathematical operations
1379/// - An object representation when extension metadata is accessed
1380/// - Empty collection when the element has no value or extensions
1381#[derive(Debug, PartialEq, Eq, Clone, Default)]
1382pub struct DecimalElement<E> {
1383 /// Optional element identifier for referencing within the resource
1384 pub id: Option<String>,
1385 /// Optional extensions providing additional metadata
1386 pub extension: Option<Vec<E>>,
1387 /// The decimal value with precision preservation
1388 pub value: Option<PreciseDecimal>,
1389}
1390
1391impl<E> DecimalElement<E> {
1392 /// Creates a new `DecimalElement` with the specified decimal value.
1393 ///
1394 /// This constructor creates a simple decimal element with no extensions or ID,
1395 /// containing only the decimal value. The original string representation is
1396 /// automatically derived from the `Decimal` value's `Display` implementation.
1397 ///
1398 /// # Arguments
1399 ///
1400 /// * `value` - The `Decimal` value to store
1401 ///
1402 /// # Returns
1403 ///
1404 /// A new `DecimalElement` with the value set and `id`/`extension` as `None`.
1405 ///
1406 /// # Examples
1407 ///
1408 /// ```rust
1409 /// use helios_fhir::{DecimalElement, r4::Extension};
1410 /// use rust_decimal::Decimal;
1411 ///
1412 /// // Create a simple decimal element
1413 /// let element = DecimalElement::<Extension>::new(Decimal::new(12345, 3)); // 12.345
1414 ///
1415 /// // Verify the structure
1416 /// assert!(element.id.is_none());
1417 /// assert!(element.extension.is_none());
1418 /// assert!(element.value.is_some());
1419 ///
1420 /// // Access the decimal value
1421 /// if let Some(precise_decimal) = &element.value {
1422 /// assert_eq!(precise_decimal.value(), Some(Decimal::new(12345, 3)));
1423 /// assert_eq!(precise_decimal.original_string(), "12.345");
1424 /// }
1425 /// ```
1426 ///
1427 /// # Usage in FHIR Resources
1428 ///
1429 /// This method is typically used when creating FHIR elements programmatically:
1430 ///
1431 /// ```rust
1432 /// use helios_fhir::{DecimalElement, r4::{Extension, Observation}};
1433 /// use rust_decimal::Decimal;
1434 ///
1435 /// let temperature = DecimalElement::<Extension>::new(Decimal::new(3672, 2)); // 36.72
1436 ///
1437 /// // Would be used in an Observation like:
1438 /// // observation.value_quantity.value = Some(temperature);
1439 /// ```
1440 pub fn new(value: Decimal) -> Self {
1441 // Convert the Decimal to PreciseDecimal, which automatically handles
1442 // storing the original string representation via the From trait
1443 let precise_value = PreciseDecimal::from(value);
1444 Self {
1445 id: None,
1446 extension: None,
1447 value: Some(precise_value),
1448 }
1449 }
1450}
1451
1452// Custom Deserialize for DecimalElement<E> using intermediate Value
1453impl<'de, E> Deserialize<'de> for DecimalElement<E>
1454where
1455 E: Deserialize<'de> + Default,
1456{
1457 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1458 where
1459 D: Deserializer<'de>,
1460 {
1461 // Deserialize into an intermediate serde_json::Value first
1462 let json_value = serde_json::Value::deserialize(deserializer)?;
1463
1464 match json_value {
1465 // Handle primitive JSON Number
1466 serde_json::Value::Number(n) => {
1467 // Directly parse the number string to create PreciseDecimal
1468 let s = n.to_string(); // Note: n.to_string() might normalize exponent case (e.g., 'E' -> 'e')
1469 // Replace 'E' with 'e' for parsing
1470 let s_for_parsing = s.replace('E', "e");
1471 // Use from_scientific if 'e' is present, otherwise parse
1472 let parsed_value = if s_for_parsing.contains('e') {
1473 Decimal::from_scientific(&s_for_parsing).ok()
1474 } else {
1475 s_for_parsing.parse::<Decimal>().ok()
1476 };
1477 // Store the ORIGINAL string `s` (as returned by n.to_string()).
1478 let pd = PreciseDecimal::from_parts(parsed_value, s);
1479 Ok(DecimalElement {
1480 id: None,
1481 extension: None,
1482 value: Some(pd),
1483 })
1484 }
1485 // Handle primitive JSON String
1486 serde_json::Value::String(s) => {
1487 // Directly parse the string to create PreciseDecimal
1488 // Replace 'E' with 'e' for parsing
1489 let s_for_parsing = s.replace('E', "e");
1490 // Use from_scientific if 'e' is present, otherwise parse
1491 let parsed_value = if s_for_parsing.contains('e') {
1492 Decimal::from_scientific(&s_for_parsing).ok()
1493 } else {
1494 s_for_parsing.parse::<Decimal>().ok()
1495 };
1496 // Store the ORIGINAL string `s`.
1497 let pd = PreciseDecimal::from_parts(parsed_value, s); // s is owned, no clone needed
1498 Ok(DecimalElement {
1499 id: None,
1500 extension: None,
1501 value: Some(pd),
1502 })
1503 }
1504 // Handle JSON object: deserialize fields individually
1505 serde_json::Value::Object(map) => {
1506 let mut id: Option<String> = None;
1507 let mut extension: Option<Vec<E>> = None;
1508 let mut value: Option<PreciseDecimal> = None;
1509
1510 for (k, v) in map {
1511 match k.as_str() {
1512 "id" => {
1513 if id.is_some() {
1514 return Err(de::Error::duplicate_field("id"));
1515 }
1516 // Deserialize id directly from its Value
1517 id = Deserialize::deserialize(v).map_err(de::Error::custom)?;
1518 }
1519 "extension" => {
1520 if extension.is_some() {
1521 return Err(de::Error::duplicate_field("extension"));
1522 }
1523 // Deserialize extension directly from its Value
1524 extension = Deserialize::deserialize(v).map_err(de::Error::custom)?;
1525 }
1526 "value" => {
1527 if value.is_some() {
1528 return Err(de::Error::duplicate_field("value"));
1529 }
1530 // Deserialize value using PreciseDecimal::deserialize from its Value
1531 // Handle null explicitly within the value field
1532 if v.is_null() {
1533 value = None;
1534 } else {
1535 value = Some(
1536 PreciseDecimal::deserialize(v).map_err(de::Error::custom)?,
1537 );
1538 }
1539 }
1540 // Ignore any unknown fields encountered
1541 _ => {} // Simply ignore unknown fields
1542 }
1543 }
1544 Ok(DecimalElement {
1545 id,
1546 extension,
1547 value,
1548 })
1549 }
1550 // Handle JSON Null for the whole element
1551 serde_json::Value::Null => Ok(DecimalElement::default()), // Default has value: None
1552 // Handle other unexpected types
1553 other => Err(de::Error::invalid_type(
1554 match other {
1555 serde_json::Value::Bool(b) => de::Unexpected::Bool(b),
1556 serde_json::Value::Array(_) => de::Unexpected::Seq,
1557 _ => de::Unexpected::Other("unexpected JSON type for DecimalElement"),
1558 },
1559 &"a decimal number, string, object, or null",
1560 )),
1561 }
1562 }
1563}
1564
1565// Reinstate custom Serialize implementation for DecimalElement
1566// Remove PartialEq bound for E
1567impl<E> Serialize for DecimalElement<E>
1568where
1569 E: Serialize, // Removed PartialEq bound for E
1570{
1571 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1572 where
1573 S: Serializer,
1574 {
1575 // If we only have a value and no other fields, serialize just the value
1576 if self.id.is_none() && self.extension.is_none() {
1577 if let Some(value) = &self.value {
1578 // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
1579 return value.serialize(serializer);
1580 } else {
1581 // If value is also None, serialize as null
1582 // based on updated test_serialize_decimal_with_no_fields
1583 return serializer.serialize_none();
1584 }
1585 }
1586
1587 // Otherwise, serialize as a struct with all present fields
1588 // Calculate the number of fields that are NOT None
1589 let mut len = 0;
1590 if self.id.is_some() {
1591 len += 1;
1592 }
1593 if self.extension.is_some() {
1594 len += 1;
1595 }
1596 if self.value.is_some() {
1597 len += 1;
1598 }
1599
1600 // Start serializing a struct with the calculated length
1601 let mut state = serializer.serialize_struct("DecimalElement", len)?;
1602
1603 // Serialize 'id' field if it's Some
1604 if let Some(id) = &self.id {
1605 state.serialize_field("id", id)?;
1606 }
1607
1608 // Serialize 'extension' field if it's Some
1609 if let Some(extension) = &self.extension {
1610 state.serialize_field("extension", extension)?;
1611 }
1612
1613 // Serialize 'value' field if it's Some
1614 if let Some(value) = &self.value {
1615 // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
1616 state.serialize_field("value", value)?;
1617 }
1618
1619 // End the struct serialization
1620 state.end()
1621 }
1622}
1623
1624// For Element<V, E> - Returns Object with id, extension, value if present
1625impl<V, E> IntoEvaluationResult for Element<V, E>
1626where
1627 V: IntoEvaluationResult + Clone + 'static,
1628 E: IntoEvaluationResult + Clone,
1629{
1630 fn to_evaluation_result(&self) -> EvaluationResult {
1631 use std::any::TypeId;
1632
1633 // Prioritize returning the primitive value if it exists
1634 if let Some(v) = &self.value {
1635 let result = v.to_evaluation_result();
1636 // For primitive values, we need to preserve FHIR type information
1637 return match result {
1638 EvaluationResult::Boolean(b, _) => {
1639 // Return FHIR boolean
1640 EvaluationResult::fhir_boolean(b)
1641 }
1642 EvaluationResult::Integer(i, _) => {
1643 // Return FHIR integer
1644 EvaluationResult::fhir_integer(i)
1645 }
1646 #[cfg(not(any(feature = "R4", feature = "R4B")))]
1647 EvaluationResult::Integer64(i, _) => {
1648 // Return FHIR integer64 (R5 and above)
1649 EvaluationResult::fhir_integer64(i)
1650 }
1651 EvaluationResult::String(s, _) => {
1652 // Determine the FHIR type name based on V's type
1653 let fhir_type_name = if TypeId::of::<V>() == TypeId::of::<String>() {
1654 // For strings, we need more context to determine the exact FHIR type
1655 // Default to "string" but this could be date, dateTime, etc.
1656 "string"
1657 } else {
1658 // Default fallback
1659 "string"
1660 };
1661 EvaluationResult::fhir_string(s, fhir_type_name)
1662 }
1663 _ => result, // For other types, return as-is
1664 };
1665 } else if self.id.is_some() || self.extension.is_some() {
1666 // If value is None, but id or extension exist, return an Object with those
1667 let mut map = std::collections::HashMap::new();
1668 if let Some(id) = &self.id {
1669 map.insert("id".to_string(), EvaluationResult::string(id.clone()));
1670 }
1671 if let Some(ext) = &self.extension {
1672 let ext_collection: Vec<EvaluationResult> =
1673 ext.iter().map(|e| e.to_evaluation_result()).collect();
1674 if !ext_collection.is_empty() {
1675 map.insert(
1676 "extension".to_string(),
1677 EvaluationResult::collection(ext_collection),
1678 );
1679 }
1680 }
1681 // Only return Object if map is not empty (i.e., id or extension was actually present)
1682 if !map.is_empty() {
1683 return EvaluationResult::typed_object(map, "FHIR", "Element");
1684 }
1685 }
1686
1687 // If value, id, and extension are all None, return Empty
1688 EvaluationResult::Empty
1689 }
1690}
1691
1692// For DecimalElement<E> - Returns Decimal value if present, otherwise handles id/extension
1693impl<E> IntoEvaluationResult for DecimalElement<E>
1694where
1695 E: IntoEvaluationResult + Clone,
1696{
1697 fn to_evaluation_result(&self) -> EvaluationResult {
1698 // Prioritize returning the primitive decimal value if it exists
1699 if let Some(precise_decimal) = &self.value {
1700 if let Some(decimal_val) = precise_decimal.value() {
1701 // Return FHIR decimal
1702 return EvaluationResult::fhir_decimal(decimal_val);
1703 }
1704 // If PreciseDecimal holds None for value, fall through to check id/extension
1705 }
1706
1707 // If value is None, but id or extension exist, return an Object with those
1708 if self.id.is_some() || self.extension.is_some() {
1709 let mut map = std::collections::HashMap::new();
1710 if let Some(id) = &self.id {
1711 map.insert("id".to_string(), EvaluationResult::string(id.clone()));
1712 }
1713 if let Some(ext) = &self.extension {
1714 let ext_collection: Vec<EvaluationResult> =
1715 ext.iter().map(|e| e.to_evaluation_result()).collect();
1716 if !ext_collection.is_empty() {
1717 map.insert(
1718 "extension".to_string(),
1719 EvaluationResult::collection(ext_collection),
1720 );
1721 }
1722 }
1723 // Only return Object if map is not empty
1724 if !map.is_empty() {
1725 return EvaluationResult::typed_object(map, "FHIR", "decimal");
1726 }
1727 }
1728
1729 // If value, id, and extension are all None, return Empty
1730 EvaluationResult::Empty
1731 }
1732}
1733
1734// Implement the trait for the top-level enum
1735impl IntoEvaluationResult for FhirResource {
1736 fn to_evaluation_result(&self) -> EvaluationResult {
1737 match self {
1738 #[cfg(feature = "R4")]
1739 FhirResource::R4(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4::Resource>
1740 #[cfg(feature = "R4B")]
1741 FhirResource::R4B(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4b::Resource>
1742 #[cfg(feature = "R5")]
1743 FhirResource::R5(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r5::Resource>
1744 #[cfg(feature = "R6")]
1745 FhirResource::R6(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r6::Resource>
1746 // Note: If no features are enabled, this match might be empty or non-exhaustive.
1747 // This is generally okay as the enum itself wouldn't be usable.
1748 }
1749 }
1750}
1751
1752#[cfg(test)]
1753mod tests {
1754 use super::*;
1755
1756 #[test]
1757 fn test_integer_string_deserialization() {
1758 // Test deserializing a string "2" into Element<i64, ()>
1759 type TestElement = Element<i64, ()>;
1760
1761 // Test case 1: String containing integer
1762 let json_str = r#""2""#;
1763 let result: Result<TestElement, _> = serde_json::from_str(json_str);
1764 assert!(
1765 result.is_ok(),
1766 "Failed to deserialize string '2' as i64: {:?}",
1767 result.err()
1768 );
1769
1770 let element = result.unwrap();
1771 assert_eq!(element.value, Some(2i64));
1772 assert_eq!(element.id, None);
1773 assert_eq!(element.extension, None);
1774
1775 // Test case 2: Number
1776 let json_num = r#"2"#;
1777 let result: Result<TestElement, _> = serde_json::from_str(json_num);
1778 assert!(
1779 result.is_ok(),
1780 "Failed to deserialize number 2 as i64: {:?}",
1781 result.err()
1782 );
1783
1784 let element = result.unwrap();
1785 assert_eq!(element.value, Some(2i64));
1786 }
1787
1788 #[test]
1789 fn test_i32_string_deserialization() {
1790 type TestElement = Element<i32, ()>;
1791
1792 let json_str = r#""123""#;
1793 let result: Result<TestElement, _> = serde_json::from_str(json_str);
1794 assert!(result.is_ok());
1795
1796 let element = result.unwrap();
1797 assert_eq!(element.value, Some(123i32));
1798 }
1799
1800 #[test]
1801 fn test_invalid_string_fallback() {
1802 type TestElement = Element<i64, ()>;
1803
1804 // Non-numeric string should fail for integer type
1805 let json_str = r#""not_a_number""#;
1806 let result: Result<TestElement, _> = serde_json::from_str(json_str);
1807 assert!(
1808 result.is_err(),
1809 "Should fail to deserialize non-numeric string as i64"
1810 );
1811 }
1812}