Skip to main content

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 chrono::{DateTime as ChronoDateTime, NaiveDate, NaiveTime, Utc};
45use helios_fhirpath_support::{EvaluationResult, IntoEvaluationResult, TypeInfoResult};
46#[cfg(feature = "xml")]
47use helios_serde_support::SingleOrVec;
48
49use rust_decimal::Decimal;
50use serde::{
51    Deserialize, Serialize,
52    de::{self, Deserializer, MapAccess, Visitor},
53    ser::{SerializeStruct, Serializer},
54};
55use std::cmp::Ordering;
56use std::fmt;
57use std::marker::PhantomData;
58use std::sync::Arc;
59
60/// Custom deserializer that is more forgiving of null values in JSON.
61///
62/// This creates a custom `Option<T>` deserializer that will return None for null values
63/// but also for any deserialization errors. This makes it possible to skip over
64/// malformed or unexpected values in FHIR JSON.
65pub fn deserialize_forgiving_option<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
66where
67    T: Deserialize<'de>,
68    D: Deserializer<'de>,
69{
70    // Use the intermediate Value approach to check for null first
71    let json_value = serde_json::Value::deserialize(deserializer)?;
72
73    match json_value {
74        serde_json::Value::Null => Ok(None),
75        _ => {
76            // Try to deserialize the value, but return None if it fails
77            match T::deserialize(json_value) {
78                Ok(value) => Ok(Some(value)),
79                Err(_) => Ok(None), // Ignore errors and return None
80            }
81        }
82    }
83}
84
85/// High-precision decimal type that preserves original string representation.
86///
87/// FHIR requires that decimal values maintain their original precision and format
88/// when serialized back to JSON. This type stores both the parsed `Decimal` value
89/// for mathematical operations and the original string for serialization.
90///
91/// # FHIR Precision Requirements
92///
93/// FHIR decimal values must:
94/// - Preserve trailing zeros (e.g., "12.340" vs "12.34")
95/// - Maintain original precision during round-trip serialization
96/// - Support high-precision arithmetic without floating-point errors
97/// - Handle edge cases like very large or very small numbers
98///
99/// # Examples
100///
101/// ```rust
102/// use helios_fhir::PreciseDecimal;
103/// use rust_decimal::Decimal;
104///
105/// // Create from Decimal (derives string representation)
106/// let precise = PreciseDecimal::from(Decimal::new(12340, 3)); // 12.340
107/// assert_eq!(precise.original_string(), "12.340");
108///
109/// // Create with specific string format
110/// let precise = PreciseDecimal::from_parts(
111///     Some(Decimal::new(1000, 2)),
112///     "10.00".to_string()
113/// );
114/// assert_eq!(precise.original_string(), "10.00");
115/// ```
116#[derive(Debug, Clone)]
117pub struct PreciseDecimal {
118    /// The parsed decimal value, `None` if parsing failed (e.g., out of range)
119    value: Option<Decimal>,
120    /// The original string representation preserving format and precision
121    original_string: Arc<str>,
122}
123
124/// Implements equality comparison based on the parsed decimal value.
125///
126/// Two `PreciseDecimal` values are equal if their parsed `Decimal` values are equal,
127/// regardless of their original string representations. This enables mathematical
128/// equality while preserving string format for serialization.
129///
130/// # Examples
131///
132/// ```rust
133/// use helios_fhir::PreciseDecimal;
134/// use rust_decimal::Decimal;
135///
136/// let a = PreciseDecimal::from_parts(Some(Decimal::new(100, 1)), "10.0".to_string());
137/// let b = PreciseDecimal::from_parts(Some(Decimal::new(1000, 2)), "10.00".to_string());
138/// assert_eq!(a, b); // Same decimal value (10.0 == 10.00)
139/// ```
140impl PartialEq for PreciseDecimal {
141    fn eq(&self, other: &Self) -> bool {
142        // Compare parsed decimal values for mathematical equality
143        self.value == other.value
144    }
145}
146
147/// Marker trait implementation indicating total equality for `PreciseDecimal`.
148impl Eq for PreciseDecimal {}
149
150/// Implements partial ordering based on the parsed decimal value.
151///
152/// Ordering is based on the mathematical value of the decimal, not the string
153/// representation. `None` values (unparseable decimals) are considered less than
154/// any valid decimal value.
155impl PartialOrd for PreciseDecimal {
156    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
157        Some(self.cmp(other))
158    }
159}
160
161/// Implements total ordering for `PreciseDecimal`.
162///
163/// Provides a consistent ordering for sorting operations. The ordering is based
164/// on the mathematical value: `None` < `Some(smaller_decimal)` < `Some(larger_decimal)`.
165impl Ord for PreciseDecimal {
166    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
167        self.value.cmp(&other.value)
168    }
169}
170
171// === PreciseDecimal Methods ===
172
173impl PreciseDecimal {
174    /// Creates a new `PreciseDecimal` from its constituent parts.
175    ///
176    /// This constructor allows explicit control over both the parsed value and the
177    /// original string representation. Use this when you need to preserve a specific
178    /// string format or when parsing has already been attempted.
179    ///
180    /// # Arguments
181    ///
182    /// * `value` - The parsed decimal value, or `None` if parsing failed
183    /// * `original_string` - The original string representation to preserve
184    ///
185    /// # Examples
186    ///
187    /// ```rust
188    /// use helios_fhir::PreciseDecimal;
189    /// use rust_decimal::Decimal;
190    ///
191    /// // Create with successful parsing
192    /// let precise = PreciseDecimal::from_parts(
193    ///     Some(Decimal::new(12340, 3)),
194    ///     "12.340".to_string()
195    /// );
196    ///
197    /// // Create with failed parsing (preserves original string)
198    /// let invalid = PreciseDecimal::from_parts(
199    ///     None,
200    ///     "invalid_decimal".to_string()
201    /// );
202    /// ```
203    pub fn from_parts(value: Option<Decimal>, original_string: String) -> Self {
204        Self {
205            value,
206            original_string: Arc::from(original_string.as_str()),
207        }
208    }
209
210    /// Helper method to parse a decimal string with support for scientific notation.
211    ///
212    /// This method handles the complexity of parsing decimal strings that may be in
213    /// scientific notation (with 'E' or 'e' exponents) or regular decimal format.
214    /// It normalizes 'E' to 'e' for consistent parsing while preserving the original
215    /// string representation for serialization.
216    ///
217    /// # Arguments
218    ///
219    /// * `s` - The string to parse as a decimal
220    ///
221    /// # Returns
222    ///
223    /// `Some(Decimal)` if parsing succeeds, `None` if the string is not a valid decimal.
224    ///
225    /// # Examples
226    ///
227    /// ```ignore
228    /// use helios_fhir::PreciseDecimal;
229    /// use rust_decimal::Decimal;
230    ///
231    /// // Regular decimal format
232    /// assert!(PreciseDecimal::parse_decimal_string("123.45").is_some());
233    ///
234    /// // Scientific notation with 'e'
235    /// assert!(PreciseDecimal::parse_decimal_string("1.23e2").is_some());
236    ///
237    /// // Scientific notation with 'E' (normalized to 'e')
238    /// assert!(PreciseDecimal::parse_decimal_string("1.23E2").is_some());
239    ///
240    /// // Invalid format
241    /// assert!(PreciseDecimal::parse_decimal_string("invalid").is_none());
242    /// ```
243    fn parse_decimal_string(s: &str) -> Option<Decimal> {
244        // Normalize 'E' to 'e' for consistent parsing
245        let normalized = s.replace('E', "e");
246
247        if normalized.contains('e') {
248            // Use scientific notation parsing
249            Decimal::from_scientific(&normalized).ok()
250        } else {
251            // Use regular decimal parsing
252            normalized.parse::<Decimal>().ok()
253        }
254    }
255
256    /// Returns the parsed decimal value if parsing was successful.
257    ///
258    /// This method provides access to the mathematical value for arithmetic
259    /// operations and comparisons. Returns `None` if the original string
260    /// could not be parsed as a valid decimal.
261    ///
262    /// # Examples
263    ///
264    /// ```rust
265    /// use helios_fhir::PreciseDecimal;
266    /// use rust_decimal::Decimal;
267    ///
268    /// let precise = PreciseDecimal::from(Decimal::new(1234, 2)); // 12.34
269    /// assert_eq!(precise.value(), Some(Decimal::new(1234, 2)));
270    ///
271    /// let invalid = PreciseDecimal::from_parts(None, "invalid".to_string());
272    /// assert_eq!(invalid.value(), None);
273    /// ```
274    pub fn value(&self) -> Option<Decimal> {
275        self.value
276    }
277
278    /// Returns the original string representation.
279    ///
280    /// This method provides access to the exact string format that was used
281    /// to create this `PreciseDecimal`. This string is used during serialization
282    /// to maintain FHIR's precision requirements.
283    ///
284    /// # Examples
285    ///
286    /// ```rust
287    /// use helios_fhir::PreciseDecimal;
288    /// use rust_decimal::Decimal;
289    ///
290    /// let precise = PreciseDecimal::from_parts(
291    ///     Some(Decimal::new(100, 2)),
292    ///     "1.00".to_string()
293    /// );
294    /// assert_eq!(precise.original_string(), "1.00");
295    /// ```
296    pub fn original_string(&self) -> &str {
297        &self.original_string
298    }
299}
300
301/// Converts a `Decimal` to `PreciseDecimal` with derived string representation.
302///
303/// This implementation allows easy conversion from `rust_decimal::Decimal` values
304/// by automatically generating the string representation using the decimal's
305/// `Display` implementation.
306///
307/// # Examples
308///
309/// ```rust
310/// use helios_fhir::PreciseDecimal;
311/// use rust_decimal::Decimal;
312///
313/// let decimal = Decimal::new(12345, 3); // 12.345
314/// let precise: PreciseDecimal = decimal.into();
315/// assert_eq!(precise.value(), Some(decimal));
316/// assert_eq!(precise.original_string(), "12.345");
317/// ```
318impl From<Decimal> for PreciseDecimal {
319    fn from(value: Decimal) -> Self {
320        // Generate string representation from the decimal value
321        let original_string = Arc::from(value.to_string());
322        Self {
323            value: Some(value),
324            original_string,
325        }
326    }
327}
328
329/// Implements serialization for `PreciseDecimal` preserving original format.
330///
331/// This implementation ensures that the exact original string representation
332/// is preserved during JSON serialization, maintaining FHIR's precision
333/// requirements including trailing zeros and specific formatting.
334///
335/// # FHIR Compliance
336///
337/// FHIR requires that decimal values maintain their original precision when
338/// round-tripped through JSON. This implementation uses `serde_json::RawValue`
339/// to serialize the original string directly as a JSON number.
340///
341/// # Examples
342///
343/// ```rust
344/// use helios_fhir::PreciseDecimal;
345/// use rust_decimal::Decimal;
346/// use serde_json;
347///
348/// let precise = PreciseDecimal::from_parts(
349///     Some(Decimal::new(1230, 2)),
350///     "12.30".to_string()
351/// );
352///
353/// let json = serde_json::to_string(&precise).unwrap();
354/// assert_eq!(json, "12.30"); // Preserves trailing zero
355/// ```
356impl Serialize for PreciseDecimal {
357    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
358    where
359        S: Serializer,
360    {
361        // Use RawValue to preserve exact string format in JSON
362        match serde_json::value::RawValue::from_string(self.original_string.to_string()) {
363            Ok(raw_value) => raw_value.serialize(serializer),
364            Err(e) => Err(serde::ser::Error::custom(format!(
365                "Failed to serialize PreciseDecimal '{}': {}",
366                self.original_string, e
367            ))),
368        }
369    }
370}
371
372/// Implements deserialization for `PreciseDecimal` preserving original format.
373///
374/// This implementation deserializes JSON numbers and strings into `PreciseDecimal`
375/// while preserving the exact original string representation. It handles various
376/// JSON formats including scientific notation and nested object structures.
377///
378/// # Supported Formats
379///
380/// - Direct numbers: `12.340`
381/// - String numbers: `"12.340"`
382/// - Scientific notation: `1.234e2` or `1.234E2`
383/// - Nested objects: `{"value": 12.340}` (for macro-generated structures)
384///
385/// # Examples
386///
387/// ```rust
388/// use helios_fhir::PreciseDecimal;
389/// use serde_json;
390///
391/// // Deserialize from JSON number (trailing zeros are normalized)
392/// let precise: PreciseDecimal = serde_json::from_str("12.340").unwrap();
393/// assert_eq!(precise.original_string(), "12.340"); // JSON number format
394///
395/// // Deserialize from JSON string (preserves exact format)
396/// let precise: PreciseDecimal = serde_json::from_str("\"12.340\"").unwrap();
397/// assert_eq!(precise.original_string(), "12.340"); // Preserves string format
398/// ```
399impl<'de> Deserialize<'de> for PreciseDecimal {
400    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
401    where
402        D: Deserializer<'de>,
403    {
404        // Use intermediate Value to capture exact string representation
405        let json_value = serde_json::Value::deserialize(deserializer)?;
406
407        match json_value {
408            serde_json::Value::Number(n) => {
409                // Extract string representation from JSON number
410                let original_string = n.to_string();
411                let parsed_value = Self::parse_decimal_string(&original_string);
412                Ok(PreciseDecimal::from_parts(parsed_value, original_string))
413            }
414            serde_json::Value::String(s) => {
415                // Use string value directly (preserves exact format)
416                let parsed_value = Self::parse_decimal_string(&s);
417                Ok(PreciseDecimal::from_parts(parsed_value, s))
418            }
419            // Handle nested object format (for macro-generated structures)
420            serde_json::Value::Object(map) => match map.get("value") {
421                Some(serde_json::Value::Number(n)) => {
422                    let original_string = n.to_string();
423                    let parsed_value = Self::parse_decimal_string(&original_string);
424                    Ok(PreciseDecimal::from_parts(parsed_value, original_string))
425                }
426                Some(serde_json::Value::String(s)) => {
427                    let original_string = s.clone();
428                    let parsed_value = Self::parse_decimal_string(&original_string);
429                    Ok(PreciseDecimal::from_parts(parsed_value, original_string))
430                }
431                Some(serde_json::Value::Null) => Err(de::Error::invalid_value(
432                    de::Unexpected::Unit,
433                    &"a number or string for decimal value",
434                )),
435                None => Err(de::Error::missing_field("value")),
436                _ => Err(de::Error::invalid_type(
437                    de::Unexpected::Map,
438                    &"a map with a 'value' field containing a number or string",
439                )),
440            },
441            // Handle remaining unexpected types
442            other => Err(de::Error::invalid_type(
443                match other {
444                    serde_json::Value::Null => de::Unexpected::Unit, // Or Unexpected::Option if mapping null to None
445                    serde_json::Value::Bool(b) => de::Unexpected::Bool(b),
446                    serde_json::Value::Array(_) => de::Unexpected::Seq,
447                    _ => de::Unexpected::Other("unexpected JSON type for PreciseDecimal"),
448                },
449                &"a number, string, or object with a 'value' field",
450            )),
451        }
452    }
453}
454
455// --- End PreciseDecimal ---
456
457/// Precision levels for FHIR Date values.
458///
459/// FHIR dates support partial precision, allowing year-only, year-month,
460/// or full date specifications. This enum tracks which components are present.
461#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
462pub enum DatePrecision {
463    /// Year only (YYYY)
464    Year,
465    /// Year and month (YYYY-MM)
466    YearMonth,
467    /// Full date (YYYY-MM-DD)
468    Full,
469}
470
471/// Precision levels for FHIR Time values.
472///
473/// FHIR times support partial precision from hour-only through
474/// sub-second precision. This enum tracks which components are present.
475#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
476pub enum TimePrecision {
477    /// Hour only (HH)
478    Hour,
479    /// Hour and minute (HH:MM)
480    HourMinute,
481    /// Hour, minute, and second (HH:MM:SS)
482    HourMinuteSecond,
483    /// Full time with sub-second precision (HH:MM:SS.sss)
484    Millisecond,
485}
486
487/// Precision levels for FHIR DateTime values.
488///
489/// FHIR datetimes support partial precision from year-only through
490/// sub-second precision with optional timezone information.
491#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
492pub enum DateTimePrecision {
493    /// Year only (YYYY)
494    Year,
495    /// Year and month (YYYY-MM)
496    YearMonth,
497    /// Date only (YYYY-MM-DD)
498    Date,
499    /// Date with hour (YYYY-MM-DDTHH)
500    DateHour,
501    /// Date with hour and minute (YYYY-MM-DDTHH:MM)
502    DateHourMinute,
503    /// Date with time to seconds (YYYY-MM-DDTHH:MM:SS)
504    DateHourMinuteSecond,
505    /// Full datetime with sub-second precision (YYYY-MM-DDTHH:MM:SS.sss)
506    Full,
507}
508
509impl Default for PrecisionDate {
510    fn default() -> Self {
511        // Default to epoch date 1970-01-01
512        Self::from_ymd(1970, 1, 1)
513    }
514}
515
516/// Precision-aware FHIR Date type.
517///
518/// This type preserves the original precision and string representation
519/// of FHIR date values while providing typed access to date components.
520///
521/// # FHIR Date Formats
522/// - `YYYY` - Year only
523/// - `YYYY-MM` - Year and month  
524/// - `YYYY-MM-DD` - Full date
525///
526/// # Examples
527/// ```rust
528/// use helios_fhir::{PrecisionDate, DatePrecision};
529///
530/// // Create a year-only date
531/// let year_date = PrecisionDate::from_year(2023);
532/// assert_eq!(year_date.precision(), DatePrecision::Year);
533/// assert_eq!(year_date.original_string(), "2023");
534///
535/// // Create a full date
536/// let full_date = PrecisionDate::from_ymd(2023, 3, 15);
537/// assert_eq!(full_date.precision(), DatePrecision::Full);
538/// assert_eq!(full_date.original_string(), "2023-03-15");
539/// ```
540#[derive(Debug, Clone, PartialEq, Eq)]
541pub struct PrecisionDate {
542    /// Year component (always present)
543    year: i32,
544    /// Month component (1-12, None for year-only precision)
545    month: Option<u32>,
546    /// Day component (1-31, None for year or year-month precision)
547    day: Option<u32>,
548    /// Precision level of this date
549    precision: DatePrecision,
550    /// Original string representation
551    original_string: Arc<str>,
552}
553
554impl PrecisionDate {
555    /// Creates a year-only precision date.
556    pub fn from_year(year: i32) -> Self {
557        Self {
558            year,
559            month: None,
560            day: None,
561            precision: DatePrecision::Year,
562            original_string: Arc::from(format!("{:04}", year)),
563        }
564    }
565
566    /// Creates a year-month precision date.
567    pub fn from_year_month(year: i32, month: u32) -> Self {
568        Self {
569            year,
570            month: Some(month),
571            day: None,
572            precision: DatePrecision::YearMonth,
573            original_string: Arc::from(format!("{:04}-{:02}", year, month)),
574        }
575    }
576
577    /// Creates a full precision date.
578    pub fn from_ymd(year: i32, month: u32, day: u32) -> Self {
579        Self {
580            year,
581            month: Some(month),
582            day: Some(day),
583            precision: DatePrecision::Full,
584            original_string: Arc::from(format!("{:04}-{:02}-{:02}", year, month, day)),
585        }
586    }
587
588    /// Parses a FHIR date string, preserving precision.
589    pub fn parse(s: &str) -> Option<Self> {
590        // Remove @ prefix if present
591        let s = s.strip_prefix('@').unwrap_or(s);
592
593        let parts: Vec<&str> = s.split('-').collect();
594        match parts.len() {
595            1 => {
596                // Year only
597                let year = parts[0].parse::<i32>().ok()?;
598                Some(Self {
599                    year,
600                    month: None,
601                    day: None,
602                    precision: DatePrecision::Year,
603                    original_string: Arc::from(s),
604                })
605            }
606            2 => {
607                // Year-month
608                let year = parts[0].parse::<i32>().ok()?;
609                let month = parts[1].parse::<u32>().ok()?;
610                if !(1..=12).contains(&month) {
611                    return None;
612                }
613                Some(Self {
614                    year,
615                    month: Some(month),
616                    day: None,
617                    precision: DatePrecision::YearMonth,
618                    original_string: Arc::from(s),
619                })
620            }
621            3 => {
622                // Full date
623                let year = parts[0].parse::<i32>().ok()?;
624                let month = parts[1].parse::<u32>().ok()?;
625                let day = parts[2].parse::<u32>().ok()?;
626                if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
627                    return None;
628                }
629                Some(Self {
630                    year,
631                    month: Some(month),
632                    day: Some(day),
633                    precision: DatePrecision::Full,
634                    original_string: Arc::from(s),
635                })
636            }
637            _ => None,
638        }
639    }
640
641    /// Returns the precision level of this date.
642    pub fn precision(&self) -> DatePrecision {
643        self.precision
644    }
645
646    /// Returns the original string representation.
647    pub fn original_string(&self) -> &str {
648        &self.original_string
649    }
650
651    /// Returns the year component.
652    pub fn year(&self) -> i32 {
653        self.year
654    }
655
656    /// Returns the month component if present.
657    pub fn month(&self) -> Option<u32> {
658        self.month
659    }
660
661    /// Returns the day component if present.
662    pub fn day(&self) -> Option<u32> {
663        self.day
664    }
665
666    /// Converts to a NaiveDate, using defaults for missing components.
667    pub fn to_naive_date(&self) -> NaiveDate {
668        NaiveDate::from_ymd_opt(self.year, self.month.unwrap_or(1), self.day.unwrap_or(1))
669            .expect("Valid date components")
670    }
671
672    /// Compares two dates considering precision.
673    /// Returns None if comparison is indeterminate due to precision differences.
674    pub fn compare(&self, other: &Self) -> Option<Ordering> {
675        // Compare years first
676        match self.year.cmp(&other.year) {
677            Ordering::Equal => {
678                // Years are equal, check month precision
679                match (self.month, other.month) {
680                    (None, None) => Some(Ordering::Equal),
681                    (None, Some(_)) | (Some(_), None) => {
682                        // Different precisions - comparison may be indeterminate
683                        // For < and > we can still determine, but for = it's indeterminate
684                        None
685                    }
686                    (Some(m1), Some(m2)) => match m1.cmp(&m2) {
687                        Ordering::Equal => {
688                            // Months are equal, check day precision
689                            match (self.day, other.day) {
690                                (None, None) => Some(Ordering::Equal),
691                                (None, Some(_)) | (Some(_), None) => {
692                                    // Different precisions - indeterminate
693                                    None
694                                }
695                                (Some(d1), Some(d2)) => Some(d1.cmp(&d2)),
696                            }
697                        }
698                        other => Some(other),
699                    },
700                }
701            }
702            other => Some(other),
703        }
704    }
705}
706
707impl Default for PrecisionTime {
708    fn default() -> Self {
709        // Default to midnight 00:00:00
710        Self::from_hms(0, 0, 0)
711    }
712}
713
714/// Precision-aware FHIR Time type.
715///
716/// This type preserves the original precision and string representation
717/// of FHIR time values. Note that FHIR times do not support timezone information.
718///
719/// # FHIR Time Formats
720/// - `HH` - Hour only
721/// - `HH:MM` - Hour and minute
722/// - `HH:MM:SS` - Hour, minute, and second
723/// - `HH:MM:SS.sss` - Full time with milliseconds
724///
725/// # Examples
726/// ```rust
727/// use helios_fhir::{PrecisionTime, TimePrecision};
728///
729/// // Create an hour-only time
730/// let hour_time = PrecisionTime::from_hour(14);
731/// assert_eq!(hour_time.precision(), TimePrecision::Hour);
732/// assert_eq!(hour_time.original_string(), "14");
733///
734/// // Create a full precision time
735/// let full_time = PrecisionTime::from_hms_milli(14, 30, 45, 123);
736/// assert_eq!(full_time.precision(), TimePrecision::Millisecond);
737/// assert_eq!(full_time.original_string(), "14:30:45.123");
738/// ```
739#[derive(Debug, Clone, PartialEq, Eq)]
740pub struct PrecisionTime {
741    /// Hour component (0-23, always present)
742    hour: u32,
743    /// Minute component (0-59)
744    minute: Option<u32>,
745    /// Second component (0-59)
746    second: Option<u32>,
747    /// Millisecond component (0-999)
748    millisecond: Option<u32>,
749    /// Precision level of this time
750    precision: TimePrecision,
751    /// Original string representation
752    original_string: Arc<str>,
753}
754
755impl PrecisionTime {
756    /// Creates an hour-only precision time.
757    pub fn from_hour(hour: u32) -> Self {
758        Self {
759            hour,
760            minute: None,
761            second: None,
762            millisecond: None,
763            precision: TimePrecision::Hour,
764            original_string: Arc::from(format!("{:02}", hour)),
765        }
766    }
767
768    /// Creates an hour-minute precision time.
769    pub fn from_hm(hour: u32, minute: u32) -> Self {
770        Self {
771            hour,
772            minute: Some(minute),
773            second: None,
774            millisecond: None,
775            precision: TimePrecision::HourMinute,
776            original_string: Arc::from(format!("{:02}:{:02}", hour, minute)),
777        }
778    }
779
780    /// Creates an hour-minute-second precision time.
781    pub fn from_hms(hour: u32, minute: u32, second: u32) -> Self {
782        Self {
783            hour,
784            minute: Some(minute),
785            second: Some(second),
786            millisecond: None,
787            precision: TimePrecision::HourMinuteSecond,
788            original_string: Arc::from(format!("{:02}:{:02}:{:02}", hour, minute, second)),
789        }
790    }
791
792    /// Creates a full precision time with milliseconds.
793    pub fn from_hms_milli(hour: u32, minute: u32, second: u32, millisecond: u32) -> Self {
794        Self {
795            hour,
796            minute: Some(minute),
797            second: Some(second),
798            millisecond: Some(millisecond),
799            precision: TimePrecision::Millisecond,
800            original_string: Arc::from(format!(
801                "{:02}:{:02}:{:02}.{:03}",
802                hour, minute, second, millisecond
803            )),
804        }
805    }
806
807    /// Parses a FHIR time string, preserving precision.
808    pub fn parse(s: &str) -> Option<Self> {
809        // Remove @ and T prefixes if present
810        let s = s.strip_prefix('@').unwrap_or(s);
811        let s = s.strip_prefix('T').unwrap_or(s);
812
813        // Check for timezone (not allowed in FHIR time)
814        if s.contains('+') || s.contains('-') || s.ends_with('Z') {
815            return None;
816        }
817
818        let parts: Vec<&str> = s.split(':').collect();
819        match parts.len() {
820            1 => {
821                // Hour only
822                let hour = parts[0].parse::<u32>().ok()?;
823                if hour > 23 {
824                    return None;
825                }
826                Some(Self {
827                    hour,
828                    minute: None,
829                    second: None,
830                    millisecond: None,
831                    precision: TimePrecision::Hour,
832                    original_string: Arc::from(s),
833                })
834            }
835            2 => {
836                // Hour:minute
837                let hour = parts[0].parse::<u32>().ok()?;
838                let minute = parts[1].parse::<u32>().ok()?;
839                if hour > 23 || minute > 59 {
840                    return None;
841                }
842                Some(Self {
843                    hour,
844                    minute: Some(minute),
845                    second: None,
846                    millisecond: None,
847                    precision: TimePrecision::HourMinute,
848                    original_string: Arc::from(s),
849                })
850            }
851            3 => {
852                // Hour:minute:second[.millisecond]
853                let hour = parts[0].parse::<u32>().ok()?;
854                let minute = parts[1].parse::<u32>().ok()?;
855
856                // Check for milliseconds
857                let (second, millisecond, precision) = if parts[2].contains('.') {
858                    let sec_parts: Vec<&str> = parts[2].split('.').collect();
859                    if sec_parts.len() != 2 {
860                        return None;
861                    }
862                    let second = sec_parts[0].parse::<u32>().ok()?;
863                    // Parse milliseconds, padding or truncating as needed
864                    let ms_str = sec_parts[1];
865                    let ms = if ms_str.len() <= 3 {
866                        // Pad with zeros if needed
867                        let padded = format!("{:0<3}", ms_str);
868                        padded.parse::<u32>().ok()?
869                    } else {
870                        // Truncate to 3 digits
871                        ms_str[..3].parse::<u32>().ok()?
872                    };
873                    (second, Some(ms), TimePrecision::Millisecond)
874                } else {
875                    let second = parts[2].parse::<u32>().ok()?;
876                    (second, None, TimePrecision::HourMinuteSecond)
877                };
878
879                if hour > 23 || minute > 59 || second > 59 {
880                    return None;
881                }
882
883                Some(Self {
884                    hour,
885                    minute: Some(minute),
886                    second: Some(second),
887                    millisecond,
888                    precision,
889                    original_string: Arc::from(s),
890                })
891            }
892            _ => None,
893        }
894    }
895
896    /// Returns the precision level of this time.
897    pub fn precision(&self) -> TimePrecision {
898        self.precision
899    }
900
901    /// Returns the original string representation.
902    pub fn original_string(&self) -> &str {
903        &self.original_string
904    }
905
906    /// Converts to a NaiveTime, using defaults for missing components.
907    pub fn to_naive_time(&self) -> NaiveTime {
908        let milli = self.millisecond.unwrap_or(0);
909        let micro = milli * 1000; // Convert milliseconds to microseconds
910        NaiveTime::from_hms_micro_opt(
911            self.hour,
912            self.minute.unwrap_or(0),
913            self.second.unwrap_or(0),
914            micro,
915        )
916        .expect("Valid time components")
917    }
918
919    /// Compares two times considering precision.
920    /// Per FHIRPath spec: seconds and milliseconds are considered the same precision level
921    pub fn compare(&self, other: &Self) -> Option<Ordering> {
922        match self.hour.cmp(&other.hour) {
923            Ordering::Equal => {
924                match (self.minute, other.minute) {
925                    (None, None) => Some(Ordering::Equal),
926                    (None, Some(_)) | (Some(_), None) => None,
927                    (Some(m1), Some(m2)) => match m1.cmp(&m2) {
928                        Ordering::Equal => {
929                            match (self.second, other.second) {
930                                (None, None) => Some(Ordering::Equal),
931                                (None, Some(_)) | (Some(_), None) => None,
932                                (Some(s1), Some(s2)) => {
933                                    // Per FHIRPath spec: second and millisecond precisions are
934                                    // considered a single precision using decimal comparison
935                                    let ms1 = self.millisecond.unwrap_or(0);
936                                    let ms2 = other.millisecond.unwrap_or(0);
937                                    let total1 = s1 * 1000 + ms1;
938                                    let total2 = s2 * 1000 + ms2;
939                                    Some(total1.cmp(&total2))
940                                }
941                            }
942                        }
943                        other => Some(other),
944                    },
945                }
946            }
947            other => Some(other),
948        }
949    }
950}
951
952impl Default for PrecisionDateTime {
953    fn default() -> Self {
954        // Default to Unix epoch 1970-01-01T00:00:00
955        Self::from_date(1970, 1, 1)
956    }
957}
958
959/// Precision-aware FHIR DateTime type.
960///
961/// This type preserves the original precision and string representation
962/// of FHIR datetime values, including timezone information when present.
963///
964/// # FHIR DateTime Formats
965/// - `YYYY` - Year only
966/// - `YYYY-MM` - Year and month
967/// - `YYYY-MM-DD` - Date only
968/// - `YYYY-MM-DDTHH` - Date with hour
969/// - `YYYY-MM-DDTHH:MM` - Date with hour and minute
970/// - `YYYY-MM-DDTHH:MM:SS` - Date with time to seconds
971/// - `YYYY-MM-DDTHH:MM:SS.sss` - Full datetime with milliseconds
972/// - All time formats can include timezone: `Z`, `+HH:MM`, `-HH:MM`
973///
974/// # Examples
975/// ```rust
976/// use helios_fhir::{PrecisionDateTime, DateTimePrecision};
977///
978/// // Create a date-only datetime
979/// let date_dt = PrecisionDateTime::from_date(2023, 3, 15);
980/// assert_eq!(date_dt.precision(), DateTimePrecision::Date);
981/// assert_eq!(date_dt.original_string(), "2023-03-15");
982///
983/// // Create a full datetime with timezone
984/// let full_dt = PrecisionDateTime::parse("2023-03-15T14:30:45.123Z").unwrap();
985/// assert_eq!(full_dt.precision(), DateTimePrecision::Full);
986/// ```
987#[derive(Debug, Clone, PartialEq, Eq)]
988pub struct PrecisionDateTime {
989    /// Date components
990    pub date: PrecisionDate,
991    /// Time components (if precision includes time)
992    time: Option<PrecisionTime>,
993    /// Timezone offset in minutes from UTC (None means local/unspecified)
994    timezone_offset: Option<i32>,
995    /// Precision level of this datetime
996    precision: DateTimePrecision,
997    /// Original string representation
998    original_string: Arc<str>,
999}
1000
1001impl PrecisionDateTime {
1002    /// Creates a year-only datetime.
1003    pub fn from_year(year: i32) -> Self {
1004        let date = PrecisionDate::from_year(year);
1005        Self {
1006            original_string: date.original_string.clone(),
1007            date,
1008            time: None,
1009            timezone_offset: None,
1010            precision: DateTimePrecision::Year,
1011        }
1012    }
1013
1014    /// Creates a year-month datetime.
1015    pub fn from_year_month(year: i32, month: u32) -> Self {
1016        let date = PrecisionDate::from_year_month(year, month);
1017        Self {
1018            original_string: date.original_string.clone(),
1019            date,
1020            time: None,
1021            timezone_offset: None,
1022            precision: DateTimePrecision::YearMonth,
1023        }
1024    }
1025
1026    /// Creates a date-only datetime.
1027    pub fn from_date(year: i32, month: u32, day: u32) -> Self {
1028        let date = PrecisionDate::from_ymd(year, month, day);
1029        Self {
1030            original_string: date.original_string.clone(),
1031            date,
1032            time: None,
1033            timezone_offset: None,
1034            precision: DateTimePrecision::Date,
1035        }
1036    }
1037
1038    /// Parses a FHIR datetime string, preserving precision and timezone.
1039    pub fn parse(s: &str) -> Option<Self> {
1040        // Remove @ prefix if present
1041        let s = s.strip_prefix('@').unwrap_or(s);
1042
1043        // Check for 'T' separator to determine if time is present
1044        if let Some(t_pos) = s.find('T') {
1045            let date_part = &s[..t_pos];
1046            let time_and_tz = &s[t_pos + 1..];
1047
1048            // Parse date part
1049            let date = PrecisionDate::parse(date_part)?;
1050
1051            // Check for timezone at the end
1052            let (time_part, timezone_offset) = if let Some(stripped) = time_and_tz.strip_suffix('Z')
1053            {
1054                (stripped, Some(0))
1055            } else if let Some(plus_pos) = time_and_tz.rfind('+') {
1056                let tz_str = &time_and_tz[plus_pos + 1..];
1057                let offset = Self::parse_timezone_offset(tz_str)?;
1058                (&time_and_tz[..plus_pos], Some(offset))
1059            } else if let Some(minus_pos) = time_and_tz.rfind('-') {
1060                // Be careful not to confuse negative timezone with date separator
1061                if minus_pos > 0 && time_and_tz[..minus_pos].contains(':') {
1062                    let tz_str = &time_and_tz[minus_pos + 1..];
1063                    let offset = Self::parse_timezone_offset(tz_str)?;
1064                    (&time_and_tz[..minus_pos], Some(-offset))
1065                } else {
1066                    (time_and_tz, None)
1067                }
1068            } else {
1069                (time_and_tz, None)
1070            };
1071
1072            // Parse time part if not empty
1073            let (time, precision) = if time_part.is_empty() {
1074                // Just "T" with no time components (partial datetime)
1075                (
1076                    None,
1077                    match date.precision {
1078                        DatePrecision::Full => DateTimePrecision::Date,
1079                        DatePrecision::YearMonth => DateTimePrecision::YearMonth,
1080                        DatePrecision::Year => DateTimePrecision::Year,
1081                    },
1082                )
1083            } else {
1084                let time = PrecisionTime::parse(time_part)?;
1085                let precision = match time.precision {
1086                    TimePrecision::Hour => DateTimePrecision::DateHour,
1087                    TimePrecision::HourMinute => DateTimePrecision::DateHourMinute,
1088                    TimePrecision::HourMinuteSecond => DateTimePrecision::DateHourMinuteSecond,
1089                    TimePrecision::Millisecond => DateTimePrecision::Full,
1090                };
1091                (Some(time), precision)
1092            };
1093
1094            Some(Self {
1095                date,
1096                time,
1097                timezone_offset,
1098                precision,
1099                original_string: Arc::from(s),
1100            })
1101        } else {
1102            // No 'T' separator, just a date
1103            let date = PrecisionDate::parse(s)?;
1104            let precision = match date.precision {
1105                DatePrecision::Year => DateTimePrecision::Year,
1106                DatePrecision::YearMonth => DateTimePrecision::YearMonth,
1107                DatePrecision::Full => DateTimePrecision::Date,
1108            };
1109
1110            Some(Self {
1111                original_string: Arc::from(s),
1112                date,
1113                time: None,
1114                timezone_offset: None,
1115                precision,
1116            })
1117        }
1118    }
1119
1120    /// Parses a timezone offset string (e.g., "05:30") into minutes.
1121    fn parse_timezone_offset(s: &str) -> Option<i32> {
1122        let parts: Vec<&str> = s.split(':').collect();
1123        match parts.len() {
1124            1 => {
1125                // Just hours
1126                let hours = parts[0].parse::<i32>().ok()?;
1127                Some(hours * 60)
1128            }
1129            2 => {
1130                // Hours and minutes
1131                let hours = parts[0].parse::<i32>().ok()?;
1132                let minutes = parts[1].parse::<i32>().ok()?;
1133                Some(hours * 60 + minutes)
1134            }
1135            _ => None,
1136        }
1137    }
1138
1139    /// Creates a PrecisionDateTime from a PrecisionDate (for date to datetime conversion).
1140    pub fn from_precision_date(date: PrecisionDate) -> Self {
1141        let precision = match date.precision {
1142            DatePrecision::Year => DateTimePrecision::Year,
1143            DatePrecision::YearMonth => DateTimePrecision::YearMonth,
1144            DatePrecision::Full => DateTimePrecision::Date,
1145        };
1146        Self {
1147            original_string: date.original_string.clone(),
1148            date,
1149            time: None,
1150            timezone_offset: None,
1151            precision,
1152        }
1153    }
1154
1155    /// Returns the precision level of this datetime.
1156    pub fn precision(&self) -> DateTimePrecision {
1157        self.precision
1158    }
1159
1160    /// Returns the original string representation.
1161    pub fn original_string(&self) -> &str {
1162        &self.original_string
1163    }
1164
1165    /// Converts to a chrono DateTime<Utc>, using defaults for missing components.
1166    pub fn to_chrono_datetime(&self) -> ChronoDateTime<Utc> {
1167        let naive_date = self.date.to_naive_date();
1168        let naive_time = self
1169            .time
1170            .as_ref()
1171            .map(|t| t.to_naive_time())
1172            .unwrap_or_else(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
1173
1174        let naive_dt = naive_date.and_time(naive_time);
1175
1176        // Apply timezone offset if present
1177        if let Some(offset_minutes) = self.timezone_offset {
1178            // The datetime is in local time with the given offset
1179            // We need to subtract the offset to get UTC
1180            let utc_naive = naive_dt - chrono::Duration::minutes(offset_minutes as i64);
1181            ChronoDateTime::<Utc>::from_naive_utc_and_offset(utc_naive, Utc)
1182        } else {
1183            // No timezone means we assume UTC
1184            ChronoDateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc)
1185        }
1186    }
1187
1188    /// Compares two datetimes considering precision and timezones.
1189    pub fn compare(&self, other: &Self) -> Option<Ordering> {
1190        // Check if precisions are compatible
1191        // Per FHIRPath spec: seconds and milliseconds are the same precision
1192        let self_precision_normalized = match self.precision {
1193            DateTimePrecision::Full => DateTimePrecision::DateHourMinuteSecond,
1194            p => p,
1195        };
1196        let other_precision_normalized = match other.precision {
1197            DateTimePrecision::Full => DateTimePrecision::DateHourMinuteSecond,
1198            p => p,
1199        };
1200
1201        // If precisions don't match (except for seconds/milliseconds), return None
1202        if self_precision_normalized != other_precision_normalized {
1203            // Special handling for date vs datetime with time components
1204            if self.time.is_none() != other.time.is_none() {
1205                return None;
1206            }
1207        }
1208
1209        // If both have sufficient precision and timezone info, compare as full datetimes
1210        if self.precision >= DateTimePrecision::DateHour
1211            && other.precision >= DateTimePrecision::DateHour
1212            && self.timezone_offset.is_some()
1213            && other.timezone_offset.is_some()
1214        {
1215            // Convert to UTC and compare
1216            return Some(self.to_chrono_datetime().cmp(&other.to_chrono_datetime()));
1217        }
1218
1219        // If one has timezone and the other doesn't, comparison is indeterminate
1220        if self.timezone_offset.is_some() != other.timezone_offset.is_some() {
1221            return None;
1222        }
1223
1224        // Otherwise, compare components with precision awareness
1225        match self.date.compare(&other.date) {
1226            Some(Ordering::Equal) => {
1227                // Dates are equal at their precision level
1228                match (&self.time, &other.time) {
1229                    (None, None) => Some(Ordering::Equal),
1230                    (None, Some(_)) | (Some(_), None) => None, // Different precisions
1231                    (Some(t1), Some(t2)) => t1.compare(t2),
1232                }
1233            }
1234            other => other,
1235        }
1236    }
1237}
1238
1239// === Display Implementations for Precision Types ===
1240
1241impl std::fmt::Display for PrecisionDate {
1242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1243        write!(f, "{}", self.original_string)
1244    }
1245}
1246
1247impl std::fmt::Display for PrecisionDateTime {
1248    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1249        write!(f, "{}", self.original_string)
1250    }
1251}
1252
1253impl std::fmt::Display for PrecisionTime {
1254    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1255        write!(f, "{}", self.original_string)
1256    }
1257}
1258
1259// === Serde Implementations for Precision Types ===
1260
1261impl Serialize for PrecisionDate {
1262    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1263    where
1264        S: Serializer,
1265    {
1266        // Serialize as a simple string
1267        serializer.serialize_str(&self.original_string)
1268    }
1269}
1270
1271impl<'de> Deserialize<'de> for PrecisionDate {
1272    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1273    where
1274        D: Deserializer<'de>,
1275    {
1276        let s = String::deserialize(deserializer)?;
1277        PrecisionDate::parse(&s)
1278            .ok_or_else(|| de::Error::custom(format!("Invalid FHIR date format: {}", s)))
1279    }
1280}
1281
1282impl Serialize for PrecisionTime {
1283    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1284    where
1285        S: Serializer,
1286    {
1287        // Serialize as a simple string
1288        serializer.serialize_str(&self.original_string)
1289    }
1290}
1291
1292impl<'de> Deserialize<'de> for PrecisionTime {
1293    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1294    where
1295        D: Deserializer<'de>,
1296    {
1297        let s = String::deserialize(deserializer)?;
1298        PrecisionTime::parse(&s)
1299            .ok_or_else(|| de::Error::custom(format!("Invalid FHIR time format: {}", s)))
1300    }
1301}
1302
1303impl Serialize for PrecisionDateTime {
1304    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1305    where
1306        S: Serializer,
1307    {
1308        // Serialize as a simple string
1309        serializer.serialize_str(&self.original_string)
1310    }
1311}
1312
1313impl<'de> Deserialize<'de> for PrecisionDateTime {
1314    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1315    where
1316        D: Deserializer<'de>,
1317    {
1318        let s = String::deserialize(deserializer)?;
1319        PrecisionDateTime::parse(&s)
1320            .ok_or_else(|| de::Error::custom(format!("Invalid FHIR datetime format: {}", s)))
1321    }
1322}
1323
1324// === PrecisionInstant Implementation ===
1325
1326/// A FHIR instant value that preserves the original string representation and precision.
1327///
1328/// Instants in FHIR must be complete date-time values with timezone information,
1329/// representing a specific moment in time. This type wraps PrecisionDateTime but
1330/// enforces instant-specific constraints.
1331#[derive(Debug, Clone, PartialEq, Eq, Default)]
1332pub struct PrecisionInstant {
1333    inner: PrecisionDateTime,
1334}
1335
1336impl PrecisionInstant {
1337    /// Parses a FHIR instant string.
1338    /// Returns None if the string is not a valid instant (must have full date, time, and timezone).
1339    pub fn parse(s: &str) -> Option<Self> {
1340        // Parse as PrecisionDateTime first
1341        let dt = PrecisionDateTime::parse(s)?;
1342
1343        // For now, accept any valid datetime as an instant
1344        // In strict mode, we could require timezone, but many FHIR resources
1345        // use instant fields without explicit timezones
1346        Some(PrecisionInstant { inner: dt })
1347    }
1348
1349    /// Returns the original string representation
1350    pub fn original_string(&self) -> &str {
1351        self.inner.original_string()
1352    }
1353
1354    /// Get the inner PrecisionDateTime
1355    pub fn as_datetime(&self) -> &PrecisionDateTime {
1356        &self.inner
1357    }
1358
1359    /// Convert to chrono DateTime<Utc>
1360    pub fn to_chrono_datetime(&self) -> ChronoDateTime<Utc> {
1361        // PrecisionDateTime::to_chrono_datetime returns ChronoDateTime<Utc>
1362        self.inner.to_chrono_datetime()
1363    }
1364}
1365
1366impl fmt::Display for PrecisionInstant {
1367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1368        write!(f, "{}", self.inner)
1369    }
1370}
1371
1372impl Serialize for PrecisionInstant {
1373    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1374    where
1375        S: Serializer,
1376    {
1377        self.inner.serialize(serializer)
1378    }
1379}
1380
1381impl<'de> Deserialize<'de> for PrecisionInstant {
1382    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1383    where
1384        D: Deserializer<'de>,
1385    {
1386        let s = String::deserialize(deserializer)?;
1387        PrecisionInstant::parse(&s)
1388            .ok_or_else(|| de::Error::custom(format!("Invalid FHIR instant format: {}", s)))
1389    }
1390}
1391
1392// === IntoEvaluationResult Implementations for Precision Types ===
1393
1394impl IntoEvaluationResult for PrecisionDate {
1395    fn to_evaluation_result(&self) -> EvaluationResult {
1396        EvaluationResult::date(self.original_string.to_string())
1397    }
1398}
1399
1400impl IntoEvaluationResult for PrecisionTime {
1401    fn to_evaluation_result(&self) -> EvaluationResult {
1402        EvaluationResult::time(self.original_string.to_string())
1403    }
1404}
1405
1406impl IntoEvaluationResult for PrecisionDateTime {
1407    fn to_evaluation_result(&self) -> EvaluationResult {
1408        EvaluationResult::datetime(self.original_string.to_string())
1409    }
1410}
1411
1412impl IntoEvaluationResult for PrecisionInstant {
1413    fn to_evaluation_result(&self) -> EvaluationResult {
1414        // Return as datetime with instant type info
1415        EvaluationResult::DateTime(
1416            self.inner.original_string.to_string(),
1417            Some(TypeInfoResult::new("FHIR", "instant")),
1418            None,
1419        )
1420    }
1421}
1422
1423// Removed DecimalElementObjectVisitor
1424
1425#[cfg(feature = "R4")]
1426pub mod r4;
1427#[cfg(feature = "R4B")]
1428pub mod r4b;
1429#[cfg(feature = "R5")]
1430pub mod r5;
1431#[cfg(feature = "R6")]
1432pub mod r6;
1433
1434pub mod compartment;
1435pub mod compartment_expressions;
1436pub mod parameters;
1437pub mod search;
1438
1439// Re-export commonly used types from parameters module
1440pub use parameters::{ParameterValueAccessor, VersionIndependentParameters};
1441
1442/// Returns the search-parameter NAMES that link `resource_type` to the
1443/// named compartment (e.g. `"Patient"`, `"Group"`, `"Encounter"`,
1444/// `"Practitioner"`, `"RelatedPerson"`, `"Device"`), for the specified
1445/// FHIR version.
1446///
1447/// Thin version-dispatching wrapper around the per-version code-generated
1448/// `get_compartment_params`. Returns an empty slice when the resource
1449/// type is not a member of the named compartment.
1450///
1451/// Used by:
1452/// - REST compartment-search handler (`/Patient/{id}/Observation` style URLs)
1453///   to know which search params to feed into the search-index query.
1454/// - SoF in-DB runners to filter `$viewdefinition-run` results by patient /
1455///   group membership.
1456///
1457/// Pair this with [`compartment_expressions`] when you need the FHIRPath
1458/// expressions themselves (e.g. for in-process FHIRPath evaluation against
1459/// raw JSON, as `helios_sof::compartment` does).
1460#[allow(unreachable_patterns)]
1461pub fn compartment_params(
1462    version: FhirVersion,
1463    compartment_type: &str,
1464    resource_type: &str,
1465) -> &'static [&'static str] {
1466    match version {
1467        #[cfg(feature = "R4")]
1468        FhirVersion::R4 => r4::get_compartment_params(compartment_type, resource_type),
1469        #[cfg(feature = "R4B")]
1470        FhirVersion::R4B => r4b::get_compartment_params(compartment_type, resource_type),
1471        #[cfg(feature = "R5")]
1472        FhirVersion::R5 => r5::get_compartment_params(compartment_type, resource_type),
1473        #[cfg(feature = "R6")]
1474        FhirVersion::R6 => r6::get_compartment_params(compartment_type, resource_type),
1475        _ => &[],
1476    }
1477}
1478
1479/// Returns the JSON element names (camelCase — `billablePeriod`, `type`,
1480/// `use`) of every element the FHIR specification marks `isSummary: true`
1481/// on `resource_type`, for the specified FHIR version: the set `_summary=true`
1482/// keeps. An unknown resource type gets the generated lookup's minimal
1483/// `resourceType`, `id`, `meta` set.
1484///
1485/// Thin version-dispatching wrapper around the per-version code-generated
1486/// `get_summary_fields`, which reports the *Rust* field names of the
1487/// generated structs — snake_case, and a raw identifier (`r#type`, `r#use`,
1488/// `r#abstract`, `r#for`) wherever a FHIR element name collides with a Rust
1489/// keyword. The conversion back to element names lives here, once, so every
1490/// consumer agrees on it: REST `_summary` subsetting and the UI results table
1491/// each used to carry their own copy, and the UI's missed the `r#` prefix,
1492/// rendering `R#TYPE` / `R#USE` headers over empty columns for Claim and
1493/// ~50 other types (#1107).
1494#[allow(unreachable_patterns)]
1495pub fn summary_elements(version: FhirVersion, resource_type: &str) -> Vec<String> {
1496    let fields: &[&str] = match version {
1497        #[cfg(feature = "R4")]
1498        FhirVersion::R4 => r4::get_summary_fields(resource_type),
1499        #[cfg(feature = "R4B")]
1500        FhirVersion::R4B => r4b::get_summary_fields(resource_type),
1501        #[cfg(feature = "R5")]
1502        FhirVersion::R5 => r5::get_summary_fields(resource_type),
1503        #[cfg(feature = "R6")]
1504        FhirVersion::R6 => r6::get_summary_fields(resource_type),
1505        _ => &[],
1506    };
1507    fields.iter().map(|f| field_to_element_name(f)).collect()
1508}
1509
1510/// Maps a generated struct's Rust field name back to the FHIR element name it
1511/// serializes as: drops the raw-identifier prefix the generator adds for
1512/// keyword collisions (`r#type` → `type`) and converts snake_case to
1513/// camelCase (`birth_date` → `birthDate`). Exactly inverts the generator's
1514/// `make_rust_safe`.
1515fn field_to_element_name(field: &str) -> String {
1516    let field = field.strip_prefix("r#").unwrap_or(field);
1517    let mut out = String::with_capacity(field.len());
1518    let mut upper_next = false;
1519    for c in field.chars() {
1520        if c == '_' {
1521            upper_next = true;
1522        } else if upper_next {
1523            out.push(c.to_ascii_uppercase());
1524            upper_next = false;
1525        } else {
1526            out.push(c);
1527        }
1528    }
1529    out
1530}
1531
1532// Internal helpers used by the derive macro; not part of the public API
1533#[doc(hidden)]
1534/// Multi-version FHIR resource container supporting version-agnostic operations.
1535///
1536/// This enum provides a unified interface for working with FHIR resources across
1537/// different specification versions. It enables applications to handle multiple
1538/// FHIR versions simultaneously while maintaining type safety and version-specific
1539/// behavior where needed.
1540///
1541/// # Supported Versions
1542///
1543/// - **R4**: FHIR 4.0.1 (normative)
1544/// - **R4B**: FHIR 4.3.0 (ballot)  
1545/// - **R5**: FHIR 5.0.0 (ballot)
1546/// - **R6**: FHIR 6.0.0 (draft)
1547///
1548/// # Feature Flags
1549///
1550/// Each FHIR version is controlled by a corresponding Cargo feature flag.
1551/// Only enabled versions will be available in the enum variants.
1552///
1553/// # Examples
1554///
1555/// ```rust
1556/// use helios_fhir::{FhirResource, FhirVersion};
1557/// # #[cfg(feature = "R4")]
1558/// use helios_fhir::r4::{Patient, HumanName};
1559///
1560/// # #[cfg(feature = "R4")]
1561/// {
1562///     // Create an R4 patient
1563///     let patient = Patient {
1564///         name: Some(vec![HumanName {
1565///             family: Some("Doe".to_string().into()),
1566///             given: Some(vec!["John".to_string().into()]),
1567///             ..Default::default()
1568///         }]),
1569///         ..Default::default()
1570///     };
1571///
1572///     // Wrap in version-agnostic container
1573///     let resource = FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(Box::new(patient))));
1574///     assert_eq!(resource.version(), FhirVersion::R4);
1575/// }
1576/// ```
1577///
1578/// # Version Detection
1579///
1580/// Use the `version()` method to determine which FHIR version a resource uses:
1581///
1582/// ```rust
1583/// # use helios_fhir::{FhirResource, FhirVersion};
1584/// # #[cfg(feature = "R4")]
1585/// # {
1586/// # let resource = FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(Default::default())));
1587/// match resource.version() {
1588///     #[cfg(feature = "R4")]
1589///     FhirVersion::R4 => println!("This is an R4 resource"),
1590///     #[cfg(feature = "R4B")]
1591///     FhirVersion::R4B => println!("This is an R4B resource"),
1592///     #[cfg(feature = "R5")]
1593///     FhirVersion::R5 => println!("This is an R5 resource"),
1594///     #[cfg(feature = "R6")]
1595///     FhirVersion::R6 => println!("This is an R6 resource"),
1596/// }
1597/// # }
1598/// ```
1599#[derive(Debug)]
1600pub enum FhirResource {
1601    /// FHIR 4.0.1 (normative) resource
1602    #[cfg(feature = "R4")]
1603    R4(Box<r4::Resource>),
1604    /// FHIR 4.3.0 (ballot) resource
1605    #[cfg(feature = "R4B")]
1606    R4B(Box<r4b::Resource>),
1607    /// FHIR 5.0.0 (ballot) resource
1608    #[cfg(feature = "R5")]
1609    R5(Box<r5::Resource>),
1610    /// FHIR 6.0.0 (draft) resource
1611    #[cfg(feature = "R6")]
1612    R6(Box<r6::Resource>),
1613}
1614
1615impl FhirResource {
1616    /// Returns the FHIR specification version of this resource.
1617    ///
1618    /// This method provides version detection for multi-version applications,
1619    /// enabling version-specific processing logic and compatibility checks.
1620    ///
1621    /// # Returns
1622    ///
1623    /// The `FhirVersion` enum variant corresponding to this resource's specification.
1624    ///
1625    /// # Examples
1626    ///
1627    /// ```rust
1628    /// use helios_fhir::{FhirResource, FhirVersion};
1629    ///
1630    /// # #[cfg(feature = "R5")]
1631    /// # {
1632    /// # let resource = FhirResource::R5(Box::new(helios_fhir::r5::Resource::Patient(Default::default())));
1633    /// let version = resource.version();
1634    /// assert_eq!(version, FhirVersion::R5);
1635    ///
1636    /// // Use version for conditional logic
1637    /// match version {
1638    ///     FhirVersion::R5 => {
1639    ///         println!("Processing R5 resource with latest features");
1640    ///     },
1641    ///     FhirVersion::R4 => {
1642    ///         println!("Processing R4 resource with normative features");
1643    ///     },
1644    ///     _ => {
1645    ///         println!("Processing other FHIR version");
1646    ///     }
1647    /// }
1648    /// # }
1649    /// ```
1650    pub fn version(&self) -> FhirVersion {
1651        match self {
1652            #[cfg(feature = "R4")]
1653            FhirResource::R4(_) => FhirVersion::R4,
1654            #[cfg(feature = "R4B")]
1655            FhirResource::R4B(_) => FhirVersion::R4B,
1656            #[cfg(feature = "R5")]
1657            FhirResource::R5(_) => FhirVersion::R5,
1658            #[cfg(feature = "R6")]
1659            FhirResource::R6(_) => FhirVersion::R6,
1660        }
1661    }
1662}
1663
1664/// Enumeration of supported FHIR specification versions.
1665///
1666/// This enum represents the different versions of the FHIR (Fast Healthcare
1667/// Interoperability Resources) specification that this library supports.
1668/// Each version represents a specific release of the FHIR standard with
1669/// its own set of features, resources, and compatibility requirements.
1670///
1671/// # Version Status
1672///
1673/// - **R4** (4.0.1): Normative version, widely adopted in production
1674/// - **R4B** (4.3.0): Ballot version with additional features
1675/// - **R5** (5.0.0): Ballot version with significant enhancements
1676/// - **R6** (6.0.0): Draft version under active development
1677///
1678/// # Feature Flags
1679///
1680/// Each version is controlled by a corresponding Cargo feature flag:
1681/// - `R4`: Enables FHIR R4 support
1682/// - `R4B`: Enables FHIR R4B support  
1683/// - `R5`: Enables FHIR R5 support
1684/// - `R6`: Enables FHIR R6 support
1685///
1686/// # Examples
1687///
1688/// ```rust
1689/// use helios_fhir::FhirVersion;
1690///
1691/// // Version comparison
1692/// # #[cfg(all(feature = "R4", feature = "R5"))]
1693/// # {
1694/// assert_ne!(FhirVersion::R4, FhirVersion::R5);
1695/// # }
1696///
1697/// // String representation
1698/// # #[cfg(feature = "R4")]
1699/// # {
1700/// let version = FhirVersion::R4;
1701/// assert_eq!(version.as_str(), "R4");
1702/// assert_eq!(version.to_string(), "R4");
1703/// # }
1704/// ```
1705///
1706/// # CLI Integration
1707///
1708/// This enum implements `clap::ValueEnum` for command-line argument parsing:
1709///
1710/// ```rust,no_run
1711/// use clap::Parser;
1712/// use helios_fhir::FhirVersion;
1713///
1714/// #[derive(Parser)]
1715/// struct Args {
1716///     #[arg(value_enum)]
1717///     version: FhirVersion,
1718/// }
1719/// ```
1720#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1721pub enum FhirVersion {
1722    /// FHIR 4.0.1 (normative) - The current normative version
1723    #[cfg(feature = "R4")]
1724    R4,
1725    /// FHIR 4.3.0 (ballot) - Intermediate version with additional features
1726    #[cfg(feature = "R4B")]
1727    R4B,
1728    /// FHIR 5.0.0 (ballot) - Next major version with significant changes
1729    #[cfg(feature = "R5")]
1730    R5,
1731    /// FHIR 6.0.0 (draft) - Future version under development
1732    #[cfg(feature = "R6")]
1733    R6,
1734}
1735
1736impl FhirVersion {
1737    /// Returns the string representation of the FHIR version.
1738    ///
1739    /// This method provides the standard version identifier as used in
1740    /// FHIR documentation, URLs, and configuration files.
1741    ///
1742    /// # Returns
1743    ///
1744    /// A static string slice representing the version (e.g., "R4", "R5").
1745    ///
1746    /// # Examples
1747    ///
1748    /// ```rust
1749    /// use helios_fhir::FhirVersion;
1750    ///
1751    /// # #[cfg(feature = "R4")]
1752    /// assert_eq!(FhirVersion::R4.as_str(), "R4");
1753    /// # #[cfg(feature = "R5")]
1754    /// assert_eq!(FhirVersion::R5.as_str(), "R5");
1755    /// ```
1756    ///
1757    /// # Usage
1758    ///
1759    /// This method is commonly used for:
1760    /// - Logging and debugging output
1761    /// - Configuration file parsing
1762    /// - API endpoint construction
1763    /// - Version-specific resource loading
1764    pub fn as_str(&self) -> &'static str {
1765        match self {
1766            #[cfg(feature = "R4")]
1767            FhirVersion::R4 => "R4",
1768            #[cfg(feature = "R4B")]
1769            FhirVersion::R4B => "R4B",
1770            #[cfg(feature = "R5")]
1771            FhirVersion::R5 => "R5",
1772            #[cfg(feature = "R6")]
1773            FhirVersion::R6 => "R6",
1774        }
1775    }
1776
1777    /// Parse from MIME-type parameter value (e.g., "4.0", "5.0").
1778    ///
1779    /// Per FHIR spec: <https://hl7.org/fhir/http.html#version-parameter>
1780    ///
1781    /// # Arguments
1782    ///
1783    /// * `value` - The MIME-type parameter value (e.g., "4.0", "4.3", "5.0", "6.0")
1784    ///
1785    /// # Returns
1786    ///
1787    /// The corresponding `FhirVersion` if the value matches an enabled version,
1788    /// or `None` if not recognized or the version feature is not enabled.
1789    ///
1790    /// # Examples
1791    ///
1792    /// ```rust
1793    /// use helios_fhir::FhirVersion;
1794    ///
1795    /// # #[cfg(feature = "R4")]
1796    /// assert_eq!(FhirVersion::from_mime_param("4.0"), Some(FhirVersion::R4));
1797    /// # #[cfg(feature = "R5")]
1798    /// assert_eq!(FhirVersion::from_mime_param("5.0"), Some(FhirVersion::R5));
1799    /// assert_eq!(FhirVersion::from_mime_param("invalid"), None);
1800    /// ```
1801    pub fn from_mime_param(value: &str) -> Option<Self> {
1802        match value.trim() {
1803            #[cfg(feature = "R4")]
1804            "4.0" => Some(FhirVersion::R4),
1805            #[cfg(feature = "R4B")]
1806            "4.3" => Some(FhirVersion::R4B),
1807            #[cfg(feature = "R5")]
1808            "5.0" => Some(FhirVersion::R5),
1809            #[cfg(feature = "R6")]
1810            "6.0" => Some(FhirVersion::R6),
1811            _ => None,
1812        }
1813    }
1814
1815    /// Returns the MIME-type parameter value for this version.
1816    ///
1817    /// This value is used in Content-Type and Accept headers per FHIR spec.
1818    /// Example: `application/fhir+json; fhirVersion=4.0`
1819    ///
1820    /// # Examples
1821    ///
1822    /// ```rust
1823    /// use helios_fhir::FhirVersion;
1824    ///
1825    /// # #[cfg(feature = "R4")]
1826    /// assert_eq!(FhirVersion::R4.as_mime_param(), "4.0");
1827    /// # #[cfg(feature = "R5")]
1828    /// assert_eq!(FhirVersion::R5.as_mime_param(), "5.0");
1829    /// ```
1830    pub fn as_mime_param(&self) -> &'static str {
1831        match self {
1832            #[cfg(feature = "R4")]
1833            FhirVersion::R4 => "4.0",
1834            #[cfg(feature = "R4B")]
1835            FhirVersion::R4B => "4.3",
1836            #[cfg(feature = "R5")]
1837            FhirVersion::R5 => "5.0",
1838            #[cfg(feature = "R6")]
1839            FhirVersion::R6 => "6.0",
1840        }
1841    }
1842
1843    /// Returns the full version string (e.g., "4.0.1", "5.0.0").
1844    ///
1845    /// This is the complete version identifier used in CapabilityStatement.fhirVersion.
1846    ///
1847    /// # Examples
1848    ///
1849    /// ```rust
1850    /// use helios_fhir::FhirVersion;
1851    ///
1852    /// # #[cfg(feature = "R4")]
1853    /// assert_eq!(FhirVersion::R4.full_version(), "4.0.1");
1854    /// # #[cfg(feature = "R5")]
1855    /// assert_eq!(FhirVersion::R5.full_version(), "5.0.0");
1856    /// ```
1857    pub fn full_version(&self) -> &'static str {
1858        match self {
1859            #[cfg(feature = "R4")]
1860            FhirVersion::R4 => "4.0.1",
1861            #[cfg(feature = "R4B")]
1862            FhirVersion::R4B => "4.3.0",
1863            #[cfg(feature = "R5")]
1864            FhirVersion::R5 => "5.0.0",
1865            #[cfg(feature = "R6")]
1866            FhirVersion::R6 => "6.0.0",
1867        }
1868    }
1869
1870    /// Parse from database storage string.
1871    ///
1872    /// Accepts both MIME format ("4.0") and short format ("R4") for flexibility.
1873    /// This is useful when loading version information from the database.
1874    ///
1875    /// # Arguments
1876    ///
1877    /// * `value` - The storage value (e.g., "4.0", "R4", "r4")
1878    ///
1879    /// # Returns
1880    ///
1881    /// The corresponding `FhirVersion` if recognized, or `None` otherwise.
1882    ///
1883    /// # Examples
1884    ///
1885    /// ```rust
1886    /// use helios_fhir::FhirVersion;
1887    ///
1888    /// # #[cfg(feature = "R4")]
1889    /// {
1890    /// assert_eq!(FhirVersion::from_storage("4.0"), Some(FhirVersion::R4));
1891    /// assert_eq!(FhirVersion::from_storage("R4"), Some(FhirVersion::R4));
1892    /// assert_eq!(FhirVersion::from_storage("r4"), Some(FhirVersion::R4));
1893    /// }
1894    /// ```
1895    pub fn from_storage(value: &str) -> Option<Self> {
1896        // Try MIME format first
1897        Self::from_mime_param(value).or_else(|| match value.to_uppercase().as_str() {
1898            #[cfg(feature = "R4")]
1899            "R4" => Some(FhirVersion::R4),
1900            #[cfg(feature = "R4B")]
1901            "R4B" => Some(FhirVersion::R4B),
1902            #[cfg(feature = "R5")]
1903            "R5" => Some(FhirVersion::R5),
1904            #[cfg(feature = "R6")]
1905            "R6" => Some(FhirVersion::R6),
1906            _ => None,
1907        })
1908    }
1909
1910    /// Returns the notification Bundle.type value used for FHIR Subscription
1911    /// notifications in this version.
1912    ///
1913    /// R4 and R4B follow the R4 backport IG and emit `history` Bundles; R5 and
1914    /// R6 emit native `subscription-notification` Bundles per the
1915    /// [Subscription specification](https://build.fhir.org/subscription.html).
1916    pub fn notification_bundle_type(&self) -> &'static str {
1917        match self {
1918            #[cfg(feature = "R4")]
1919            FhirVersion::R4 => "history",
1920            #[cfg(feature = "R4B")]
1921            FhirVersion::R4B => "history",
1922            #[cfg(feature = "R5")]
1923            FhirVersion::R5 => "subscription-notification",
1924            #[cfg(feature = "R6")]
1925            FhirVersion::R6 => "subscription-notification",
1926        }
1927    }
1928
1929    /// Returns all enabled FHIR versions.
1930    ///
1931    /// This is useful for listing supported versions (e.g., in `$versions` operation).
1932    pub fn enabled_versions() -> &'static [FhirVersion] {
1933        &[
1934            #[cfg(feature = "R4")]
1935            FhirVersion::R4,
1936            #[cfg(feature = "R4B")]
1937            FhirVersion::R4B,
1938            #[cfg(feature = "R5")]
1939            FhirVersion::R5,
1940            #[cfg(feature = "R6")]
1941            FhirVersion::R6,
1942        ]
1943    }
1944
1945    /// Returns the default FHIR version for the current build.
1946    ///
1947    /// This is `R4` when the `R4` feature is enabled (the canonical default),
1948    /// otherwise the first enabled version (`R4B`, then `R5`, then `R6`). Unlike
1949    /// [`Default::default`] — which is gated on `feature = "R4"` and therefore
1950    /// unavailable in single-version-minimal builds — this helper is available
1951    /// whenever at least one FHIR version feature is enabled, which the crate
1952    /// requires at compile time. Use it instead of `unwrap_or_default()` /
1953    /// `FhirVersion::default()` on code paths that must compile in any
1954    /// single-version build (e.g. R4B-only).
1955    pub fn default_enabled() -> FhirVersion {
1956        // `enabled_versions()` always has at least one entry: at least one FHIR
1957        // version feature must be enabled at compile time.
1958        FhirVersion::enabled_versions()[0]
1959    }
1960}
1961
1962/// Dispatches a field-type lookup to the per-version generated `FIELD_TYPES`
1963/// table. Returns `(field_type, is_collection)` when the
1964/// `(parent_type, field_name)` pair is known, or `None` when the version
1965/// variant isn't compiled in (e.g. a downstream crate enabled `helios-fhir`
1966/// features that this build doesn't have).
1967///
1968/// Centralizes what used to be a hand-rolled match in
1969/// `helios-persistence::sof` and `helios-fhirpath::type_inference`.
1970pub fn get_field_type(
1971    version: FhirVersion,
1972    parent_type: &str,
1973    field_name: &str,
1974) -> Option<(&'static str, bool)> {
1975    match version {
1976        #[cfg(feature = "R4")]
1977        FhirVersion::R4 => crate::r4::get_field_type(parent_type, field_name),
1978        #[cfg(feature = "R4B")]
1979        FhirVersion::R4B => crate::r4b::get_field_type(parent_type, field_name),
1980        #[cfg(feature = "R5")]
1981        FhirVersion::R5 => crate::r5::get_field_type(parent_type, field_name),
1982        #[cfg(feature = "R6")]
1983        FhirVersion::R6 => crate::r6::get_field_type(parent_type, field_name),
1984        #[allow(unreachable_patterns)]
1985        _ => None,
1986    }
1987}
1988
1989/// Returns true when `name` is the type code of a FHIR primitive datatype
1990/// (case-sensitive, lowercase as in the FHIR spec — `boolean`, `integer`,
1991/// `dateTime`, …). The set is the union across FHIR versions, so
1992/// `integer64` (added in R5) and `xhtml` are included regardless of which
1993/// version feature is enabled.
1994///
1995/// Centralizes what used to be three hand-maintained primitive-type lists
1996/// inside `helios-fhirpath` (`fhir_type_hierarchy`, `resource_type`,
1997/// `type_inference`).
1998pub fn is_primitive_type(name: &str) -> bool {
1999    matches!(
2000        name,
2001        "base64Binary"
2002            | "boolean"
2003            | "canonical"
2004            | "code"
2005            | "date"
2006            | "dateTime"
2007            | "decimal"
2008            | "id"
2009            | "instant"
2010            | "integer"
2011            | "integer64"
2012            | "markdown"
2013            | "oid"
2014            | "positiveInt"
2015            | "string"
2016            | "time"
2017            | "unsignedInt"
2018            | "uri"
2019            | "url"
2020            | "uuid"
2021            | "xhtml"
2022    )
2023}
2024
2025/// Returns true when `field_name` appears anywhere in the per-version
2026/// `FIELD_TYPES` table. Used as a parent-context-free fallback for
2027/// detecting polymorphic typed variants (`valueQuantity`,
2028/// `deceasedBoolean`, …) when the parent FHIR type isn't statically known.
2029pub fn field_exists_anywhere(version: FhirVersion, field_name: &str) -> bool {
2030    field_types(version).is_some_and(|t| t.iter().any(|(_, f, _, _)| *f == field_name))
2031}
2032
2033/// Returns the per-version `FIELD_TYPES` slice when the version's feature
2034/// is compiled in. Each entry is `(parent_type, field_name, field_type,
2035/// is_collection)`. Use this when you need to enumerate all fields of a
2036/// parent type — for a single-field lookup, prefer [`get_field_type`].
2037pub fn field_types(
2038    version: FhirVersion,
2039) -> Option<&'static [(&'static str, &'static str, &'static str, bool)]> {
2040    match version {
2041        #[cfg(feature = "R4")]
2042        FhirVersion::R4 => Some(crate::r4::FIELD_TYPES),
2043        #[cfg(feature = "R4B")]
2044        FhirVersion::R4B => Some(crate::r4b::FIELD_TYPES),
2045        #[cfg(feature = "R5")]
2046        FhirVersion::R5 => Some(crate::r5::FIELD_TYPES),
2047        #[cfg(feature = "R6")]
2048        FhirVersion::R6 => Some(crate::r6::FIELD_TYPES),
2049        #[allow(unreachable_patterns)]
2050        _ => None,
2051    }
2052}
2053
2054/// Returns the compartment search parameters for a given FHIR version.
2055///
2056/// This is a version-agnostic dispatch over the per-version
2057/// `helios_fhir::{r4,r4b,r5,r6}::get_compartment_params` functions, which are
2058/// generated from the official FHIR `CompartmentDefinition` resources.
2059///
2060/// # Arguments
2061///
2062/// * `version` - The FHIR version to use for lookup
2063/// * `compartment_type` - The compartment type (e.g., "Patient", "Encounter")
2064/// * `resource_type` - The target resource type (e.g., "Observation")
2065///
2066/// # Returns
2067///
2068/// A static slice of search parameter names that link the resource to the
2069/// compartment. Returns an empty slice if the resource is not a member of the
2070/// compartment.
2071pub fn get_compartment_params(
2072    version: FhirVersion,
2073    compartment_type: &str,
2074    resource_type: &str,
2075) -> &'static [&'static str] {
2076    match version {
2077        #[cfg(feature = "R4")]
2078        FhirVersion::R4 => r4::get_compartment_params(compartment_type, resource_type),
2079        #[cfg(feature = "R4B")]
2080        FhirVersion::R4B => r4b::get_compartment_params(compartment_type, resource_type),
2081        #[cfg(feature = "R5")]
2082        FhirVersion::R5 => r5::get_compartment_params(compartment_type, resource_type),
2083        #[cfg(feature = "R6")]
2084        FhirVersion::R6 => r6::get_compartment_params(compartment_type, resource_type),
2085    }
2086}
2087
2088/// Implements `Display` trait for user-friendly output formatting.
2089///
2090/// This enables `FhirVersion` to be used in string formatting operations
2091/// and provides consistent output across different contexts.
2092///
2093/// # Examples
2094///
2095/// ```rust
2096/// use helios_fhir::FhirVersion;
2097///
2098/// # #[cfg(feature = "R5")]
2099/// # {
2100/// let version = FhirVersion::R5;
2101/// println!("Using FHIR version: {}", version); // Prints: "Using FHIR version: R5"
2102///
2103/// let formatted = format!("fhir-{}.json", version);
2104/// assert_eq!(formatted, "fhir-R5.json");
2105/// # }
2106/// ```
2107impl std::fmt::Display for FhirVersion {
2108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2109        write!(f, "{}", self.as_str())
2110    }
2111}
2112
2113/// Provides a default FHIR version when R4 feature is enabled.
2114///
2115/// R4 is chosen as the default because it is the current normative version
2116/// of the FHIR specification and is widely adopted in production systems.
2117///
2118/// # Examples
2119///
2120/// ```rust
2121/// use helios_fhir::FhirVersion;
2122///
2123/// # #[cfg(feature = "R4")]
2124/// # {
2125/// let default_version = FhirVersion::default();
2126/// assert_eq!(default_version, FhirVersion::R4);
2127/// # }
2128/// ```
2129#[cfg(feature = "R4")]
2130impl Default for FhirVersion {
2131    fn default() -> Self {
2132        FhirVersion::R4
2133    }
2134}
2135
2136/// Implements `clap::ValueEnum` for command-line argument parsing.
2137///
2138/// This implementation enables `FhirVersion` to be used directly as a command-line
2139/// argument type with clap, providing automatic parsing, validation, and help text
2140/// generation.
2141///
2142/// # Examples
2143///
2144/// ```rust,no_run
2145/// use clap::Parser;
2146/// use helios_fhir::FhirVersion;
2147///
2148/// #[derive(Parser)]
2149/// struct Args {
2150///     /// FHIR specification version to use
2151///     #[arg(value_enum, default_value_t = FhirVersion::default())]
2152///     version: FhirVersion,
2153/// }
2154///
2155/// // Command line: my-app --version R5
2156/// let args = Args::parse();
2157/// println!("Using FHIR version: {}", args.version);
2158/// ```
2159///
2160/// # Generated Help Text
2161///
2162/// When using this enum with clap, the help text will automatically include
2163/// all available FHIR versions based on enabled feature flags.
2164impl clap::ValueEnum for FhirVersion {
2165    fn value_variants<'a>() -> &'a [Self] {
2166        &[
2167            #[cfg(feature = "R4")]
2168            FhirVersion::R4,
2169            #[cfg(feature = "R4B")]
2170            FhirVersion::R4B,
2171            #[cfg(feature = "R5")]
2172            FhirVersion::R5,
2173            #[cfg(feature = "R6")]
2174            FhirVersion::R6,
2175        ]
2176    }
2177
2178    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
2179        Some(clap::builder::PossibleValue::new(self.as_str()))
2180    }
2181}
2182
2183/// Trait for providing FHIR resource type information
2184///
2185/// This trait allows querying which resource types are available in a specific
2186/// FHIR version without hardcoding resource type lists in multiple places.
2187pub trait FhirResourceTypeProvider {
2188    /// Returns a vector of all resource type names supported in this FHIR version
2189    fn get_resource_type_names() -> Vec<&'static str>;
2190
2191    /// Checks if a given type name is a resource type in this FHIR version
2192    fn is_resource_type(type_name: &str) -> bool {
2193        Self::get_resource_type_names()
2194            .iter()
2195            .any(|&resource_type| resource_type.eq_ignore_ascii_case(type_name))
2196    }
2197}
2198
2199/// Trait for providing FHIR complex type information
2200///
2201/// This trait allows querying which complex data types are available in a specific
2202/// FHIR version without hardcoding complex type lists in multiple places.
2203pub trait FhirComplexTypeProvider {
2204    /// Returns a vector of all complex type names supported in this FHIR version
2205    fn get_complex_type_names() -> Vec<&'static str>;
2206
2207    /// Checks if a given type name is a complex type in this FHIR version
2208    fn is_complex_type(type_name: &str) -> bool {
2209        Self::get_complex_type_names()
2210            .iter()
2211            .any(|&complex_type| complex_type.eq_ignore_ascii_case(type_name))
2212    }
2213}
2214
2215/// Trait for providing FHIR primitive type information
2216///
2217/// This trait allows querying which primitive data types are available in a specific
2218/// FHIR version without hardcoding primitive type lists in multiple places. The
2219/// implementation is generated from the FHIR specification (StructureDefinitions
2220/// whose `kind` is `primitive-type`).
2221pub trait FhirPrimitiveTypeProvider {
2222    /// Returns a vector of all primitive type names supported in this FHIR version
2223    /// (e.g. `boolean`, `string`, `dateTime`, `positiveInt`).
2224    fn get_primitive_type_names() -> Vec<&'static str>;
2225
2226    /// Checks if a given type name is a primitive type in this FHIR version
2227    fn is_primitive_type(type_name: &str) -> bool {
2228        Self::get_primitive_type_names()
2229            .iter()
2230            .any(|&primitive_type| primitive_type.eq_ignore_ascii_case(type_name))
2231    }
2232}
2233
2234// --- Internal Visitor for Element Object Deserialization ---
2235
2236/// Internal visitor struct for deserializing Element objects from JSON maps.
2237///
2238/// This visitor handles the complex deserialization logic for Element<V, E> when
2239/// the JSON input is an object containing id, extension, and value fields.
2240struct ElementObjectVisitor<V, E>(PhantomData<(V, E)>);
2241
2242impl<'de, V, E> Visitor<'de> for ElementObjectVisitor<V, E>
2243where
2244    V: Deserialize<'de>,
2245    E: Deserialize<'de>,
2246{
2247    type Value = Element<V, E>;
2248
2249    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
2250        formatter.write_str("an Element object")
2251    }
2252
2253    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
2254    where
2255        A: MapAccess<'de>,
2256    {
2257        let mut id: Option<String> = None;
2258        let mut extension: Option<Vec<E>> = None;
2259        let mut value: Option<V> = None;
2260
2261        // Manually deserialize fields from the map
2262        while let Some(key) = map.next_key::<String>()? {
2263            match key.as_str() {
2264                "id" => {
2265                    if id.is_some() {
2266                        return Err(de::Error::duplicate_field("id"));
2267                    }
2268                    id = Some(map.next_value()?);
2269                }
2270                "extension" => {
2271                    if extension.is_some() {
2272                        return Err(de::Error::duplicate_field("extension"));
2273                    }
2274                    #[cfg(feature = "xml")]
2275                    {
2276                        let single_or_vec: SingleOrVec<E> = map.next_value()?;
2277                        extension = Some(single_or_vec.into());
2278                    }
2279                    #[cfg(not(feature = "xml"))]
2280                    {
2281                        extension = Some(map.next_value()?);
2282                    }
2283                }
2284                "value" => {
2285                    if value.is_some() {
2286                        return Err(de::Error::duplicate_field("value"));
2287                    }
2288                    // Deserialize directly into Option<V>
2289                    value = Some(map.next_value()?);
2290                }
2291                // Ignore any unknown fields encountered
2292                _ => {
2293                    let _ = map.next_value::<de::IgnoredAny>()?;
2294                }
2295            }
2296        }
2297
2298        Ok(Element {
2299            id,
2300            extension,
2301            value,
2302        })
2303    }
2304}
2305
2306/// Generic element container supporting FHIR's extension mechanism.
2307///
2308/// In FHIR, most primitive elements can be extended with additional metadata
2309/// through the `id` and `extension` fields. This container type provides
2310/// the infrastructure to support this pattern across all FHIR data types.
2311///
2312/// # Type Parameters
2313///
2314/// * `V` - The value type (e.g., `String`, `i32`, `PreciseDecimal`)
2315/// * `E` - The extension type (typically the generated `Extension` struct)
2316///
2317/// # FHIR Element Structure
2318///
2319/// FHIR elements can appear in three forms:
2320/// 1. **Primitive value**: Just the value itself (e.g., `"text"`, `42`)
2321/// 2. **Extended primitive**: An object with `value`, `id`, and/or `extension` fields
2322/// 3. **Extension-only**: An object with just `id` and/or `extension` (no value)
2323///
2324/// # Examples
2325///
2326/// ```rust
2327/// use helios_fhir::{Element, r4::Extension};
2328///
2329/// // Simple primitive value
2330/// let simple: Element<String, Extension> = Element {
2331///     value: Some("Hello World".to_string()),
2332///     id: None,
2333///     extension: None,
2334/// };
2335///
2336/// // Extended primitive with ID
2337/// let with_id: Element<String, Extension> = Element {
2338///     value: Some("Hello World".to_string()),
2339///     id: Some("text-element-1".to_string()),
2340///     extension: None,
2341/// };
2342///
2343/// // Extension-only element (no value)
2344/// let extension_only: Element<String, Extension> = Element {
2345///     value: None,
2346///     id: Some("disabled-element".to_string()),
2347///     extension: Some(vec![/* extensions */]),
2348/// };
2349/// ```
2350///
2351/// # Serialization Behavior
2352///
2353/// - If only `value` is present: serializes as the primitive value directly
2354/// - If `id` or `extension` are present: serializes as an object with all fields
2355/// - If everything is `None`: serializes as `null`
2356#[derive(Debug, PartialEq, Eq, Clone, Default)]
2357pub struct Element<V, E> {
2358    /// Optional element identifier for referencing within the resource
2359    pub id: Option<String>,
2360    /// Optional extensions providing additional metadata
2361    pub extension: Option<Vec<E>>,
2362    /// The actual primitive value
2363    pub value: Option<V>,
2364}
2365
2366impl<V, E> Element<V, E> {
2367    /// Returns true when no value, id, or extensions are present.
2368    pub fn is_empty(&self) -> bool {
2369        self.value.is_none()
2370            && self.id.is_none()
2371            && self.extension.as_ref().is_none_or(|ext| ext.is_empty())
2372    }
2373}
2374
2375// Custom Deserialize for Element<V, E>
2376// Remove PartialEq/Eq bounds for V and E as they are not needed for deserialization itself
2377impl<'de, V, E> Deserialize<'de> for Element<V, E>
2378where
2379    V: Deserialize<'de> + 'static, // Added 'static for TypeId comparisons
2380    E: Deserialize<'de>,           // Removed PartialEq
2381{
2382    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2383    where
2384        D: Deserializer<'de>,
2385    {
2386        // Use the AnyValueVisitor approach to handle different JSON input types
2387        struct AnyValueVisitor<V, E>(PhantomData<(V, E)>);
2388
2389        impl<'de, V, E> Visitor<'de> for AnyValueVisitor<V, E>
2390        where
2391            V: Deserialize<'de> + 'static,
2392            E: Deserialize<'de>,
2393        {
2394            type Value = Element<V, E>;
2395
2396            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
2397                formatter
2398                    .write_str("a primitive value (string, number, boolean), an object, or null")
2399            }
2400
2401            // Handle primitive types by attempting to deserialize V and wrapping it
2402            fn visit_bool<Er>(self, v: bool) -> Result<Self::Value, Er>
2403            where
2404                Er: de::Error,
2405            {
2406                V::deserialize(de::value::BoolDeserializer::new(v)).map(|value| Element {
2407                    id: None,
2408                    extension: None,
2409                    value: Some(value),
2410                })
2411            }
2412            fn visit_i64<Er>(self, v: i64) -> Result<Self::Value, Er>
2413            where
2414                Er: de::Error,
2415            {
2416                V::deserialize(de::value::I64Deserializer::new(v)).map(|value| Element {
2417                    id: None,
2418                    extension: None,
2419                    value: Some(value),
2420                })
2421            }
2422            fn visit_u64<Er>(self, v: u64) -> Result<Self::Value, Er>
2423            where
2424                Er: de::Error,
2425            {
2426                V::deserialize(de::value::U64Deserializer::new(v)).map(|value| Element {
2427                    id: None,
2428                    extension: None,
2429                    value: Some(value),
2430                })
2431            }
2432            fn visit_f64<Er>(self, v: f64) -> Result<Self::Value, Er>
2433            where
2434                Er: de::Error,
2435            {
2436                V::deserialize(de::value::F64Deserializer::new(v)).map(|value| Element {
2437                    id: None,
2438                    extension: None,
2439                    value: Some(value),
2440                })
2441            }
2442            fn visit_str<Er>(self, v: &str) -> Result<Self::Value, Er>
2443            where
2444                Er: de::Error,
2445            {
2446                use std::any::TypeId;
2447
2448                // Try to handle numeric strings for integer types
2449                if TypeId::of::<V>() == TypeId::of::<i64>() {
2450                    if let Ok(int_val) = v.parse::<i64>() {
2451                        return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
2452                            |value| Element {
2453                                id: None,
2454                                extension: None,
2455                                value: Some(value),
2456                            },
2457                        );
2458                    }
2459                } else if TypeId::of::<V>() == TypeId::of::<i32>() {
2460                    if let Ok(int_val) = v.parse::<i32>() {
2461                        return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
2462                            |value| Element {
2463                                id: None,
2464                                extension: None,
2465                                value: Some(value),
2466                            },
2467                        );
2468                    }
2469                } else if TypeId::of::<V>() == TypeId::of::<u64>() {
2470                    if let Ok(int_val) = v.parse::<u64>() {
2471                        return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
2472                            |value| Element {
2473                                id: None,
2474                                extension: None,
2475                                value: Some(value),
2476                            },
2477                        );
2478                    }
2479                } else if TypeId::of::<V>() == TypeId::of::<u32>() {
2480                    if let Ok(int_val) = v.parse::<u32>() {
2481                        return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
2482                            |value| Element {
2483                                id: None,
2484                                extension: None,
2485                                value: Some(value),
2486                            },
2487                        );
2488                    }
2489                }
2490
2491                // Fall back to normal string deserialization
2492                V::deserialize(de::value::StrDeserializer::new(v)).map(|value| Element {
2493                    id: None,
2494                    extension: None,
2495                    value: Some(value),
2496                })
2497            }
2498            fn visit_string<Er>(self, v: String) -> Result<Self::Value, Er>
2499            where
2500                Er: de::Error,
2501            {
2502                use std::any::TypeId;
2503
2504                // Try to handle numeric strings for integer types
2505                if TypeId::of::<V>() == TypeId::of::<i64>() {
2506                    if let Ok(int_val) = v.parse::<i64>() {
2507                        return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
2508                            |value| Element {
2509                                id: None,
2510                                extension: None,
2511                                value: Some(value),
2512                            },
2513                        );
2514                    }
2515                } else if TypeId::of::<V>() == TypeId::of::<i32>() {
2516                    if let Ok(int_val) = v.parse::<i32>() {
2517                        return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
2518                            |value| Element {
2519                                id: None,
2520                                extension: None,
2521                                value: Some(value),
2522                            },
2523                        );
2524                    }
2525                } else if TypeId::of::<V>() == TypeId::of::<u64>() {
2526                    if let Ok(int_val) = v.parse::<u64>() {
2527                        return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
2528                            |value| Element {
2529                                id: None,
2530                                extension: None,
2531                                value: Some(value),
2532                            },
2533                        );
2534                    }
2535                } else if TypeId::of::<V>() == TypeId::of::<u32>() {
2536                    if let Ok(int_val) = v.parse::<u32>() {
2537                        return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
2538                            |value| Element {
2539                                id: None,
2540                                extension: None,
2541                                value: Some(value),
2542                            },
2543                        );
2544                    }
2545                }
2546
2547                // Fall back to normal string deserialization
2548                V::deserialize(de::value::StringDeserializer::new(v.clone())).map(|value| Element {
2549                    // Clone v for error message
2550                    id: None,
2551                    extension: None,
2552                    value: Some(value),
2553                })
2554            }
2555            fn visit_borrowed_str<Er>(self, v: &'de str) -> Result<Self::Value, Er>
2556            where
2557                Er: de::Error,
2558            {
2559                use std::any::TypeId;
2560
2561                // Try to handle numeric strings for integer types
2562                if TypeId::of::<V>() == TypeId::of::<i64>() {
2563                    if let Ok(int_val) = v.parse::<i64>() {
2564                        return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
2565                            |value| Element {
2566                                id: None,
2567                                extension: None,
2568                                value: Some(value),
2569                            },
2570                        );
2571                    }
2572                } else if TypeId::of::<V>() == TypeId::of::<i32>() {
2573                    if let Ok(int_val) = v.parse::<i32>() {
2574                        return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
2575                            |value| Element {
2576                                id: None,
2577                                extension: None,
2578                                value: Some(value),
2579                            },
2580                        );
2581                    }
2582                } else if TypeId::of::<V>() == TypeId::of::<u64>() {
2583                    if let Ok(int_val) = v.parse::<u64>() {
2584                        return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
2585                            |value| Element {
2586                                id: None,
2587                                extension: None,
2588                                value: Some(value),
2589                            },
2590                        );
2591                    }
2592                } else if TypeId::of::<V>() == TypeId::of::<u32>() {
2593                    if let Ok(int_val) = v.parse::<u32>() {
2594                        return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
2595                            |value| Element {
2596                                id: None,
2597                                extension: None,
2598                                value: Some(value),
2599                            },
2600                        );
2601                    }
2602                }
2603
2604                // Fall back to normal string deserialization
2605                V::deserialize(de::value::BorrowedStrDeserializer::new(v)).map(|value| Element {
2606                    id: None,
2607                    extension: None,
2608                    value: Some(value),
2609                })
2610            }
2611            fn visit_bytes<Er>(self, v: &[u8]) -> Result<Self::Value, Er>
2612            where
2613                Er: de::Error,
2614            {
2615                V::deserialize(de::value::BytesDeserializer::new(v)).map(|value| Element {
2616                    id: None,
2617                    extension: None,
2618                    value: Some(value),
2619                })
2620            }
2621            fn visit_byte_buf<Er>(self, v: Vec<u8>) -> Result<Self::Value, Er>
2622            where
2623                Er: de::Error,
2624            {
2625                // Use BytesDeserializer with a slice reference &v
2626                V::deserialize(de::value::BytesDeserializer::new(&v)).map(|value| Element {
2627                    id: None,
2628                    extension: None,
2629                    value: Some(value),
2630                })
2631            }
2632
2633            // Handle null
2634            fn visit_none<Er>(self) -> Result<Self::Value, Er>
2635            where
2636                Er: de::Error,
2637            {
2638                Ok(Element {
2639                    id: None,
2640                    extension: None,
2641                    value: None,
2642                })
2643            }
2644            fn visit_unit<Er>(self) -> Result<Self::Value, Er>
2645            where
2646                Er: de::Error,
2647            {
2648                Ok(Element {
2649                    id: None,
2650                    extension: None,
2651                    value: None,
2652                })
2653            }
2654
2655            // Handle Option<T> by visiting Some
2656            fn visit_some<De>(self, deserializer: De) -> Result<Self::Value, De::Error>
2657            where
2658                De: Deserializer<'de>,
2659            {
2660                // Re-dispatch to deserialize_any to handle the inner type correctly
2661                deserializer.deserialize_any(self)
2662            }
2663
2664            // Handle object
2665            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
2666            where
2667                A: MapAccess<'de>,
2668            {
2669                // Deserialize the map using ElementObjectVisitor
2670                // Need to create a deserializer from the map access
2671                let map_deserializer = de::value::MapAccessDeserializer::new(map);
2672                map_deserializer.deserialize_map(ElementObjectVisitor(PhantomData))
2673            }
2674
2675            // We don't expect sequences for a single Element
2676            fn visit_seq<A>(self, _seq: A) -> Result<Self::Value, A::Error>
2677            where
2678                A: de::SeqAccess<'de>,
2679            {
2680                Err(de::Error::invalid_type(de::Unexpected::Seq, &self))
2681            }
2682        }
2683
2684        // Start deserialization using the visitor
2685        deserializer.deserialize_any(AnyValueVisitor(PhantomData))
2686    }
2687}
2688
2689// Custom Serialize for Element<V, E>
2690// Remove PartialEq/Eq bounds for V and E as they are not needed for serialization itself
2691impl<V, E> Serialize for Element<V, E>
2692where
2693    V: Serialize, // Removed PartialEq + Eq
2694    E: Serialize, // Removed PartialEq
2695{
2696    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2697    where
2698        S: Serializer,
2699    {
2700        // If id and extension are None, serialize value directly (or null)
2701        if self.id.is_none() && self.extension.is_none() {
2702            match &self.value {
2703                Some(val) => val.serialize(serializer),
2704                None => serializer.serialize_none(),
2705            }
2706        } else {
2707            // Otherwise, serialize as an object containing id, extension, value if present
2708            let mut len = 0;
2709            if self.id.is_some() {
2710                len += 1;
2711            }
2712            if self.extension.is_some() {
2713                len += 1;
2714            }
2715            if self.value.is_some() {
2716                len += 1;
2717            }
2718
2719            let mut state = serializer.serialize_struct("Element", len)?;
2720            if let Some(id) = &self.id {
2721                state.serialize_field("id", id)?;
2722            }
2723            if let Some(extension) = &self.extension {
2724                state.serialize_field("extension", extension)?;
2725            }
2726            // Restore value serialization for direct Element serialization
2727            if let Some(value) = &self.value {
2728                state.serialize_field("value", value)?;
2729            }
2730            state.end()
2731        }
2732    }
2733}
2734
2735/// Specialized element container for FHIR decimal values with precision preservation.
2736///
2737/// This type combines the generic `Element` pattern with `PreciseDecimal` to provide
2738/// a complete solution for FHIR decimal elements that require both extension support
2739/// and precision preservation during serialization round-trips.
2740///
2741/// # Type Parameters
2742///
2743/// * `E` - The extension type (typically the generated `Extension` struct)
2744///
2745/// # FHIR Decimal Requirements
2746///
2747/// FHIR decimal elements must:
2748/// - Preserve original string precision (e.g., "12.30" vs "12.3")
2749/// - Support mathematical operations using `Decimal` arithmetic
2750/// - Handle extension metadata through `id` and `extension` fields
2751/// - Serialize back to the exact original format when possible
2752///
2753/// # Examples
2754///
2755/// ```rust
2756/// use helios_fhir::{DecimalElement, PreciseDecimal, r4::Extension};
2757/// use rust_decimal::Decimal;
2758///
2759/// // Create from a Decimal value
2760/// let decimal_elem = DecimalElement::<Extension>::new(Decimal::new(1234, 2)); // 12.34
2761///
2762/// // Create with extensions
2763/// let extended_decimal: DecimalElement<Extension> = DecimalElement {
2764///     value: Some(PreciseDecimal::from_parts(
2765///         Some(Decimal::new(12300, 3)),
2766///         "12.300".to_string()
2767///     )),
2768///     id: Some("precision-example".to_string()),
2769///     extension: Some(vec![/* extensions */]),
2770/// };
2771///
2772/// // Access the mathematical value
2773/// if let Some(precise) = &extended_decimal.value {
2774///     if let Some(decimal_val) = precise.value() {
2775///         println!("Mathematical value: {}", decimal_val);
2776///     }
2777///     println!("Original format: {}", precise.original_string());
2778/// }
2779/// ```
2780///
2781/// # Serialization Behavior
2782///
2783/// - **Value only**: Serializes as a JSON number preserving original precision
2784/// - **With extensions**: Serializes as an object with `value`, `id`, and `extension` fields
2785/// - **No value**: Serializes as an object with just the extension fields, or `null` if empty
2786///
2787/// # Integration with FHIRPath
2788///
2789/// When used with FHIRPath evaluation, `DecimalElement` returns:
2790/// - The `Decimal` value for mathematical operations
2791/// - An object representation when extension metadata is accessed
2792/// - Empty collection when the element has no value or extensions
2793#[derive(Debug, PartialEq, Eq, Clone, Default)]
2794pub struct DecimalElement<E> {
2795    /// Optional element identifier for referencing within the resource
2796    pub id: Option<String>,
2797    /// Optional extensions providing additional metadata
2798    pub extension: Option<Vec<E>>,
2799    /// The decimal value with precision preservation
2800    pub value: Option<PreciseDecimal>,
2801}
2802
2803impl<E> DecimalElement<E> {
2804    /// Creates a new `DecimalElement` with the specified decimal value.
2805    ///
2806    /// This constructor creates a simple decimal element with no extensions or ID,
2807    /// containing only the decimal value. The original string representation is
2808    /// automatically derived from the `Decimal` value's `Display` implementation.
2809    ///
2810    /// # Arguments
2811    ///
2812    /// * `value` - The `Decimal` value to store
2813    ///
2814    /// # Returns
2815    ///
2816    /// A new `DecimalElement` with the value set and `id`/`extension` as `None`.
2817    ///
2818    /// # Examples
2819    ///
2820    /// ```rust
2821    /// use helios_fhir::{DecimalElement, r4::Extension};
2822    /// use rust_decimal::Decimal;
2823    ///
2824    /// // Create a simple decimal element
2825    /// let element = DecimalElement::<Extension>::new(Decimal::new(12345, 3)); // 12.345
2826    ///
2827    /// // Verify the structure
2828    /// assert!(element.id.is_none());
2829    /// assert!(element.extension.is_none());
2830    /// assert!(element.value.is_some());
2831    ///
2832    /// // Access the decimal value
2833    /// if let Some(precise_decimal) = &element.value {
2834    ///     assert_eq!(precise_decimal.value(), Some(Decimal::new(12345, 3)));
2835    ///     assert_eq!(precise_decimal.original_string(), "12.345");
2836    /// }
2837    /// ```
2838    ///
2839    /// # Usage in FHIR Resources
2840    ///
2841    /// This method is typically used when creating FHIR elements programmatically:
2842    ///
2843    /// ```rust
2844    /// use helios_fhir::{DecimalElement, r4::{Extension, Observation}};
2845    /// use rust_decimal::Decimal;
2846    ///
2847    /// let temperature = DecimalElement::<Extension>::new(Decimal::new(3672, 2)); // 36.72
2848    ///
2849    /// // Would be used in an Observation like:
2850    /// // observation.value_quantity.value = Some(temperature);
2851    /// ```
2852    pub fn new(value: Decimal) -> Self {
2853        // Convert the Decimal to PreciseDecimal, which automatically handles
2854        // storing the original string representation via the From trait
2855        let precise_value = PreciseDecimal::from(value);
2856        Self {
2857            id: None,
2858            extension: None,
2859            value: Some(precise_value),
2860        }
2861    }
2862}
2863
2864// Custom Deserialize for DecimalElement<E> using intermediate Value
2865impl<'de, E> Deserialize<'de> for DecimalElement<E>
2866where
2867    E: Deserialize<'de> + Default,
2868{
2869    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2870    where
2871        D: Deserializer<'de>,
2872    {
2873        // Deserialize into an intermediate serde_json::Value first
2874        let json_value = serde_json::Value::deserialize(deserializer)?;
2875
2876        match json_value {
2877            // Handle primitive JSON Number
2878            serde_json::Value::Number(n) => {
2879                // Directly parse the number string to create PreciseDecimal
2880                let s = n.to_string(); // Note: n.to_string() might normalize exponent case (e.g., 'E' -> 'e')
2881                // Replace 'E' with 'e' for parsing
2882                let s_for_parsing = s.replace('E', "e");
2883                // Use from_scientific if 'e' is present, otherwise parse
2884                let parsed_value = if s_for_parsing.contains('e') {
2885                    Decimal::from_scientific(&s_for_parsing).ok()
2886                } else {
2887                    s_for_parsing.parse::<Decimal>().ok()
2888                };
2889                // Store the ORIGINAL string `s` (as returned by n.to_string()).
2890                let pd = PreciseDecimal::from_parts(parsed_value, s);
2891                Ok(DecimalElement {
2892                    id: None,
2893                    extension: None,
2894                    value: Some(pd),
2895                })
2896            }
2897            // Handle primitive JSON String
2898            serde_json::Value::String(s) => {
2899                // Directly parse the string to create PreciseDecimal
2900                // Replace 'E' with 'e' for parsing
2901                let s_for_parsing = s.replace('E', "e");
2902                // Use from_scientific if 'e' is present, otherwise parse
2903                let parsed_value = if s_for_parsing.contains('e') {
2904                    Decimal::from_scientific(&s_for_parsing).ok()
2905                } else {
2906                    s_for_parsing.parse::<Decimal>().ok()
2907                };
2908                // Store the ORIGINAL string `s`.
2909                let pd = PreciseDecimal::from_parts(parsed_value, s); // s is owned, no clone needed
2910                Ok(DecimalElement {
2911                    id: None,
2912                    extension: None,
2913                    value: Some(pd),
2914                })
2915            }
2916            // Handle JSON object: deserialize fields individually
2917            serde_json::Value::Object(map) => {
2918                let mut id: Option<String> = None;
2919                let mut extension: Option<Vec<E>> = None;
2920                let mut value: Option<PreciseDecimal> = None;
2921
2922                for (k, v) in map {
2923                    match k.as_str() {
2924                        "id" => {
2925                            if id.is_some() {
2926                                return Err(de::Error::duplicate_field("id"));
2927                            }
2928                            // Deserialize id directly from its Value
2929                            id = Deserialize::deserialize(v).map_err(de::Error::custom)?;
2930                        }
2931                        "extension" => {
2932                            if extension.is_some() {
2933                                return Err(de::Error::duplicate_field("extension"));
2934                            }
2935                            #[cfg(feature = "xml")]
2936                            {
2937                                let single_or_vec: SingleOrVec<E> =
2938                                    Deserialize::deserialize(v).map_err(de::Error::custom)?;
2939                                extension = Some(single_or_vec.into());
2940                            }
2941                            #[cfg(not(feature = "xml"))]
2942                            {
2943                                extension =
2944                                    Deserialize::deserialize(v).map_err(de::Error::custom)?;
2945                            }
2946                        }
2947                        "value" => {
2948                            if value.is_some() {
2949                                return Err(de::Error::duplicate_field("value"));
2950                            }
2951                            // Deserialize value using PreciseDecimal::deserialize from its Value
2952                            // Handle null explicitly within the value field
2953                            if v.is_null() {
2954                                value = None;
2955                            } else {
2956                                value = Some(
2957                                    PreciseDecimal::deserialize(v).map_err(de::Error::custom)?,
2958                                );
2959                            }
2960                        }
2961                        // Ignore any unknown fields encountered
2962                        _ => {} // Simply ignore unknown fields
2963                    }
2964                }
2965                Ok(DecimalElement {
2966                    id,
2967                    extension,
2968                    value,
2969                })
2970            }
2971            // Handle JSON Null for the whole element
2972            serde_json::Value::Null => Ok(DecimalElement::default()), // Default has value: None
2973            // Handle other unexpected types
2974            other => Err(de::Error::invalid_type(
2975                match other {
2976                    serde_json::Value::Bool(b) => de::Unexpected::Bool(b),
2977                    serde_json::Value::Array(_) => de::Unexpected::Seq,
2978                    _ => de::Unexpected::Other("unexpected JSON type for DecimalElement"),
2979                },
2980                &"a decimal number, string, object, or null",
2981            )),
2982        }
2983    }
2984}
2985
2986// Reinstate custom Serialize implementation for DecimalElement
2987// Remove PartialEq bound for E
2988impl<E> Serialize for DecimalElement<E>
2989where
2990    E: Serialize, // Removed PartialEq bound for E
2991{
2992    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2993    where
2994        S: Serializer,
2995    {
2996        // If we only have a value and no other fields, serialize just the value
2997        if self.id.is_none() && self.extension.is_none() {
2998            if let Some(value) = &self.value {
2999                // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
3000                return value.serialize(serializer);
3001            } else {
3002                // If value is also None, serialize as null
3003                // based on updated test_serialize_decimal_with_no_fields
3004                return serializer.serialize_none();
3005            }
3006        }
3007
3008        // Otherwise, serialize as a struct with all present fields
3009        // Calculate the number of fields that are NOT None
3010        let mut len = 0;
3011        if self.id.is_some() {
3012            len += 1;
3013        }
3014        if self.extension.is_some() {
3015            len += 1;
3016        }
3017        if self.value.is_some() {
3018            len += 1;
3019        }
3020
3021        // Start serializing a struct with the calculated length
3022        let mut state = serializer.serialize_struct("DecimalElement", len)?;
3023
3024        // Serialize 'id' field if it's Some
3025        if let Some(id) = &self.id {
3026            state.serialize_field("id", id)?;
3027        }
3028
3029        // Serialize 'extension' field if it's Some
3030        if let Some(extension) = &self.extension {
3031            state.serialize_field("extension", extension)?;
3032        }
3033
3034        // Serialize 'value' field if it's Some
3035        if let Some(value) = &self.value {
3036            // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
3037            state.serialize_field("value", value)?;
3038        }
3039
3040        // End the struct serialization
3041        state.end()
3042    }
3043}
3044
3045// For Element<V, E> - Returns Object with id, extension, value if present
3046impl<V, E> IntoEvaluationResult for Element<V, E>
3047where
3048    V: IntoEvaluationResult + Clone + 'static,
3049    E: IntoEvaluationResult + Clone,
3050{
3051    fn to_evaluation_result(&self) -> EvaluationResult {
3052        use helios_fhirpath_support::PrimitiveElement;
3053        use std::any::TypeId;
3054
3055        // Build PrimitiveElement metadata from id/extension (used when value is also present)
3056        let primitive_meta = if self.id.is_some() || self.extension.is_some() {
3057            let mut meta = PrimitiveElement::default();
3058            if let Some(id) = &self.id {
3059                meta.id = Some(id.clone());
3060            }
3061            if let Some(ext) = &self.extension {
3062                meta.extension = ext.iter().map(|e| e.to_evaluation_result()).collect();
3063            }
3064            if !meta.is_empty() { Some(meta) } else { None }
3065        } else {
3066            None
3067        };
3068
3069        // Prioritize returning the primitive value if it exists
3070        if let Some(v) = &self.value {
3071            let result = v.to_evaluation_result();
3072            // For primitive values, we need to preserve FHIR type information
3073            let typed = match result {
3074                EvaluationResult::Boolean(b, _, _) => EvaluationResult::fhir_boolean(b),
3075                EvaluationResult::Integer(i, _, _) => EvaluationResult::fhir_integer(i),
3076                #[cfg(not(any(feature = "R4", feature = "R4B")))]
3077                EvaluationResult::Integer64(i, _, _) => EvaluationResult::fhir_integer64(i),
3078                EvaluationResult::String(s, _, _) => EvaluationResult::fhir_string(s, "string"),
3079                EvaluationResult::DateTime(dt, type_info, _) => {
3080                    if TypeId::of::<V>() == TypeId::of::<PrecisionInstant>() {
3081                        EvaluationResult::DateTime(
3082                            dt,
3083                            Some(TypeInfoResult::new("FHIR", "instant")),
3084                            None,
3085                        )
3086                    } else {
3087                        EvaluationResult::DateTime(dt, type_info, None)
3088                    }
3089                }
3090                other => other,
3091            };
3092            return match primitive_meta {
3093                Some(meta) => typed.with_primitive_element(meta),
3094                None => typed,
3095            };
3096        } else if self.id.is_some() || self.extension.is_some() {
3097            // If value is None, but id or extension exist, return an Object with those
3098            let mut map = std::collections::HashMap::new();
3099            if let Some(id) = &self.id {
3100                map.insert("id".to_string(), EvaluationResult::string(id.clone()));
3101            }
3102            if let Some(ext) = &self.extension {
3103                let ext_collection: Vec<EvaluationResult> =
3104                    ext.iter().map(|e| e.to_evaluation_result()).collect();
3105                if !ext_collection.is_empty() {
3106                    map.insert(
3107                        "extension".to_string(),
3108                        EvaluationResult::collection(ext_collection),
3109                    );
3110                }
3111            }
3112            // Only return Object if map is not empty (i.e., id or extension was actually present)
3113            if !map.is_empty() {
3114                return EvaluationResult::typed_object(map, "FHIR", "Element");
3115            }
3116        }
3117
3118        // If value, id, and extension are all None, return Empty
3119        EvaluationResult::Empty
3120    }
3121}
3122
3123// For DecimalElement<E> - Returns Decimal value if present, otherwise handles id/extension
3124impl<E> IntoEvaluationResult for DecimalElement<E>
3125where
3126    E: IntoEvaluationResult + Clone,
3127{
3128    fn to_evaluation_result(&self) -> EvaluationResult {
3129        use helios_fhirpath_support::PrimitiveElement;
3130
3131        // Build PrimitiveElement metadata from id/extension
3132        let primitive_meta = if self.id.is_some() || self.extension.is_some() {
3133            let mut meta = PrimitiveElement::default();
3134            if let Some(id) = &self.id {
3135                meta.id = Some(id.clone());
3136            }
3137            if let Some(ext) = &self.extension {
3138                meta.extension = ext.iter().map(|e| e.to_evaluation_result()).collect();
3139            }
3140            if !meta.is_empty() { Some(meta) } else { None }
3141        } else {
3142            None
3143        };
3144
3145        // Prioritize returning the primitive decimal value if it exists
3146        if let Some(precise_decimal) = &self.value {
3147            if let Some(decimal_val) = precise_decimal.value() {
3148                let result = EvaluationResult::fhir_decimal(decimal_val);
3149                return match primitive_meta {
3150                    Some(meta) => result.with_primitive_element(meta),
3151                    None => result,
3152                };
3153            }
3154            // If PreciseDecimal holds None for value, fall through to check id/extension
3155        }
3156
3157        // If value is None, but id or extension exist, return an Object with those
3158        if self.id.is_some() || self.extension.is_some() {
3159            let mut map = std::collections::HashMap::new();
3160            if let Some(id) = &self.id {
3161                map.insert("id".to_string(), EvaluationResult::string(id.clone()));
3162            }
3163            if let Some(ext) = &self.extension {
3164                let ext_collection: Vec<EvaluationResult> =
3165                    ext.iter().map(|e| e.to_evaluation_result()).collect();
3166                if !ext_collection.is_empty() {
3167                    map.insert(
3168                        "extension".to_string(),
3169                        EvaluationResult::collection(ext_collection),
3170                    );
3171                }
3172            }
3173            // Only return Object if map is not empty
3174            if !map.is_empty() {
3175                return EvaluationResult::typed_object(map, "FHIR", "decimal");
3176            }
3177        }
3178
3179        // If value, id, and extension are all None, return Empty
3180        EvaluationResult::Empty
3181    }
3182}
3183
3184// Implement the trait for the top-level enum
3185impl IntoEvaluationResult for FhirResource {
3186    fn to_evaluation_result(&self) -> EvaluationResult {
3187        match self {
3188            #[cfg(feature = "R4")]
3189            FhirResource::R4(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4::Resource>
3190            #[cfg(feature = "R4B")]
3191            FhirResource::R4B(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4b::Resource>
3192            #[cfg(feature = "R5")]
3193            FhirResource::R5(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r5::Resource>
3194            #[cfg(feature = "R6")]
3195            FhirResource::R6(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r6::Resource>
3196                                                                // Note: If no features are enabled, this match might be empty or non-exhaustive.
3197                                                                // This is generally okay as the enum itself wouldn't be usable.
3198        }
3199    }
3200}
3201
3202#[cfg(test)]
3203mod tests {
3204    use super::*;
3205
3206    #[test]
3207    fn field_to_element_name_inverts_the_generator() {
3208        assert_eq!(field_to_element_name("birth_date"), "birthDate");
3209        assert_eq!(
3210            field_to_element_name("managing_organization"),
3211            "managingOrganization"
3212        );
3213        assert_eq!(field_to_element_name("id"), "id");
3214        assert_eq!(field_to_element_name("implicit_rules"), "implicitRules");
3215        // Every raw identifier `make_rust_safe` can emit (#1107).
3216        assert_eq!(field_to_element_name("r#type"), "type");
3217        assert_eq!(field_to_element_name("r#use"), "use");
3218        assert_eq!(field_to_element_name("r#abstract"), "abstract");
3219        assert_eq!(field_to_element_name("r#for"), "for");
3220    }
3221
3222    /// The raw identifiers `make_rust_safe` emits reach every consumer as
3223    /// plain element names — no `r#` may survive for any resource type in
3224    /// any enabled version (#1107).
3225    #[test]
3226    fn summary_elements_never_leak_raw_identifiers() {
3227        let checks: &[(FhirVersion, &str, &[&str])] = &[
3228            #[cfg(feature = "R4")]
3229            (FhirVersion::R4, "Claim", &["type", "use", "billablePeriod"]),
3230            #[cfg(feature = "R4")]
3231            (
3232                FhirVersion::R4,
3233                "StructureDefinition",
3234                &["abstract", "type"],
3235            ),
3236            #[cfg(feature = "R4")]
3237            (FhirVersion::R4, "Task", &["for", "status"]),
3238            #[cfg(feature = "R4B")]
3239            (
3240                FhirVersion::R4B,
3241                "Claim",
3242                &["type", "use", "billablePeriod"],
3243            ),
3244            #[cfg(feature = "R5")]
3245            (FhirVersion::R5, "Claim", &["type", "use", "billablePeriod"]),
3246            #[cfg(feature = "R6")]
3247            (FhirVersion::R6, "Claim", &["type", "use", "billablePeriod"]),
3248        ];
3249        for (version, resource_type, expected) in checks {
3250            let elements = summary_elements(*version, resource_type);
3251            for e in *expected {
3252                assert!(
3253                    elements.iter().any(|x| x == e),
3254                    "{version:?} {resource_type} summary {elements:?} lacks {e}"
3255                );
3256            }
3257            assert!(
3258                elements
3259                    .iter()
3260                    .all(|e| !e.contains("r#") && !e.contains('_')),
3261                "{version:?} {resource_type}: {elements:?}"
3262            );
3263        }
3264        #[cfg(feature = "R4")]
3265        assert_eq!(
3266            summary_elements(FhirVersion::R4, "NotAResource"),
3267            ["resourceType", "id", "meta"],
3268            "unknown types get the generated lookup's minimal set"
3269        );
3270    }
3271
3272    #[test]
3273    fn test_integer_string_deserialization() {
3274        // Test deserializing a string "2" into Element<i64, ()>
3275        type TestElement = Element<i64, ()>;
3276
3277        // Test case 1: String containing integer
3278        let json_str = r#""2""#;
3279        let result: Result<TestElement, _> = serde_json::from_str(json_str);
3280        assert!(
3281            result.is_ok(),
3282            "Failed to deserialize string '2' as i64: {:?}",
3283            result.err()
3284        );
3285
3286        let element = result.unwrap();
3287        assert_eq!(element.value, Some(2i64));
3288        assert_eq!(element.id, None);
3289        assert_eq!(element.extension, None);
3290
3291        // Test case 2: Number
3292        let json_num = r#"2"#;
3293        let result: Result<TestElement, _> = serde_json::from_str(json_num);
3294        assert!(
3295            result.is_ok(),
3296            "Failed to deserialize number 2 as i64: {:?}",
3297            result.err()
3298        );
3299
3300        let element = result.unwrap();
3301        assert_eq!(element.value, Some(2i64));
3302    }
3303
3304    #[test]
3305    fn test_i32_string_deserialization() {
3306        type TestElement = Element<i32, ()>;
3307
3308        let json_str = r#""123""#;
3309        let result: Result<TestElement, _> = serde_json::from_str(json_str);
3310        assert!(result.is_ok());
3311
3312        let element = result.unwrap();
3313        assert_eq!(element.value, Some(123i32));
3314    }
3315
3316    #[test]
3317    fn test_invalid_string_fallback() {
3318        type TestElement = Element<i64, ()>;
3319
3320        // Non-numeric string should fail for integer type
3321        let json_str = r#""not_a_number""#;
3322        let result: Result<TestElement, _> = serde_json::from_str(json_str);
3323        assert!(
3324            result.is_err(),
3325            "Should fail to deserialize non-numeric string as i64"
3326        );
3327    }
3328}