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