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// Removed the FhirSerde trait definition
1436
1437/// Multi-version FHIR resource container supporting version-agnostic operations.
1438///
1439/// This enum provides a unified interface for working with FHIR resources across
1440/// different specification versions. It enables applications to handle multiple
1441/// FHIR versions simultaneously while maintaining type safety and version-specific
1442/// behavior where needed.
1443///
1444/// # Supported Versions
1445///
1446/// - **R4**: FHIR 4.0.1 (normative)
1447/// - **R4B**: FHIR 4.3.0 (ballot)
1448/// - **R5**: FHIR 5.0.0 (ballot)
1449/// - **R6**: FHIR 6.0.0 (draft)
1450///
1451/// # Feature Flags
1452///
1453/// Each FHIR version is controlled by a corresponding Cargo feature flag.
1454/// Only enabled versions will be available in the enum variants.
1455///
1456/// # Examples
1457///
1458/// ```rust
1459/// use helios_fhir::{FhirResource, FhirVersion};
1460/// # #[cfg(feature = "R4")]
1461/// use helios_fhir::r4::{Patient, HumanName};
1462///
1463/// # #[cfg(feature = "R4")]
1464/// {
1465/// // Create an R4 patient
1466/// let patient = Patient {
1467/// name: Some(vec![HumanName {
1468/// family: Some("Doe".to_string().into()),
1469/// given: Some(vec!["John".to_string().into()]),
1470/// ..Default::default()
1471/// }]),
1472/// ..Default::default()
1473/// };
1474///
1475/// // Wrap in version-agnostic container
1476/// let resource = FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(patient)));
1477/// assert_eq!(resource.version(), FhirVersion::R4);
1478/// }
1479/// ```
1480///
1481/// # Version Detection
1482///
1483/// Use the `version()` method to determine which FHIR version a resource uses:
1484///
1485/// ```rust
1486/// # use helios_fhir::{FhirResource, FhirVersion};
1487/// # #[cfg(feature = "R4")]
1488/// # {
1489/// # let resource = FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(Default::default())));
1490/// match resource.version() {
1491/// #[cfg(feature = "R4")]
1492/// FhirVersion::R4 => println!("This is an R4 resource"),
1493/// #[cfg(feature = "R4B")]
1494/// FhirVersion::R4B => println!("This is an R4B resource"),
1495/// #[cfg(feature = "R5")]
1496/// FhirVersion::R5 => println!("This is an R5 resource"),
1497/// #[cfg(feature = "R6")]
1498/// FhirVersion::R6 => println!("This is an R6 resource"),
1499/// }
1500/// # }
1501/// ```
1502#[derive(Debug)]
1503pub enum FhirResource {
1504 /// FHIR 4.0.1 (normative) resource
1505 #[cfg(feature = "R4")]
1506 R4(Box<r4::Resource>),
1507 /// FHIR 4.3.0 (ballot) resource
1508 #[cfg(feature = "R4B")]
1509 R4B(Box<r4b::Resource>),
1510 /// FHIR 5.0.0 (ballot) resource
1511 #[cfg(feature = "R5")]
1512 R5(Box<r5::Resource>),
1513 /// FHIR 6.0.0 (draft) resource
1514 #[cfg(feature = "R6")]
1515 R6(Box<r6::Resource>),
1516}
1517
1518impl FhirResource {
1519 /// Returns the FHIR specification version of this resource.
1520 ///
1521 /// This method provides version detection for multi-version applications,
1522 /// enabling version-specific processing logic and compatibility checks.
1523 ///
1524 /// # Returns
1525 ///
1526 /// The `FhirVersion` enum variant corresponding to this resource's specification.
1527 ///
1528 /// # Examples
1529 ///
1530 /// ```rust
1531 /// use helios_fhir::{FhirResource, FhirVersion};
1532 ///
1533 /// # #[cfg(feature = "R5")]
1534 /// # {
1535 /// # let resource = FhirResource::R5(Box::new(helios_fhir::r5::Resource::Patient(Default::default())));
1536 /// let version = resource.version();
1537 /// assert_eq!(version, FhirVersion::R5);
1538 ///
1539 /// // Use version for conditional logic
1540 /// match version {
1541 /// FhirVersion::R5 => {
1542 /// println!("Processing R5 resource with latest features");
1543 /// },
1544 /// FhirVersion::R4 => {
1545 /// println!("Processing R4 resource with normative features");
1546 /// },
1547 /// _ => {
1548 /// println!("Processing other FHIR version");
1549 /// }
1550 /// }
1551 /// # }
1552 /// ```
1553 pub fn version(&self) -> FhirVersion {
1554 match self {
1555 #[cfg(feature = "R4")]
1556 FhirResource::R4(_) => FhirVersion::R4,
1557 #[cfg(feature = "R4B")]
1558 FhirResource::R4B(_) => FhirVersion::R4B,
1559 #[cfg(feature = "R5")]
1560 FhirResource::R5(_) => FhirVersion::R5,
1561 #[cfg(feature = "R6")]
1562 FhirResource::R6(_) => FhirVersion::R6,
1563 }
1564 }
1565}
1566
1567/// Enumeration of supported FHIR specification versions.
1568///
1569/// This enum represents the different versions of the FHIR (Fast Healthcare
1570/// Interoperability Resources) specification that this library supports.
1571/// Each version represents a specific release of the FHIR standard with
1572/// its own set of features, resources, and compatibility requirements.
1573///
1574/// # Version Status
1575///
1576/// - **R4** (4.0.1): Normative version, widely adopted in production
1577/// - **R4B** (4.3.0): Ballot version with additional features
1578/// - **R5** (5.0.0): Ballot version with significant enhancements
1579/// - **R6** (6.0.0): Draft version under active development
1580///
1581/// # Feature Flags
1582///
1583/// Each version is controlled by a corresponding Cargo feature flag:
1584/// - `R4`: Enables FHIR R4 support
1585/// - `R4B`: Enables FHIR R4B support
1586/// - `R5`: Enables FHIR R5 support
1587/// - `R6`: Enables FHIR R6 support
1588///
1589/// # Examples
1590///
1591/// ```rust
1592/// use helios_fhir::FhirVersion;
1593///
1594/// // Version comparison
1595/// # #[cfg(all(feature = "R4", feature = "R5"))]
1596/// # {
1597/// assert_ne!(FhirVersion::R4, FhirVersion::R5);
1598/// # }
1599///
1600/// // String representation
1601/// # #[cfg(feature = "R4")]
1602/// # {
1603/// let version = FhirVersion::R4;
1604/// assert_eq!(version.as_str(), "R4");
1605/// assert_eq!(version.to_string(), "R4");
1606/// # }
1607/// ```
1608///
1609/// # CLI Integration
1610///
1611/// This enum implements `clap::ValueEnum` for command-line argument parsing:
1612///
1613/// ```rust,no_run
1614/// use clap::Parser;
1615/// use helios_fhir::FhirVersion;
1616///
1617/// #[derive(Parser)]
1618/// struct Args {
1619/// #[arg(value_enum)]
1620/// version: FhirVersion,
1621/// }
1622/// ```
1623#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1624pub enum FhirVersion {
1625 /// FHIR 4.0.1 (normative) - The current normative version
1626 #[cfg(feature = "R4")]
1627 R4,
1628 /// FHIR 4.3.0 (ballot) - Intermediate version with additional features
1629 #[cfg(feature = "R4B")]
1630 R4B,
1631 /// FHIR 5.0.0 (ballot) - Next major version with significant changes
1632 #[cfg(feature = "R5")]
1633 R5,
1634 /// FHIR 6.0.0 (draft) - Future version under development
1635 #[cfg(feature = "R6")]
1636 R6,
1637}
1638
1639impl FhirVersion {
1640 /// Returns the string representation of the FHIR version.
1641 ///
1642 /// This method provides the standard version identifier as used in
1643 /// FHIR documentation, URLs, and configuration files.
1644 ///
1645 /// # Returns
1646 ///
1647 /// A static string slice representing the version (e.g., "R4", "R5").
1648 ///
1649 /// # Examples
1650 ///
1651 /// ```rust
1652 /// use helios_fhir::FhirVersion;
1653 ///
1654 /// # #[cfg(feature = "R4")]
1655 /// assert_eq!(FhirVersion::R4.as_str(), "R4");
1656 /// # #[cfg(feature = "R5")]
1657 /// assert_eq!(FhirVersion::R5.as_str(), "R5");
1658 /// ```
1659 ///
1660 /// # Usage
1661 ///
1662 /// This method is commonly used for:
1663 /// - Logging and debugging output
1664 /// - Configuration file parsing
1665 /// - API endpoint construction
1666 /// - Version-specific resource loading
1667 pub fn as_str(&self) -> &'static str {
1668 match self {
1669 #[cfg(feature = "R4")]
1670 FhirVersion::R4 => "R4",
1671 #[cfg(feature = "R4B")]
1672 FhirVersion::R4B => "R4B",
1673 #[cfg(feature = "R5")]
1674 FhirVersion::R5 => "R5",
1675 #[cfg(feature = "R6")]
1676 FhirVersion::R6 => "R6",
1677 }
1678 }
1679}
1680
1681/// Implements `Display` trait for user-friendly output formatting.
1682///
1683/// This enables `FhirVersion` to be used in string formatting operations
1684/// and provides consistent output across different contexts.
1685///
1686/// # Examples
1687///
1688/// ```rust
1689/// use helios_fhir::FhirVersion;
1690///
1691/// # #[cfg(feature = "R5")]
1692/// # {
1693/// let version = FhirVersion::R5;
1694/// println!("Using FHIR version: {}", version); // Prints: "Using FHIR version: R5"
1695///
1696/// let formatted = format!("fhir-{}.json", version);
1697/// assert_eq!(formatted, "fhir-R5.json");
1698/// # }
1699/// ```
1700impl std::fmt::Display for FhirVersion {
1701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1702 write!(f, "{}", self.as_str())
1703 }
1704}
1705
1706/// Provides a default FHIR version when R4 feature is enabled.
1707///
1708/// R4 is chosen as the default because it is the current normative version
1709/// of the FHIR specification and is widely adopted in production systems.
1710///
1711/// # Examples
1712///
1713/// ```rust
1714/// use helios_fhir::FhirVersion;
1715///
1716/// # #[cfg(feature = "R4")]
1717/// # {
1718/// let default_version = FhirVersion::default();
1719/// assert_eq!(default_version, FhirVersion::R4);
1720/// # }
1721/// ```
1722#[cfg(feature = "R4")]
1723impl Default for FhirVersion {
1724 fn default() -> Self {
1725 FhirVersion::R4
1726 }
1727}
1728
1729/// Implements `clap::ValueEnum` for command-line argument parsing.
1730///
1731/// This implementation enables `FhirVersion` to be used directly as a command-line
1732/// argument type with clap, providing automatic parsing, validation, and help text
1733/// generation.
1734///
1735/// # Examples
1736///
1737/// ```rust,no_run
1738/// use clap::Parser;
1739/// use helios_fhir::FhirVersion;
1740///
1741/// #[derive(Parser)]
1742/// struct Args {
1743/// /// FHIR specification version to use
1744/// #[arg(value_enum, default_value_t = FhirVersion::default())]
1745/// version: FhirVersion,
1746/// }
1747///
1748/// // Command line: my-app --version R5
1749/// let args = Args::parse();
1750/// println!("Using FHIR version: {}", args.version);
1751/// ```
1752///
1753/// # Generated Help Text
1754///
1755/// When using this enum with clap, the help text will automatically include
1756/// all available FHIR versions based on enabled feature flags.
1757impl clap::ValueEnum for FhirVersion {
1758 fn value_variants<'a>() -> &'a [Self] {
1759 &[
1760 #[cfg(feature = "R4")]
1761 FhirVersion::R4,
1762 #[cfg(feature = "R4B")]
1763 FhirVersion::R4B,
1764 #[cfg(feature = "R5")]
1765 FhirVersion::R5,
1766 #[cfg(feature = "R6")]
1767 FhirVersion::R6,
1768 ]
1769 }
1770
1771 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
1772 Some(clap::builder::PossibleValue::new(self.as_str()))
1773 }
1774}
1775
1776/// Trait for providing FHIR resource type information
1777///
1778/// This trait allows querying which resource types are available in a specific
1779/// FHIR version without hardcoding resource type lists in multiple places.
1780pub trait FhirResourceTypeProvider {
1781 /// Returns a vector of all resource type names supported in this FHIR version
1782 fn get_resource_type_names() -> Vec<&'static str>;
1783
1784 /// Checks if a given type name is a resource type in this FHIR version
1785 fn is_resource_type(type_name: &str) -> bool {
1786 Self::get_resource_type_names()
1787 .iter()
1788 .any(|&resource_type| resource_type.eq_ignore_ascii_case(type_name))
1789 }
1790}
1791
1792/// Trait for providing FHIR complex type information
1793///
1794/// This trait allows querying which complex data types are available in a specific
1795/// FHIR version without hardcoding complex type lists in multiple places.
1796pub trait FhirComplexTypeProvider {
1797 /// Returns a vector of all complex type names supported in this FHIR version
1798 fn get_complex_type_names() -> Vec<&'static str>;
1799
1800 /// Checks if a given type name is a complex type in this FHIR version
1801 fn is_complex_type(type_name: &str) -> bool {
1802 Self::get_complex_type_names()
1803 .iter()
1804 .any(|&complex_type| complex_type.eq_ignore_ascii_case(type_name))
1805 }
1806}
1807
1808// --- Internal Visitor for Element Object Deserialization ---
1809
1810/// Internal visitor struct for deserializing Element objects from JSON maps.
1811///
1812/// This visitor handles the complex deserialization logic for Element<V, E> when
1813/// the JSON input is an object containing id, extension, and value fields.
1814struct ElementObjectVisitor<V, E>(PhantomData<(V, E)>);
1815
1816impl<'de, V, E> Visitor<'de> for ElementObjectVisitor<V, E>
1817where
1818 V: Deserialize<'de>,
1819 E: Deserialize<'de>,
1820{
1821 type Value = Element<V, E>;
1822
1823 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1824 formatter.write_str("an Element object")
1825 }
1826
1827 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1828 where
1829 A: MapAccess<'de>,
1830 {
1831 let mut id: Option<String> = None;
1832 let mut extension: Option<Vec<E>> = None;
1833 let mut value: Option<V> = None;
1834
1835 // Manually deserialize fields from the map
1836 while let Some(key) = map.next_key::<String>()? {
1837 match key.as_str() {
1838 "id" => {
1839 if id.is_some() {
1840 return Err(de::Error::duplicate_field("id"));
1841 }
1842 id = Some(map.next_value()?);
1843 }
1844 "extension" => {
1845 if extension.is_some() {
1846 return Err(de::Error::duplicate_field("extension"));
1847 }
1848 extension = Some(map.next_value()?);
1849 }
1850 "value" => {
1851 if value.is_some() {
1852 return Err(de::Error::duplicate_field("value"));
1853 }
1854 // Deserialize directly into Option<V>
1855 value = Some(map.next_value()?);
1856 }
1857 // Ignore any unknown fields encountered
1858 _ => {
1859 let _ = map.next_value::<de::IgnoredAny>()?;
1860 }
1861 }
1862 }
1863
1864 Ok(Element {
1865 id,
1866 extension,
1867 value,
1868 })
1869 }
1870}
1871
1872/// Generic element container supporting FHIR's extension mechanism.
1873///
1874/// In FHIR, most primitive elements can be extended with additional metadata
1875/// through the `id` and `extension` fields. This container type provides
1876/// the infrastructure to support this pattern across all FHIR data types.
1877///
1878/// # Type Parameters
1879///
1880/// * `V` - The value type (e.g., `String`, `i32`, `PreciseDecimal`)
1881/// * `E` - The extension type (typically the generated `Extension` struct)
1882///
1883/// # FHIR Element Structure
1884///
1885/// FHIR elements can appear in three forms:
1886/// 1. **Primitive value**: Just the value itself (e.g., `"text"`, `42`)
1887/// 2. **Extended primitive**: An object with `value`, `id`, and/or `extension` fields
1888/// 3. **Extension-only**: An object with just `id` and/or `extension` (no value)
1889///
1890/// # Examples
1891///
1892/// ```rust
1893/// use helios_fhir::{Element, r4::Extension};
1894///
1895/// // Simple primitive value
1896/// let simple: Element<String, Extension> = Element {
1897/// value: Some("Hello World".to_string()),
1898/// id: None,
1899/// extension: None,
1900/// };
1901///
1902/// // Extended primitive with ID
1903/// let with_id: Element<String, Extension> = Element {
1904/// value: Some("Hello World".to_string()),
1905/// id: Some("text-element-1".to_string()),
1906/// extension: None,
1907/// };
1908///
1909/// // Extension-only element (no value)
1910/// let extension_only: Element<String, Extension> = Element {
1911/// value: None,
1912/// id: Some("disabled-element".to_string()),
1913/// extension: Some(vec![/* extensions */]),
1914/// };
1915/// ```
1916///
1917/// # Serialization Behavior
1918///
1919/// - If only `value` is present: serializes as the primitive value directly
1920/// - If `id` or `extension` are present: serializes as an object with all fields
1921/// - If everything is `None`: serializes as `null`
1922#[derive(Debug, PartialEq, Eq, Clone, Default)]
1923pub struct Element<V, E> {
1924 /// Optional element identifier for referencing within the resource
1925 pub id: Option<String>,
1926 /// Optional extensions providing additional metadata
1927 pub extension: Option<Vec<E>>,
1928 /// The actual primitive value
1929 pub value: Option<V>,
1930}
1931
1932// Custom Deserialize for Element<V, E>
1933// Remove PartialEq/Eq bounds for V and E as they are not needed for deserialization itself
1934impl<'de, V, E> Deserialize<'de> for Element<V, E>
1935where
1936 V: Deserialize<'de> + 'static, // Added 'static for TypeId comparisons
1937 E: Deserialize<'de>, // Removed PartialEq
1938{
1939 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1940 where
1941 D: Deserializer<'de>,
1942 {
1943 // Use the AnyValueVisitor approach to handle different JSON input types
1944 struct AnyValueVisitor<V, E>(PhantomData<(V, E)>);
1945
1946 impl<'de, V, E> Visitor<'de> for AnyValueVisitor<V, E>
1947 where
1948 V: Deserialize<'de> + 'static,
1949 E: Deserialize<'de>,
1950 {
1951 type Value = Element<V, E>;
1952
1953 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1954 formatter
1955 .write_str("a primitive value (string, number, boolean), an object, or null")
1956 }
1957
1958 // Handle primitive types by attempting to deserialize V and wrapping it
1959 fn visit_bool<Er>(self, v: bool) -> Result<Self::Value, Er>
1960 where
1961 Er: de::Error,
1962 {
1963 V::deserialize(de::value::BoolDeserializer::new(v)).map(|value| Element {
1964 id: None,
1965 extension: None,
1966 value: Some(value),
1967 })
1968 }
1969 fn visit_i64<Er>(self, v: i64) -> Result<Self::Value, Er>
1970 where
1971 Er: de::Error,
1972 {
1973 V::deserialize(de::value::I64Deserializer::new(v)).map(|value| Element {
1974 id: None,
1975 extension: None,
1976 value: Some(value),
1977 })
1978 }
1979 fn visit_u64<Er>(self, v: u64) -> Result<Self::Value, Er>
1980 where
1981 Er: de::Error,
1982 {
1983 V::deserialize(de::value::U64Deserializer::new(v)).map(|value| Element {
1984 id: None,
1985 extension: None,
1986 value: Some(value),
1987 })
1988 }
1989 fn visit_f64<Er>(self, v: f64) -> Result<Self::Value, Er>
1990 where
1991 Er: de::Error,
1992 {
1993 V::deserialize(de::value::F64Deserializer::new(v)).map(|value| Element {
1994 id: None,
1995 extension: None,
1996 value: Some(value),
1997 })
1998 }
1999 fn visit_str<Er>(self, v: &str) -> Result<Self::Value, Er>
2000 where
2001 Er: de::Error,
2002 {
2003 use std::any::TypeId;
2004
2005 // Try to handle numeric strings for integer types
2006 if TypeId::of::<V>() == TypeId::of::<i64>() {
2007 if let Ok(int_val) = v.parse::<i64>() {
2008 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
2009 |value| Element {
2010 id: None,
2011 extension: None,
2012 value: Some(value),
2013 },
2014 );
2015 }
2016 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
2017 if let Ok(int_val) = v.parse::<i32>() {
2018 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
2019 |value| Element {
2020 id: None,
2021 extension: None,
2022 value: Some(value),
2023 },
2024 );
2025 }
2026 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
2027 if let Ok(int_val) = v.parse::<u64>() {
2028 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
2029 |value| Element {
2030 id: None,
2031 extension: None,
2032 value: Some(value),
2033 },
2034 );
2035 }
2036 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
2037 if let Ok(int_val) = v.parse::<u32>() {
2038 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
2039 |value| Element {
2040 id: None,
2041 extension: None,
2042 value: Some(value),
2043 },
2044 );
2045 }
2046 }
2047
2048 // Fall back to normal string deserialization
2049 V::deserialize(de::value::StrDeserializer::new(v)).map(|value| Element {
2050 id: None,
2051 extension: None,
2052 value: Some(value),
2053 })
2054 }
2055 fn visit_string<Er>(self, v: String) -> Result<Self::Value, Er>
2056 where
2057 Er: de::Error,
2058 {
2059 use std::any::TypeId;
2060
2061 // Try to handle numeric strings for integer types
2062 if TypeId::of::<V>() == TypeId::of::<i64>() {
2063 if let Ok(int_val) = v.parse::<i64>() {
2064 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
2065 |value| Element {
2066 id: None,
2067 extension: None,
2068 value: Some(value),
2069 },
2070 );
2071 }
2072 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
2073 if let Ok(int_val) = v.parse::<i32>() {
2074 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
2075 |value| Element {
2076 id: None,
2077 extension: None,
2078 value: Some(value),
2079 },
2080 );
2081 }
2082 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
2083 if let Ok(int_val) = v.parse::<u64>() {
2084 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
2085 |value| Element {
2086 id: None,
2087 extension: None,
2088 value: Some(value),
2089 },
2090 );
2091 }
2092 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
2093 if let Ok(int_val) = v.parse::<u32>() {
2094 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
2095 |value| Element {
2096 id: None,
2097 extension: None,
2098 value: Some(value),
2099 },
2100 );
2101 }
2102 }
2103
2104 // Fall back to normal string deserialization
2105 V::deserialize(de::value::StringDeserializer::new(v.clone())).map(|value| Element {
2106 // Clone v for error message
2107 id: None,
2108 extension: None,
2109 value: Some(value),
2110 })
2111 }
2112 fn visit_borrowed_str<Er>(self, v: &'de str) -> Result<Self::Value, Er>
2113 where
2114 Er: de::Error,
2115 {
2116 use std::any::TypeId;
2117
2118 // Try to handle numeric strings for integer types
2119 if TypeId::of::<V>() == TypeId::of::<i64>() {
2120 if let Ok(int_val) = v.parse::<i64>() {
2121 return V::deserialize(de::value::I64Deserializer::new(int_val)).map(
2122 |value| Element {
2123 id: None,
2124 extension: None,
2125 value: Some(value),
2126 },
2127 );
2128 }
2129 } else if TypeId::of::<V>() == TypeId::of::<i32>() {
2130 if let Ok(int_val) = v.parse::<i32>() {
2131 return V::deserialize(de::value::I32Deserializer::new(int_val)).map(
2132 |value| Element {
2133 id: None,
2134 extension: None,
2135 value: Some(value),
2136 },
2137 );
2138 }
2139 } else if TypeId::of::<V>() == TypeId::of::<u64>() {
2140 if let Ok(int_val) = v.parse::<u64>() {
2141 return V::deserialize(de::value::U64Deserializer::new(int_val)).map(
2142 |value| Element {
2143 id: None,
2144 extension: None,
2145 value: Some(value),
2146 },
2147 );
2148 }
2149 } else if TypeId::of::<V>() == TypeId::of::<u32>() {
2150 if let Ok(int_val) = v.parse::<u32>() {
2151 return V::deserialize(de::value::U32Deserializer::new(int_val)).map(
2152 |value| Element {
2153 id: None,
2154 extension: None,
2155 value: Some(value),
2156 },
2157 );
2158 }
2159 }
2160
2161 // Fall back to normal string deserialization
2162 V::deserialize(de::value::BorrowedStrDeserializer::new(v)).map(|value| Element {
2163 id: None,
2164 extension: None,
2165 value: Some(value),
2166 })
2167 }
2168 fn visit_bytes<Er>(self, v: &[u8]) -> Result<Self::Value, Er>
2169 where
2170 Er: de::Error,
2171 {
2172 V::deserialize(de::value::BytesDeserializer::new(v)).map(|value| Element {
2173 id: None,
2174 extension: None,
2175 value: Some(value),
2176 })
2177 }
2178 fn visit_byte_buf<Er>(self, v: Vec<u8>) -> Result<Self::Value, Er>
2179 where
2180 Er: de::Error,
2181 {
2182 // Use BytesDeserializer with a slice reference &v
2183 V::deserialize(de::value::BytesDeserializer::new(&v)).map(|value| Element {
2184 id: None,
2185 extension: None,
2186 value: Some(value),
2187 })
2188 }
2189
2190 // Handle null
2191 fn visit_none<Er>(self) -> Result<Self::Value, Er>
2192 where
2193 Er: de::Error,
2194 {
2195 Ok(Element {
2196 id: None,
2197 extension: None,
2198 value: None,
2199 })
2200 }
2201 fn visit_unit<Er>(self) -> Result<Self::Value, Er>
2202 where
2203 Er: de::Error,
2204 {
2205 Ok(Element {
2206 id: None,
2207 extension: None,
2208 value: None,
2209 })
2210 }
2211
2212 // Handle Option<T> by visiting Some
2213 fn visit_some<De>(self, deserializer: De) -> Result<Self::Value, De::Error>
2214 where
2215 De: Deserializer<'de>,
2216 {
2217 // Re-dispatch to deserialize_any to handle the inner type correctly
2218 deserializer.deserialize_any(self)
2219 }
2220
2221 // Handle object
2222 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
2223 where
2224 A: MapAccess<'de>,
2225 {
2226 // Deserialize the map using ElementObjectVisitor
2227 // Need to create a deserializer from the map access
2228 let map_deserializer = de::value::MapAccessDeserializer::new(map);
2229 map_deserializer.deserialize_map(ElementObjectVisitor(PhantomData))
2230 }
2231
2232 // We don't expect sequences for a single Element
2233 fn visit_seq<A>(self, _seq: A) -> Result<Self::Value, A::Error>
2234 where
2235 A: de::SeqAccess<'de>,
2236 {
2237 Err(de::Error::invalid_type(de::Unexpected::Seq, &self))
2238 }
2239 }
2240
2241 // Start deserialization using the visitor
2242 deserializer.deserialize_any(AnyValueVisitor(PhantomData))
2243 }
2244}
2245
2246// Custom Serialize for Element<V, E>
2247// Remove PartialEq/Eq bounds for V and E as they are not needed for serialization itself
2248impl<V, E> Serialize for Element<V, E>
2249where
2250 V: Serialize, // Removed PartialEq + Eq
2251 E: Serialize, // Removed PartialEq
2252{
2253 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2254 where
2255 S: Serializer,
2256 {
2257 // If id and extension are None, serialize value directly (or null)
2258 if self.id.is_none() && self.extension.is_none() {
2259 match &self.value {
2260 Some(val) => val.serialize(serializer),
2261 None => serializer.serialize_none(),
2262 }
2263 } else {
2264 // Otherwise, serialize as an object containing id, extension, value if present
2265 let mut len = 0;
2266 if self.id.is_some() {
2267 len += 1;
2268 }
2269 if self.extension.is_some() {
2270 len += 1;
2271 }
2272 if self.value.is_some() {
2273 len += 1;
2274 }
2275
2276 let mut state = serializer.serialize_struct("Element", len)?;
2277 if let Some(id) = &self.id {
2278 state.serialize_field("id", id)?;
2279 }
2280 if let Some(extension) = &self.extension {
2281 state.serialize_field("extension", extension)?;
2282 }
2283 // Restore value serialization for direct Element serialization
2284 if let Some(value) = &self.value {
2285 state.serialize_field("value", value)?;
2286 }
2287 state.end()
2288 }
2289 }
2290}
2291
2292/// Specialized element container for FHIR decimal values with precision preservation.
2293///
2294/// This type combines the generic `Element` pattern with `PreciseDecimal` to provide
2295/// a complete solution for FHIR decimal elements that require both extension support
2296/// and precision preservation during serialization round-trips.
2297///
2298/// # Type Parameters
2299///
2300/// * `E` - The extension type (typically the generated `Extension` struct)
2301///
2302/// # FHIR Decimal Requirements
2303///
2304/// FHIR decimal elements must:
2305/// - Preserve original string precision (e.g., "12.30" vs "12.3")
2306/// - Support mathematical operations using `Decimal` arithmetic
2307/// - Handle extension metadata through `id` and `extension` fields
2308/// - Serialize back to the exact original format when possible
2309///
2310/// # Examples
2311///
2312/// ```rust
2313/// use helios_fhir::{DecimalElement, PreciseDecimal, r4::Extension};
2314/// use rust_decimal::Decimal;
2315///
2316/// // Create from a Decimal value
2317/// let decimal_elem = DecimalElement::<Extension>::new(Decimal::new(1234, 2)); // 12.34
2318///
2319/// // Create with extensions
2320/// let extended_decimal: DecimalElement<Extension> = DecimalElement {
2321/// value: Some(PreciseDecimal::from_parts(
2322/// Some(Decimal::new(12300, 3)),
2323/// "12.300".to_string()
2324/// )),
2325/// id: Some("precision-example".to_string()),
2326/// extension: Some(vec![/* extensions */]),
2327/// };
2328///
2329/// // Access the mathematical value
2330/// if let Some(precise) = &extended_decimal.value {
2331/// if let Some(decimal_val) = precise.value() {
2332/// println!("Mathematical value: {}", decimal_val);
2333/// }
2334/// println!("Original format: {}", precise.original_string());
2335/// }
2336/// ```
2337///
2338/// # Serialization Behavior
2339///
2340/// - **Value only**: Serializes as a JSON number preserving original precision
2341/// - **With extensions**: Serializes as an object with `value`, `id`, and `extension` fields
2342/// - **No value**: Serializes as an object with just the extension fields, or `null` if empty
2343///
2344/// # Integration with FHIRPath
2345///
2346/// When used with FHIRPath evaluation, `DecimalElement` returns:
2347/// - The `Decimal` value for mathematical operations
2348/// - An object representation when extension metadata is accessed
2349/// - Empty collection when the element has no value or extensions
2350#[derive(Debug, PartialEq, Eq, Clone, Default)]
2351pub struct DecimalElement<E> {
2352 /// Optional element identifier for referencing within the resource
2353 pub id: Option<String>,
2354 /// Optional extensions providing additional metadata
2355 pub extension: Option<Vec<E>>,
2356 /// The decimal value with precision preservation
2357 pub value: Option<PreciseDecimal>,
2358}
2359
2360impl<E> DecimalElement<E> {
2361 /// Creates a new `DecimalElement` with the specified decimal value.
2362 ///
2363 /// This constructor creates a simple decimal element with no extensions or ID,
2364 /// containing only the decimal value. The original string representation is
2365 /// automatically derived from the `Decimal` value's `Display` implementation.
2366 ///
2367 /// # Arguments
2368 ///
2369 /// * `value` - The `Decimal` value to store
2370 ///
2371 /// # Returns
2372 ///
2373 /// A new `DecimalElement` with the value set and `id`/`extension` as `None`.
2374 ///
2375 /// # Examples
2376 ///
2377 /// ```rust
2378 /// use helios_fhir::{DecimalElement, r4::Extension};
2379 /// use rust_decimal::Decimal;
2380 ///
2381 /// // Create a simple decimal element
2382 /// let element = DecimalElement::<Extension>::new(Decimal::new(12345, 3)); // 12.345
2383 ///
2384 /// // Verify the structure
2385 /// assert!(element.id.is_none());
2386 /// assert!(element.extension.is_none());
2387 /// assert!(element.value.is_some());
2388 ///
2389 /// // Access the decimal value
2390 /// if let Some(precise_decimal) = &element.value {
2391 /// assert_eq!(precise_decimal.value(), Some(Decimal::new(12345, 3)));
2392 /// assert_eq!(precise_decimal.original_string(), "12.345");
2393 /// }
2394 /// ```
2395 ///
2396 /// # Usage in FHIR Resources
2397 ///
2398 /// This method is typically used when creating FHIR elements programmatically:
2399 ///
2400 /// ```rust
2401 /// use helios_fhir::{DecimalElement, r4::{Extension, Observation}};
2402 /// use rust_decimal::Decimal;
2403 ///
2404 /// let temperature = DecimalElement::<Extension>::new(Decimal::new(3672, 2)); // 36.72
2405 ///
2406 /// // Would be used in an Observation like:
2407 /// // observation.value_quantity.value = Some(temperature);
2408 /// ```
2409 pub fn new(value: Decimal) -> Self {
2410 // Convert the Decimal to PreciseDecimal, which automatically handles
2411 // storing the original string representation via the From trait
2412 let precise_value = PreciseDecimal::from(value);
2413 Self {
2414 id: None,
2415 extension: None,
2416 value: Some(precise_value),
2417 }
2418 }
2419}
2420
2421// Custom Deserialize for DecimalElement<E> using intermediate Value
2422impl<'de, E> Deserialize<'de> for DecimalElement<E>
2423where
2424 E: Deserialize<'de> + Default,
2425{
2426 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2427 where
2428 D: Deserializer<'de>,
2429 {
2430 // Deserialize into an intermediate serde_json::Value first
2431 let json_value = serde_json::Value::deserialize(deserializer)?;
2432
2433 match json_value {
2434 // Handle primitive JSON Number
2435 serde_json::Value::Number(n) => {
2436 // Directly parse the number string to create PreciseDecimal
2437 let s = n.to_string(); // Note: n.to_string() might normalize exponent case (e.g., 'E' -> 'e')
2438 // Replace 'E' with 'e' for parsing
2439 let s_for_parsing = s.replace('E', "e");
2440 // Use from_scientific if 'e' is present, otherwise parse
2441 let parsed_value = if s_for_parsing.contains('e') {
2442 Decimal::from_scientific(&s_for_parsing).ok()
2443 } else {
2444 s_for_parsing.parse::<Decimal>().ok()
2445 };
2446 // Store the ORIGINAL string `s` (as returned by n.to_string()).
2447 let pd = PreciseDecimal::from_parts(parsed_value, s);
2448 Ok(DecimalElement {
2449 id: None,
2450 extension: None,
2451 value: Some(pd),
2452 })
2453 }
2454 // Handle primitive JSON String
2455 serde_json::Value::String(s) => {
2456 // Directly parse the string to create PreciseDecimal
2457 // Replace 'E' with 'e' for parsing
2458 let s_for_parsing = s.replace('E', "e");
2459 // Use from_scientific if 'e' is present, otherwise parse
2460 let parsed_value = if s_for_parsing.contains('e') {
2461 Decimal::from_scientific(&s_for_parsing).ok()
2462 } else {
2463 s_for_parsing.parse::<Decimal>().ok()
2464 };
2465 // Store the ORIGINAL string `s`.
2466 let pd = PreciseDecimal::from_parts(parsed_value, s); // s is owned, no clone needed
2467 Ok(DecimalElement {
2468 id: None,
2469 extension: None,
2470 value: Some(pd),
2471 })
2472 }
2473 // Handle JSON object: deserialize fields individually
2474 serde_json::Value::Object(map) => {
2475 let mut id: Option<String> = None;
2476 let mut extension: Option<Vec<E>> = None;
2477 let mut value: Option<PreciseDecimal> = None;
2478
2479 for (k, v) in map {
2480 match k.as_str() {
2481 "id" => {
2482 if id.is_some() {
2483 return Err(de::Error::duplicate_field("id"));
2484 }
2485 // Deserialize id directly from its Value
2486 id = Deserialize::deserialize(v).map_err(de::Error::custom)?;
2487 }
2488 "extension" => {
2489 if extension.is_some() {
2490 return Err(de::Error::duplicate_field("extension"));
2491 }
2492 // Deserialize extension directly from its Value
2493 extension = Deserialize::deserialize(v).map_err(de::Error::custom)?;
2494 }
2495 "value" => {
2496 if value.is_some() {
2497 return Err(de::Error::duplicate_field("value"));
2498 }
2499 // Deserialize value using PreciseDecimal::deserialize from its Value
2500 // Handle null explicitly within the value field
2501 if v.is_null() {
2502 value = None;
2503 } else {
2504 value = Some(
2505 PreciseDecimal::deserialize(v).map_err(de::Error::custom)?,
2506 );
2507 }
2508 }
2509 // Ignore any unknown fields encountered
2510 _ => {} // Simply ignore unknown fields
2511 }
2512 }
2513 Ok(DecimalElement {
2514 id,
2515 extension,
2516 value,
2517 })
2518 }
2519 // Handle JSON Null for the whole element
2520 serde_json::Value::Null => Ok(DecimalElement::default()), // Default has value: None
2521 // Handle other unexpected types
2522 other => Err(de::Error::invalid_type(
2523 match other {
2524 serde_json::Value::Bool(b) => de::Unexpected::Bool(b),
2525 serde_json::Value::Array(_) => de::Unexpected::Seq,
2526 _ => de::Unexpected::Other("unexpected JSON type for DecimalElement"),
2527 },
2528 &"a decimal number, string, object, or null",
2529 )),
2530 }
2531 }
2532}
2533
2534// Reinstate custom Serialize implementation for DecimalElement
2535// Remove PartialEq bound for E
2536impl<E> Serialize for DecimalElement<E>
2537where
2538 E: Serialize, // Removed PartialEq bound for E
2539{
2540 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2541 where
2542 S: Serializer,
2543 {
2544 // If we only have a value and no other fields, serialize just the value
2545 if self.id.is_none() && self.extension.is_none() {
2546 if let Some(value) = &self.value {
2547 // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
2548 return value.serialize(serializer);
2549 } else {
2550 // If value is also None, serialize as null
2551 // based on updated test_serialize_decimal_with_no_fields
2552 return serializer.serialize_none();
2553 }
2554 }
2555
2556 // Otherwise, serialize as a struct with all present fields
2557 // Calculate the number of fields that are NOT None
2558 let mut len = 0;
2559 if self.id.is_some() {
2560 len += 1;
2561 }
2562 if self.extension.is_some() {
2563 len += 1;
2564 }
2565 if self.value.is_some() {
2566 len += 1;
2567 }
2568
2569 // Start serializing a struct with the calculated length
2570 let mut state = serializer.serialize_struct("DecimalElement", len)?;
2571
2572 // Serialize 'id' field if it's Some
2573 if let Some(id) = &self.id {
2574 state.serialize_field("id", id)?;
2575 }
2576
2577 // Serialize 'extension' field if it's Some
2578 if let Some(extension) = &self.extension {
2579 state.serialize_field("extension", extension)?;
2580 }
2581
2582 // Serialize 'value' field if it's Some
2583 if let Some(value) = &self.value {
2584 // Serialize the PreciseDecimal directly, invoking its custom Serialize impl
2585 state.serialize_field("value", value)?;
2586 }
2587
2588 // End the struct serialization
2589 state.end()
2590 }
2591}
2592
2593// For Element<V, E> - Returns Object with id, extension, value if present
2594impl<V, E> IntoEvaluationResult for Element<V, E>
2595where
2596 V: IntoEvaluationResult + Clone + 'static,
2597 E: IntoEvaluationResult + Clone,
2598{
2599 fn to_evaluation_result(&self) -> EvaluationResult {
2600 use std::any::TypeId;
2601
2602 // Prioritize returning the primitive value if it exists
2603 if let Some(v) = &self.value {
2604 let result = v.to_evaluation_result();
2605 // For primitive values, we need to preserve FHIR type information
2606 return match result {
2607 EvaluationResult::Boolean(b, _) => {
2608 // Return FHIR boolean
2609 EvaluationResult::fhir_boolean(b)
2610 }
2611 EvaluationResult::Integer(i, _) => {
2612 // Return FHIR integer
2613 EvaluationResult::fhir_integer(i)
2614 }
2615 #[cfg(not(any(feature = "R4", feature = "R4B")))]
2616 EvaluationResult::Integer64(i, _) => {
2617 // Return FHIR integer64 (R5 and above)
2618 EvaluationResult::fhir_integer64(i)
2619 }
2620 EvaluationResult::String(s, _) => {
2621 // Determine the FHIR type name based on V's type
2622 let fhir_type_name = if TypeId::of::<V>() == TypeId::of::<String>() {
2623 // For strings, we need more context to determine the exact FHIR type
2624 // Default to "string" but this could be date, dateTime, etc.
2625 "string"
2626 } else {
2627 // Default fallback
2628 "string"
2629 };
2630 EvaluationResult::fhir_string(s, fhir_type_name)
2631 }
2632 EvaluationResult::DateTime(dt, type_info) => {
2633 // Check if V is PrecisionInstant - if so, this is an instant
2634 if TypeId::of::<V>() == TypeId::of::<PrecisionInstant>() {
2635 // Return as FHIR instant
2636 EvaluationResult::DateTime(dt, Some(TypeInfoResult::new("FHIR", "instant")))
2637 } else {
2638 // Preserve original type info for PrecisionDateTime
2639 EvaluationResult::DateTime(dt, type_info)
2640 }
2641 }
2642 _ => result, // For other types, return as-is
2643 };
2644 } else if self.id.is_some() || self.extension.is_some() {
2645 // If value is None, but id or extension exist, return an Object with those
2646 let mut map = std::collections::HashMap::new();
2647 if let Some(id) = &self.id {
2648 map.insert("id".to_string(), EvaluationResult::string(id.clone()));
2649 }
2650 if let Some(ext) = &self.extension {
2651 let ext_collection: Vec<EvaluationResult> =
2652 ext.iter().map(|e| e.to_evaluation_result()).collect();
2653 if !ext_collection.is_empty() {
2654 map.insert(
2655 "extension".to_string(),
2656 EvaluationResult::collection(ext_collection),
2657 );
2658 }
2659 }
2660 // Only return Object if map is not empty (i.e., id or extension was actually present)
2661 if !map.is_empty() {
2662 return EvaluationResult::typed_object(map, "FHIR", "Element");
2663 }
2664 }
2665
2666 // If value, id, and extension are all None, return Empty
2667 EvaluationResult::Empty
2668 }
2669}
2670
2671// For DecimalElement<E> - Returns Decimal value if present, otherwise handles id/extension
2672impl<E> IntoEvaluationResult for DecimalElement<E>
2673where
2674 E: IntoEvaluationResult + Clone,
2675{
2676 fn to_evaluation_result(&self) -> EvaluationResult {
2677 // Prioritize returning the primitive decimal value if it exists
2678 if let Some(precise_decimal) = &self.value {
2679 if let Some(decimal_val) = precise_decimal.value() {
2680 // Return FHIR decimal
2681 return EvaluationResult::fhir_decimal(decimal_val);
2682 }
2683 // If PreciseDecimal holds None for value, fall through to check id/extension
2684 }
2685
2686 // If value is None, but id or extension exist, return an Object with those
2687 if self.id.is_some() || self.extension.is_some() {
2688 let mut map = std::collections::HashMap::new();
2689 if let Some(id) = &self.id {
2690 map.insert("id".to_string(), EvaluationResult::string(id.clone()));
2691 }
2692 if let Some(ext) = &self.extension {
2693 let ext_collection: Vec<EvaluationResult> =
2694 ext.iter().map(|e| e.to_evaluation_result()).collect();
2695 if !ext_collection.is_empty() {
2696 map.insert(
2697 "extension".to_string(),
2698 EvaluationResult::collection(ext_collection),
2699 );
2700 }
2701 }
2702 // Only return Object if map is not empty
2703 if !map.is_empty() {
2704 return EvaluationResult::typed_object(map, "FHIR", "decimal");
2705 }
2706 }
2707
2708 // If value, id, and extension are all None, return Empty
2709 EvaluationResult::Empty
2710 }
2711}
2712
2713// Implement the trait for the top-level enum
2714impl IntoEvaluationResult for FhirResource {
2715 fn to_evaluation_result(&self) -> EvaluationResult {
2716 match self {
2717 #[cfg(feature = "R4")]
2718 FhirResource::R4(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4::Resource>
2719 #[cfg(feature = "R4B")]
2720 FhirResource::R4B(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r4b::Resource>
2721 #[cfg(feature = "R5")]
2722 FhirResource::R5(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r5::Resource>
2723 #[cfg(feature = "R6")]
2724 FhirResource::R6(r) => (*r).to_evaluation_result(), // Call impl on inner Box<r6::Resource>
2725 // Note: If no features are enabled, this match might be empty or non-exhaustive.
2726 // This is generally okay as the enum itself wouldn't be usable.
2727 }
2728 }
2729}
2730
2731#[cfg(test)]
2732mod tests {
2733 use super::*;
2734
2735 #[test]
2736 fn test_integer_string_deserialization() {
2737 // Test deserializing a string "2" into Element<i64, ()>
2738 type TestElement = Element<i64, ()>;
2739
2740 // Test case 1: String containing integer
2741 let json_str = r#""2""#;
2742 let result: Result<TestElement, _> = serde_json::from_str(json_str);
2743 assert!(
2744 result.is_ok(),
2745 "Failed to deserialize string '2' as i64: {:?}",
2746 result.err()
2747 );
2748
2749 let element = result.unwrap();
2750 assert_eq!(element.value, Some(2i64));
2751 assert_eq!(element.id, None);
2752 assert_eq!(element.extension, None);
2753
2754 // Test case 2: Number
2755 let json_num = r#"2"#;
2756 let result: Result<TestElement, _> = serde_json::from_str(json_num);
2757 assert!(
2758 result.is_ok(),
2759 "Failed to deserialize number 2 as i64: {:?}",
2760 result.err()
2761 );
2762
2763 let element = result.unwrap();
2764 assert_eq!(element.value, Some(2i64));
2765 }
2766
2767 #[test]
2768 fn test_i32_string_deserialization() {
2769 type TestElement = Element<i32, ()>;
2770
2771 let json_str = r#""123""#;
2772 let result: Result<TestElement, _> = serde_json::from_str(json_str);
2773 assert!(result.is_ok());
2774
2775 let element = result.unwrap();
2776 assert_eq!(element.value, Some(123i32));
2777 }
2778
2779 #[test]
2780 fn test_invalid_string_fallback() {
2781 type TestElement = Element<i64, ()>;
2782
2783 // Non-numeric string should fail for integer type
2784 let json_str = r#""not_a_number""#;
2785 let result: Result<TestElement, _> = serde_json::from_str(json_str);
2786 assert!(
2787 result.is_err(),
2788 "Should fail to deserialize non-numeric string as i64"
2789 );
2790 }
2791}