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// --- Internal Visitor for Element Object Deserialization ---
824
825/// Internal visitor struct for deserializing Element objects from JSON maps.
826///
827/// This visitor handles the complex deserialization logic for Element<V, E> when
828/// the JSON input is an object containing id, extension, and value fields.
829struct ElementObjectVisitor<V, E>(PhantomData<(V, E)>);
830
831impl<'de, V, E> Visitor<'de> for ElementObjectVisitor<V, E>
832where
833 V: Deserialize<'de>,
834 E: Deserialize<'de>,
835{
836 type Value = Element<V, E>;
837
838 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
839 formatter.write_str("an Element object")
840 }
841
842 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
843 where
844 A: MapAccess<'de>,
845 {
846 let mut id: Option<String> = None;
847 let mut extension: Option<Vec<E>> = None;
848 let mut value: Option<V> = None;
849
850 // Manually deserialize fields from the map
851 while let Some(key) = map.next_key::<String>()? {
852 match key.as_str() {
853 "id" => {
854 if id.is_some() {
855 return Err(de::Error::duplicate_field("id"));
856 }
857 id = Some(map.next_value()?);
858 }
859 "extension" => {
860 if extension.is_some() {
861 return Err(de::Error::duplicate_field("extension"));
862 }
863 extension = Some(map.next_value()?);
864 }
865 "value" => {
866 if value.is_some() {
867 return Err(de::Error::duplicate_field("value"));
868 }
869 // Deserialize directly into Option<V>
870 value = Some(map.next_value()?);
871 }
872 // Ignore any unknown fields encountered
873 _ => {
874 let _ = map.next_value::<de::IgnoredAny>()?;
875 }
876 }
877 }
878
879 Ok(Element {
880 id,
881 extension,
882 value,
883 })
884 }
885}
886
887/// Generic element container supporting FHIR's extension mechanism.
888///
889/// In FHIR, most primitive elements can be extended with additional metadata
890/// through the `id` and `extension` fields. This container type provides
891/// the infrastructure to support this pattern across all FHIR data types.
892///
893/// # Type Parameters
894///
895/// * `V` - The value type (e.g., `String`, `i32`, `PreciseDecimal`)
896/// * `E` - The extension type (typically the generated `Extension` struct)
897///
898/// # FHIR Element Structure
899///
900/// FHIR elements can appear in three forms:
901/// 1. **Primitive value**: Just the value itself (e.g., `"text"`, `42`)
902/// 2. **Extended primitive**: An object with `value`, `id`, and/or `extension` fields
903/// 3. **Extension-only**: An object with just `id` and/or `extension` (no value)
904///
905/// # Examples
906///
907/// ```rust
908/// use helios_fhir::{Element, r4::Extension};
909///
910/// // Simple primitive value
911/// let simple: Element<String, Extension> = Element {
912/// value: Some("Hello World".to_string()),
913/// id: None,
914/// extension: None,
915/// };
916///
917/// // Extended primitive with ID
918/// let with_id: Element<String, Extension> = Element {
919/// value: Some("Hello World".to_string()),
920/// id: Some("text-element-1".to_string()),
921/// extension: None,
922/// };
923///
924/// // Extension-only element (no value)
925/// let extension_only: Element<String, Extension> = Element {
926/// value: None,
927/// id: Some("disabled-element".to_string()),
928/// extension: Some(vec![/* extensions */]),
929/// };
930/// ```
931///
932/// # Serialization Behavior
933///
934/// - If only `value` is present: serializes as the primitive value directly
935/// - If `id` or `extension` are present: serializes as an object with all fields
936/// - If everything is `None`: serializes as `null`
937#[derive(Debug, PartialEq, Eq, Clone, Default)]
938pub struct Element<V, E> {
939 /// Optional element identifier for referencing within the resource
940 pub id: Option<String>,
941 /// Optional extensions providing additional metadata
942 pub extension: Option<Vec<E>>,
943 /// The actual primitive value
944 pub value: Option<V>,
945}
946
947// Custom Deserialize for Element<V, E>
948// Remove PartialEq/Eq bounds for V and E as they are not needed for deserialization itself
949impl<'de, V, E> Deserialize<'de> for Element<V, E>
950where
951 V: Deserialize<'de> + 'static, // Added 'static for TypeId comparisons
952 E: Deserialize<'de>, // Removed PartialEq
953{
954 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
955 where
956 D: Deserializer<'de>,
957 {
958 // Use the AnyValueVisitor approach to handle different JSON input types
959 struct AnyValueVisitor<V, E>(PhantomData<(V, E)>);
960
961 impl<'de, V, E> Visitor<'de> for AnyValueVisitor<V, E>
962 where
963 V: Deserialize<'de> + 'static,
964 E: Deserialize<'de>,
965 {
966 type Value = Element<V, E>;
967
968 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
969 formatter
970 .write_str("a primitive value (string, number, boolean), an object, or null")
971 }
972
973 // Handle primitive types by attempting to deserialize V and wrapping it
974 fn visit_bool<Er>(self, v: bool) -> Result<Self::Value, Er>
975 where
976 Er: de::Error,
977 {
978 V::deserialize(de::value::BoolDeserializer::new(v)).map(|value| Element {
979 id: None,
980 extension: None,
981 value: Some(value),
982 })
983 }
984 fn visit_i64<Er>(self, v: i64) -> Result<Self::Value, Er>
985 where
986 Er: de::Error,
987 {
988 V::deserialize(de::value::I64Deserializer::new(v)).map(|value| Element {
989 id: None,
990 extension: None,
991 value: Some(value),
992 })
993 }
994 fn visit_u64<Er>(self, v: u64) -> Result<Self::Value, Er>
995 where
996 Er: de::Error,
997 {
998 V::deserialize(de::value::U64Deserializer::new(v)).map(|value| Element {
999 id: None,
1000 extension: None,
1001 value: Some(value),
1002 })
1003 }
1004 fn visit_f64<Er>(self, v: f64) -> Result<Self::Value, Er>
1005 where
1006 Er: de::Error,
1007 {
1008 V::deserialize(de::value::F64Deserializer::new(v)).map(|value| Element {
1009 id: None,
1010 extension: None,
1011 value: Some(value),
1012 })
1013 }
1014 fn visit_str<Er>(self, v: &str) -> Result<Self::Value, Er>
1015 where
1016 Er: de::Error,
1017 {
1018 use std::any::TypeId;
1019
1020 // Try to handle numeric strings for integer types
1021 if TypeId::of::<V>() == TypeId::of::<i64>() {
1022 if let Ok(int_val) = v.parse::<i64>() {
1023 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
1024 |value| Element {
1025 id: None,
1026 extension: None,
1027 value: Some(value),
1028 },
1029 );
1030 }
1031 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
1032 if let Ok(int_val) = v.parse::<i32>() {
1033 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
1034 |value| Element {
1035 id: None,
1036 extension: None,
1037 value: Some(value),
1038 },
1039 );
1040 }
1041 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
1042 if let Ok(int_val) = v.parse::<u64>() {
1043 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
1044 |value| Element {
1045 id: None,
1046 extension: None,
1047 value: Some(value),
1048 },
1049 );
1050 }
1051 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
1052 if let Ok(int_val) = v.parse::<u32>() {
1053 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
1054 |value| Element {
1055 id: None,
1056 extension: None,
1057 value: Some(value),
1058 },
1059 );
1060 }
1061 }
1062
1063 // Fall back to normal string deserialization
1064 V::deserialize(de::value::StrDeserializer::new(v)).map(|value| Element {
1065 id: None,
1066 extension: None,
1067 value: Some(value),
1068 })
1069 }
1070 fn visit_string<Er>(self, v: String) -> Result<Self::Value, Er>
1071 where
1072 Er: de::Error,
1073 {
1074 use std::any::TypeId;
1075
1076 // Try to handle numeric strings for integer types
1077 if TypeId::of::<V>() == TypeId::of::<i64>() {
1078 if let Ok(int_val) = v.parse::<i64>() {
1079 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
1080 |value| Element {
1081 id: None,
1082 extension: None,
1083 value: Some(value),
1084 },
1085 );
1086 }
1087 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
1088 if let Ok(int_val) = v.parse::<i32>() {
1089 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
1090 |value| Element {
1091 id: None,
1092 extension: None,
1093 value: Some(value),
1094 },
1095 );
1096 }
1097 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
1098 if let Ok(int_val) = v.parse::<u64>() {
1099 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
1100 |value| Element {
1101 id: None,
1102 extension: None,
1103 value: Some(value),
1104 },
1105 );
1106 }
1107 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
1108 if let Ok(int_val) = v.parse::<u32>() {
1109 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
1110 |value| Element {
1111 id: None,
1112 extension: None,
1113 value: Some(value),
1114 },
1115 );
1116 }
1117 }
1118
1119 // Fall back to normal string deserialization
1120 V::deserialize(de::value::StringDeserializer::new(v.clone())).map(|value| Element {
1121 // Clone v for error message
1122 id: None,
1123 extension: None,
1124 value: Some(value),
1125 })
1126 }
1127 fn visit_borrowed_str<Er>(self, v: &'de str) -> Result<Self::Value, Er>
1128 where
1129 Er: de::Error,
1130 {
1131 use std::any::TypeId;
1132
1133 // Try to handle numeric strings for integer types
1134 if TypeId::of::<V>() == TypeId::of::<i64>() {
1135 if let Ok(int_val) = v.parse::<i64>() {
1136 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
1137 |value| Element {
1138 id: None,
1139 extension: None,
1140 value: Some(value),
1141 },
1142 );
1143 }
1144 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
1145 if let Ok(int_val) = v.parse::<i32>() {
1146 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
1147 |value| Element {
1148 id: None,
1149 extension: None,
1150 value: Some(value),
1151 },
1152 );
1153 }
1154 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
1155 if let Ok(int_val) = v.parse::<u64>() {
1156 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
1157 |value| Element {
1158 id: None,
1159 extension: None,
1160 value: Some(value),
1161 },
1162 );
1163 }
1164 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
1165 if let Ok(int_val) = v.parse::<u32>() {
1166 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
1167 |value| Element {
1168 id: None,
1169 extension: None,
1170 value: Some(value),
1171 },
1172 );
1173 }
1174 }
1175
1176 // Fall back to normal string deserialization
1177 V::deserialize(de::value::BorrowedStrDeserializer::new(v)).map(|value| Element {
1178 id: None,
1179 extension: None,
1180 value: Some(value),
1181 })
1182 }
1183 fn visit_bytes<Er>(self, v: &[u8]) -> Result<Self::Value, Er>
1184 where
1185 Er: de::Error,
1186 {
1187 V::deserialize(de::value::BytesDeserializer::new(v)).map(|value| Element {
1188 id: None,
1189 extension: None,
1190 value: Some(value),
1191 })
1192 }
1193 fn visit_byte_buf<Er>(self, v: Vec<u8>) -> Result<Self::Value, Er>
1194 where
1195 Er: de::Error,
1196 {
1197 // Use BytesDeserializer with a slice reference &v
1198 V::deserialize(de::value::BytesDeserializer::new(&v)).map(|value| Element {
1199 id: None,
1200 extension: None,
1201 value: Some(value),
1202 })
1203 }
1204
1205 // Handle null
1206 fn visit_none<Er>(self) -> Result<Self::Value, Er>
1207 where
1208 Er: de::Error,
1209 {
1210 Ok(Element {
1211 id: None,
1212 extension: None,
1213 value: None,
1214 })
1215 }
1216 fn visit_unit<Er>(self) -> Result<Self::Value, Er>
1217 where
1218 Er: de::Error,
1219 {
1220 Ok(Element {
1221 id: None,
1222 extension: None,
1223 value: None,
1224 })
1225 }
1226
1227 // Handle Option<T> by visiting Some
1228 fn visit_some<De>(self, deserializer: De) -> Result<Self::Value, De::Error>
1229 where
1230 De: Deserializer<'de>,
1231 {
1232 // Re-dispatch to deserialize_any to handle the inner type correctly
1233 deserializer.deserialize_any(self)
1234 }
1235
1236 // Handle object
1237 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
1238 where
1239 A: MapAccess<'de>,
1240 {
1241 // Deserialize the map using ElementObjectVisitor
1242 // Need to create a deserializer from the map access
1243 let map_deserializer = de::value::MapAccessDeserializer::new(map);
1244 map_deserializer.deserialize_map(ElementObjectVisitor(PhantomData))
1245 }
1246
1247 // We don't expect sequences for a single Element
1248 fn visit_seq<A>(self, _seq: A) -> Result<Self::Value, A::Error>
1249 where
1250 A: de::SeqAccess<'de>,
1251 {
1252 Err(de::Error::invalid_type(de::Unexpected::Seq, &self))
1253 }
1254 }
1255
1256 // Start deserialization using the visitor
1257 deserializer.deserialize_any(AnyValueVisitor(PhantomData))
1258 }
1259}
1260
1261// Custom Serialize for Element<V, E>
1262// Remove PartialEq/Eq bounds for V and E as they are not needed for serialization itself
1263impl<V, E> Serialize for Element<V, E>
1264where
1265 V: Serialize, // Removed PartialEq + Eq
1266 E: Serialize, // Removed PartialEq
1267{
1268 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1269 where
1270 S: Serializer,
1271 {
1272 // If id and extension are None, serialize value directly (or null)
1273 if self.id.is_none() && self.extension.is_none() {
1274 match &self.value {
1275 Some(val) => val.serialize(serializer),
1276 None => serializer.serialize_none(),
1277 }
1278 } else {
1279 // Otherwise, serialize as an object containing id, extension, value if present
1280 let mut len = 0;
1281 if self.id.is_some() {
1282 len += 1;
1283 }
1284 if self.extension.is_some() {
1285 len += 1;
1286 }
1287 if self.value.is_some() {
1288 len += 1;
1289 }
1290
1291 let mut state = serializer.serialize_struct("Element", len)?;
1292 if let Some(id) = &self.id {
1293 state.serialize_field("id", id)?;
1294 }
1295 if let Some(extension) = &self.extension {
1296 state.serialize_field("extension", extension)?;
1297 }
1298 // Restore value serialization for direct Element serialization
1299 if let Some(value) = &self.value {
1300 state.serialize_field("value", value)?;
1301 }
1302 state.end()
1303 }
1304 }
1305}
1306
1307/// Specialized element container for FHIR decimal values with precision preservation.
1308///
1309/// This type combines the generic `Element` pattern with `PreciseDecimal` to provide
1310/// a complete solution for FHIR decimal elements that require both extension support
1311/// and precision preservation during serialization round-trips.
1312///
1313/// # Type Parameters
1314///
1315/// * `E` - The extension type (typically the generated `Extension` struct)
1316///
1317/// # FHIR Decimal Requirements
1318///
1319/// FHIR decimal elements must:
1320/// - Preserve original string precision (e.g., "12.30" vs "12.3")
1321/// - Support mathematical operations using `Decimal` arithmetic
1322/// - Handle extension metadata through `id` and `extension` fields
1323/// - Serialize back to the exact original format when possible
1324///
1325/// # Examples
1326///
1327/// ```rust
1328/// use helios_fhir::{DecimalElement, PreciseDecimal, r4::Extension};
1329/// use rust_decimal::Decimal;
1330///
1331/// // Create from a Decimal value
1332/// let decimal_elem = DecimalElement::<Extension>::new(Decimal::new(1234, 2)); // 12.34
1333///
1334/// // Create with extensions
1335/// let extended_decimal: DecimalElement<Extension> = DecimalElement {
1336/// value: Some(PreciseDecimal::from_parts(
1337/// Some(Decimal::new(12300, 3)),
1338/// "12.300".to_string()
1339/// )),
1340/// id: Some("precision-example".to_string()),
1341/// extension: Some(vec![/* extensions */]),
1342/// };
1343///
1344/// // Access the mathematical value
1345/// if let Some(precise) = &extended_decimal.value {
1346/// if let Some(decimal_val) = precise.value() {
1347/// println!("Mathematical value: {}", decimal_val);
1348/// }
1349/// println!("Original format: {}", precise.original_string());
1350/// }
1351/// ```
1352///
1353/// # Serialization Behavior
1354///
1355/// - **Value only**: Serializes as a JSON number preserving original precision
1356/// - **With extensions**: Serializes as an object with `value`, `id`, and `extension` fields
1357/// - **No value**: Serializes as an object with just the extension fields, or `null` if empty
1358///
1359/// # Integration with FHIRPath
1360///
1361/// When used with FHIRPath evaluation, `DecimalElement` returns:
1362/// - The `Decimal` value for mathematical operations
1363/// - An object representation when extension metadata is accessed
1364/// - Empty collection when the element has no value or extensions
1365#[derive(Debug, PartialEq, Eq, Clone, Default)]
1366pub struct DecimalElement<E> {
1367 /// Optional element identifier for referencing within the resource
1368 pub id: Option<String>,
1369 /// Optional extensions providing additional metadata
1370 pub extension: Option<Vec<E>>,
1371 /// The decimal value with precision preservation
1372 pub value: Option<PreciseDecimal>,
1373}
1374
1375impl<E> DecimalElement<E> {
1376 /// Creates a new `DecimalElement` with the specified decimal value.
1377 ///
1378 /// This constructor creates a simple decimal element with no extensions or ID,
1379 /// containing only the decimal value. The original string representation is
1380 /// automatically derived from the `Decimal` value's `Display` implementation.
1381 ///
1382 /// # Arguments
1383 ///
1384 /// * `value` - The `Decimal` value to store
1385 ///
1386 /// # Returns
1387 ///
1388 /// A new `DecimalElement` with the value set and `id`/`extension` as `None`.
1389 ///
1390 /// # Examples
1391 ///
1392 /// ```rust
1393 /// use helios_fhir::{DecimalElement, r4::Extension};
1394 /// use rust_decimal::Decimal;
1395 ///
1396 /// // Create a simple decimal element
1397 /// let element = DecimalElement::<Extension>::new(Decimal::new(12345, 3)); // 12.345
1398 ///
1399 /// // Verify the structure
1400 /// assert!(element.id.is_none());
1401 /// assert!(element.extension.is_none());
1402 /// assert!(element.value.is_some());
1403 ///
1404 /// // Access the decimal value
1405 /// if let Some(precise_decimal) = &element.value {
1406 /// assert_eq!(precise_decimal.value(), Some(Decimal::new(12345, 3)));
1407 /// assert_eq!(precise_decimal.original_string(), "12.345");
1408 /// }
1409 /// ```
1410 ///
1411 /// # Usage in FHIR Resources
1412 ///
1413 /// This method is typically used when creating FHIR elements programmatically:
1414 ///
1415 /// ```rust
1416 /// use helios_fhir::{DecimalElement, r4::{Extension, Observation}};
1417 /// use rust_decimal::Decimal;
1418 ///
1419 /// let temperature = DecimalElement::<Extension>::new(Decimal::new(3672, 2)); // 36.72
1420 ///
1421 /// // Would be used in an Observation like:
1422 /// // observation.value_quantity.value = Some(temperature);
1423 /// ```
1424 pub fn new(value: Decimal) -> Self {
1425 // Convert the Decimal to PreciseDecimal, which automatically handles
1426 // storing the original string representation via the From trait
1427 let precise_value = PreciseDecimal::from(value);
1428 Self {
1429 id: None,
1430 extension: None,
1431 value: Some(precise_value),
1432 }
1433 }
1434}
1435
1436// Custom Deserialize for DecimalElement<E> using intermediate Value
1437impl<'de, E> Deserialize<'de> for DecimalElement<E>
1438where
1439 E: Deserialize<'de> + Default,
1440{
1441 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1442 where
1443 D: Deserializer<'de>,
1444 {
1445 // Deserialize into an intermediate serde_json::Value first
1446 let json_value = serde_json::Value::deserialize(deserializer)?;
1447
1448 match json_value {
1449 // Handle primitive JSON Number
1450 serde_json::Value::Number(n) => {
1451 // Directly parse the number string to create PreciseDecimal
1452 let s = n.to_string(); // Note: n.to_string() might normalize exponent case (e.g., 'E' -> 'e')
1453 // Replace 'E' with 'e' for parsing
1454 let s_for_parsing = s.replace('E', "e");
1455 // Use from_scientific if 'e' is present, otherwise parse
1456 let parsed_value = if s_for_parsing.contains('e') {
1457 Decimal::from_scientific(&s_for_parsing).ok()
1458 } else {
1459 s_for_parsing.parse::<Decimal>().ok()
1460 };
1461 // Store the ORIGINAL string `s` (as returned by n.to_string()).
1462 let pd = PreciseDecimal::from_parts(parsed_value, s);
1463 Ok(DecimalElement {
1464 id: None,
1465 extension: None,
1466 value: Some(pd),
1467 })
1468 }
1469 // Handle primitive JSON String
1470 serde_json::Value::String(s) => {
1471 // Directly parse the string to create PreciseDecimal
1472 // Replace 'E' with 'e' for parsing
1473 let s_for_parsing = s.replace('E', "e");
1474 // Use from_scientific if 'e' is present, otherwise parse
1475 let parsed_value = if s_for_parsing.contains('e') {
1476 Decimal::from_scientific(&s_for_parsing).ok()
1477 } else {
1478 s_for_parsing.parse::<Decimal>().ok()
1479 };
1480 // Store the ORIGINAL string `s`.
1481 let pd = PreciseDecimal::from_parts(parsed_value, s); // s is owned, no clone needed
1482 Ok(DecimalElement {
1483 id: None,
1484 extension: None,
1485 value: Some(pd),
1486 })
1487 }
1488 // Handle JSON object: deserialize fields individually
1489 serde_json::Value::Object(map) => {
1490 let mut id: Option<String> = None;
1491 let mut extension: Option<Vec<E>> = None;
1492 let mut value: Option<PreciseDecimal> = None;
1493
1494 for (k, v) in map {
1495 match k.as_str() {
1496 "id" => {
1497 if id.is_some() {
1498 return Err(de::Error::duplicate_field("id"));
1499 }
1500 // Deserialize id directly from its Value
1501 id = Deserialize::deserialize(v).map_err(de::Error::custom)?;
1502 }
1503 "extension" => {
1504 if extension.is_some() {
1505 return Err(de::Error::duplicate_field("extension"));
1506 }
1507 // Deserialize extension directly from its Value
1508 extension = Deserialize::deserialize(v).map_err(de::Error::custom)?;
1509 }
1510 "value" => {
1511 if value.is_some() {
1512 return Err(de::Error::duplicate_field("value"));
1513 }
1514 // Deserialize value using PreciseDecimal::deserialize from its Value
1515 // Handle null explicitly within the value field
1516 if v.is_null() {
1517 value = None;
1518 } else {
1519 value = Some(
1520 PreciseDecimal::deserialize(v).map_err(de::Error::custom)?,
1521 );
1522 }
1523 }
1524 // Ignore any unknown fields encountered
1525 _ => {} // Simply ignore unknown fields
1526 }
1527 }
1528 Ok(DecimalElement {
1529 id,
1530 extension,
1531 value,
1532 })
1533 }
1534 // Handle JSON Null for the whole element
1535 serde_json::Value::Null => Ok(DecimalElement::default()), // Default has value: None
1536 // Handle other unexpected types
1537 other => Err(de::Error::invalid_type(
1538 match other {
1539 serde_json::Value::Bool(b) => de::Unexpected::Bool(b),
1540 serde_json::Value::Array(_) => de::Unexpected::Seq,
1541 _ => de::Unexpected::Other("unexpected JSON type for DecimalElement"),
1542 },
1543 &"a decimal number, string, object, or null",
1544 )),
1545 }
1546 }
1547}
1548
1549// Reinstate custom Serialize implementation for DecimalElement
1550// Remove PartialEq bound for E
1551impl<E> Serialize for DecimalElement<E>
1552where
1553 E: Serialize, // Removed PartialEq bound for E
1554{
1555 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1556 where
1557 S: Serializer,
1558 {
1559 // If we only have a value and no other fields, serialize just the value
1560 if self.id.is_none() && self.extension.is_none() {
1561 if let Some(value) = &self.value {
1562 // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
1563 return value.serialize(serializer);
1564 } else {
1565 // If value is also None, serialize as null
1566 // based on updated test_serialize_decimal_with_no_fields
1567 return serializer.serialize_none();
1568 }
1569 }
1570
1571 // Otherwise, serialize as a struct with all present fields
1572 // Calculate the number of fields that are NOT None
1573 let mut len = 0;
1574 if self.id.is_some() {
1575 len += 1;
1576 }
1577 if self.extension.is_some() {
1578 len += 1;
1579 }
1580 if self.value.is_some() {
1581 len += 1;
1582 }
1583
1584 // Start serializing a struct with the calculated length
1585 let mut state = serializer.serialize_struct("DecimalElement", len)?;
1586
1587 // Serialize 'id' field if it's Some
1588 if let Some(id) = &self.id {
1589 state.serialize_field("id", id)?;
1590 }
1591
1592 // Serialize 'extension' field if it's Some
1593 if let Some(extension) = &self.extension {
1594 state.serialize_field("extension", extension)?;
1595 }
1596
1597 // Serialize 'value' field if it's Some
1598 if let Some(value) = &self.value {
1599 // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
1600 state.serialize_field("value", value)?;
1601 }
1602
1603 // End the struct serialization
1604 state.end()
1605 }
1606}
1607
1608// For Element<V, E> - Returns Object with id, extension, value if present
1609impl<V, E> IntoEvaluationResult for Element<V, E>
1610where
1611 V: IntoEvaluationResult + Clone + 'static,
1612 E: IntoEvaluationResult + Clone,
1613{
1614 fn to_evaluation_result(&self) -> EvaluationResult {
1615 use std::any::TypeId;
1616
1617 // Prioritize returning the primitive value if it exists
1618 if let Some(v) = &self.value {
1619 let result = v.to_evaluation_result();
1620 // For primitive values, we need to preserve FHIR type information
1621 return match result {
1622 EvaluationResult::Boolean(b, _) => {
1623 // Return FHIR boolean
1624 EvaluationResult::fhir_boolean(b)
1625 }
1626 EvaluationResult::Integer(i, _) => {
1627 // Return FHIR integer
1628 EvaluationResult::fhir_integer(i)
1629 }
1630 #[cfg(not(any(feature = "R4", feature = "R4B")))]
1631 EvaluationResult::Integer64(i, _) => {
1632 // Return FHIR integer64 (R5 and above)
1633 EvaluationResult::fhir_integer64(i)
1634 }
1635 EvaluationResult::String(s, _) => {
1636 // Determine the FHIR type name based on V's type
1637 let fhir_type_name = if TypeId::of::<V>() == TypeId::of::<String>() {
1638 // For strings, we need more context to determine the exact FHIR type
1639 // Default to "string" but this could be date, dateTime, etc.
1640 "string"
1641 } else {
1642 // Default fallback
1643 "string"
1644 };
1645 EvaluationResult::fhir_string(s, fhir_type_name)
1646 }
1647 _ => result, // For other types, return as-is
1648 };
1649 } else if self.id.is_some() || self.extension.is_some() {
1650 // If value is None, but id or extension exist, return an Object with those
1651 let mut map = std::collections::HashMap::new();
1652 if let Some(id) = &self.id {
1653 map.insert("id".to_string(), EvaluationResult::string(id.clone()));
1654 }
1655 if let Some(ext) = &self.extension {
1656 let ext_collection: Vec<EvaluationResult> =
1657 ext.iter().map(|e| e.to_evaluation_result()).collect();
1658 if !ext_collection.is_empty() {
1659 map.insert(
1660 "extension".to_string(),
1661 EvaluationResult::collection(ext_collection),
1662 );
1663 }
1664 }
1665 // Only return Object if map is not empty (i.e., id or extension was actually present)
1666 if !map.is_empty() {
1667 return EvaluationResult::typed_object(map, "FHIR", "Element");
1668 }
1669 }
1670
1671 // If value, id, and extension are all None, return Empty
1672 EvaluationResult::Empty
1673 }
1674}
1675
1676// For DecimalElement<E> - Returns Decimal value if present, otherwise handles id/extension
1677impl<E> IntoEvaluationResult for DecimalElement<E>
1678where
1679 E: IntoEvaluationResult + Clone,
1680{
1681 fn to_evaluation_result(&self) -> EvaluationResult {
1682 // Prioritize returning the primitive decimal value if it exists
1683 if let Some(precise_decimal) = &self.value {
1684 if let Some(decimal_val) = precise_decimal.value() {
1685 // Return FHIR decimal
1686 return EvaluationResult::fhir_decimal(decimal_val);
1687 }
1688 // If PreciseDecimal holds None for value, fall through to check id/extension
1689 }
1690
1691 // If value is None, but id or extension exist, return an Object with those
1692 if self.id.is_some() || self.extension.is_some() {
1693 let mut map = std::collections::HashMap::new();
1694 if let Some(id) = &self.id {
1695 map.insert("id".to_string(), EvaluationResult::string(id.clone()));
1696 }
1697 if let Some(ext) = &self.extension {
1698 let ext_collection: Vec<EvaluationResult> =
1699 ext.iter().map(|e| e.to_evaluation_result()).collect();
1700 if !ext_collection.is_empty() {
1701 map.insert(
1702 "extension".to_string(),
1703 EvaluationResult::collection(ext_collection),
1704 );
1705 }
1706 }
1707 // Only return Object if map is not empty
1708 if !map.is_empty() {
1709 return EvaluationResult::typed_object(map, "FHIR", "decimal");
1710 }
1711 }
1712
1713 // If value, id, and extension are all None, return Empty
1714 EvaluationResult::Empty
1715 }
1716}
1717
1718// Implement the trait for the top-level enum
1719impl IntoEvaluationResult for FhirResource {
1720 fn to_evaluation_result(&self) -> EvaluationResult {
1721 match self {
1722 #[cfg(feature = "R4")]
1723 FhirResource::R4(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4::Resource>
1724 #[cfg(feature = "R4B")]
1725 FhirResource::R4B(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4b::Resource>
1726 #[cfg(feature = "R5")]
1727 FhirResource::R5(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r5::Resource>
1728 #[cfg(feature = "R6")]
1729 FhirResource::R6(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r6::Resource>
1730 // Note: If no features are enabled, this match might be empty or non-exhaustive.
1731 // This is generally okay as the enum itself wouldn't be usable.
1732 }
1733 }
1734}
1735
1736#[cfg(test)]
1737mod tests {
1738 use super::*;
1739
1740 #[test]
1741 fn test_integer_string_deserialization() {
1742 // Test deserializing a string "2" into Element<i64, ()>
1743 type TestElement = Element<i64, ()>;
1744
1745 // Test case 1: String containing integer
1746 let json_str = r#""2""#;
1747 let result: Result<TestElement, _> = serde_json::from_str(json_str);
1748 assert!(
1749 result.is_ok(),
1750 "Failed to deserialize string '2' as i64: {:?}",
1751 result.err()
1752 );
1753
1754 let element = result.unwrap();
1755 assert_eq!(element.value, Some(2i64));
1756 assert_eq!(element.id, None);
1757 assert_eq!(element.extension, None);
1758
1759 // Test case 2: Number
1760 let json_num = r#"2"#;
1761 let result: Result<TestElement, _> = serde_json::from_str(json_num);
1762 assert!(
1763 result.is_ok(),
1764 "Failed to deserialize number 2 as i64: {:?}",
1765 result.err()
1766 );
1767
1768 let element = result.unwrap();
1769 assert_eq!(element.value, Some(2i64));
1770 }
1771
1772 #[test]
1773 fn test_i32_string_deserialization() {
1774 type TestElement = Element<i32, ()>;
1775
1776 let json_str = r#""123""#;
1777 let result: Result<TestElement, _> = serde_json::from_str(json_str);
1778 assert!(result.is_ok());
1779
1780 let element = result.unwrap();
1781 assert_eq!(element.value, Some(123i32));
1782 }
1783
1784 #[test]
1785 fn test_invalid_string_fallback() {
1786 type TestElement = Element<i64, ()>;
1787
1788 // Non-numeric string should fail for integer type
1789 let json_str = r#""not_a_number""#;
1790 let result: Result<TestElement, _> = serde_json::from_str(json_str);
1791 assert!(
1792 result.is_err(),
1793 "Should fail to deserialize non-numeric string as i64"
1794 );
1795 }
1796}