Skip to main content

haste_fhir_search/
indexing_conversion.rs

1/// Reference of conversions found here <https://www.hl7.org/fhir/R4/search.html#table>
2use haste_fhir_model::r4::{
3    datetime::{Date, DateTime, Instant},
4    generated::{
5        resources::{ResourceType, ResourceTypeError},
6        terminology::SearchParamType,
7        types::{
8            Address, Age, CodeableConcept, Coding, ContactPoint, Duration, FHIRBoolean,
9            FHIRCanonical, FHIRDate, FHIRDateTime, FHIRDecimal, FHIRId, FHIRInstant, FHIRInteger,
10            FHIRMarkdown, FHIRPositiveInt, FHIRString, FHIRUnsignedInt, FHIRUri, FHIRUrl, FHIRUuid,
11            HumanName, Identifier, Money, Period, Quantity, Range, Reference, Timing,
12        },
13    },
14};
15use haste_fhir_operation_error::{OperationOutcomeError, derive::OperationOutcomeError};
16use haste_reflect::MetaValue;
17use serde::{Deserialize, Serialize};
18
19use crate::ResolvedParameter;
20
21#[derive(Debug, Serialize, Deserialize)]
22pub struct TokenIndex {
23    system: Option<String>,
24    code: Option<String>,
25}
26
27impl TokenIndex {
28    #[must_use]
29    pub fn system(&self) -> Option<&str> {
30        self.system.as_deref()
31    }
32
33    #[must_use]
34    pub fn code(&self) -> Option<&str> {
35        self.code.as_deref()
36    }
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40#[serde(untagged)]
41enum RangeValue {
42    Number(f64),
43    Infinity,
44}
45
46impl RangeValue {
47    fn as_f64(&self) -> f64 {
48        match self {
49            RangeValue::Number(n) => *n,
50            RangeValue::Infinity => f64::INFINITY,
51        }
52    }
53}
54
55#[derive(Debug, Serialize, Deserialize)]
56pub struct QuantityRange {
57    start_value: RangeValue,
58    start_code: Option<String>,
59    start_system: Option<String>,
60    end_value: RangeValue,
61    end_code: Option<String>,
62    end_system: Option<String>,
63}
64
65impl QuantityRange {
66    #[must_use]
67    pub fn start_value(&self) -> f64 {
68        self.start_value.as_f64()
69    }
70
71    #[must_use]
72    pub fn start_code(&self) -> Option<&str> {
73        self.start_code.as_deref()
74    }
75
76    #[must_use]
77    pub fn start_system(&self) -> Option<&str> {
78        self.start_system.as_deref()
79    }
80
81    #[must_use]
82    pub fn end_value(&self) -> f64 {
83        self.end_value.as_f64()
84    }
85
86    #[must_use]
87    pub fn end_code(&self) -> Option<&str> {
88        self.end_code.as_deref()
89    }
90
91    #[must_use]
92    pub fn end_system(&self) -> Option<&str> {
93        self.end_system.as_deref()
94    }
95}
96
97#[derive(Debug, Serialize, Deserialize, PartialEq)]
98pub struct DateRange {
99    /// Milliseconds since epoch.
100    pub start: i64,
101    pub end: i64,
102}
103
104#[derive(Debug, Serialize, Deserialize)]
105pub struct ReferenceIndex {
106    id: Option<String>,
107    resource_type: Option<String>,
108    uri: Option<String>,
109}
110
111impl ReferenceIndex {
112    #[must_use]
113    pub fn id(&self) -> Option<&str> {
114        self.id.as_deref()
115    }
116
117    #[must_use]
118    pub fn resource_type(&self) -> Option<&str> {
119        self.resource_type.as_deref()
120    }
121
122    #[must_use]
123    pub fn uri(&self) -> Option<&str> {
124        self.uri.as_deref()
125    }
126}
127
128/// The typed value slot of a project-level (user-submitted) search parameter,
129/// matching the fixed `dynamic_parameters.value.*` schema declared in the
130/// Elasticsearch mapping. Exactly one field is set, matching the parameter's
131/// `SearchParamType`. Unlike system-level parameters, a user-submitted
132/// parameter's URL is arbitrary and can't safely become an Elasticsearch
133/// field name (mapping explosion, plus Elasticsearch would silently collapse
134/// any dot in that URL into a nested object path) - so it's stored as the
135/// plain string value of the `url` field instead. See [`DynamicParameterEntry`].
136#[derive(Debug, Serialize, Deserialize, Default)]
137pub struct DynamicParameterValue {
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub string: Option<Vec<String>>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub number: Option<Vec<f64>>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub uri: Option<Vec<String>>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub token: Option<Vec<TokenIndex>>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub date: Option<Vec<DateRange>>,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub reference: Option<Vec<ReferenceIndex>>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub quantity: Option<Vec<QuantityRange>>,
152}
153
154#[derive(Debug, Serialize, Deserialize)]
155pub struct DynamicParameterEntry {
156    pub url: String,
157    #[serde(rename = "type")]
158    pub type_: String,
159    pub value: DynamicParameterValue,
160}
161
162impl DynamicParameterEntry {
163    /// Builds an entry from a search parameter's URL, its FHIR search
164    /// parameter type code (e.g. `"string"`, `"token"`), and the leaf
165    /// `InsertableIndex` produced for it. Returns `None` for the internal
166    /// (`Meta`, `Composite`, `Special`, `DynamicParameters`) variants, which
167    /// never occur as a single parameter's evaluated value.
168    #[must_use]
169    pub fn from_leaf(url: String, type_: &str, leaf: InsertableIndex) -> Option<Self> {
170        let value = match leaf {
171            InsertableIndex::String(v) => DynamicParameterValue {
172                string: Some(v),
173                ..Default::default()
174            },
175            InsertableIndex::Number(v) => DynamicParameterValue {
176                number: Some(v),
177                ..Default::default()
178            },
179            InsertableIndex::URI(v) => DynamicParameterValue {
180                uri: Some(v),
181                ..Default::default()
182            },
183            InsertableIndex::Token(v) => DynamicParameterValue {
184                token: Some(v),
185                ..Default::default()
186            },
187            InsertableIndex::Date(v) => DynamicParameterValue {
188                date: Some(v),
189                ..Default::default()
190            },
191            InsertableIndex::Reference(v) => DynamicParameterValue {
192                reference: Some(v),
193                ..Default::default()
194            },
195            InsertableIndex::Quantity(v) => DynamicParameterValue {
196                quantity: Some(v),
197                ..Default::default()
198            },
199            InsertableIndex::Meta(_)
200            | InsertableIndex::Composite(_)
201            | InsertableIndex::Special(_)
202            | InsertableIndex::DynamicParameters(_) => return None,
203        };
204
205        Some(DynamicParameterEntry {
206            url,
207            type_: type_.to_string(),
208            value,
209        })
210    }
211}
212
213#[derive(Debug, Serialize, Deserialize)]
214#[serde(untagged)]
215pub enum InsertableIndex {
216    // Used for internal indexing only
217    Meta(String),
218    // Fhir Indexing types
219    String(Vec<String>),
220    Number(Vec<f64>),
221    URI(Vec<String>),
222    Token(Vec<TokenIndex>),
223    Date(Vec<DateRange>),
224    Reference(Vec<ReferenceIndex>),
225    Quantity(Vec<QuantityRange>),
226    Composite(Vec<String>),
227    Special(Vec<String>),
228    DynamicParameters(Vec<DynamicParameterEntry>),
229}
230
231#[derive(OperationOutcomeError, Debug)]
232pub enum InsertableIndexError {
233    #[fatal(
234        code = "exception",
235        diagnostic = "Invalid type for insertable index: '{arg0}'"
236    )]
237    InvalidType(String),
238    #[fatal(
239        code = "exception",
240        diagnostic = "Failed to downcast value to number: {arg0}"
241    )]
242    FailedDowncast(String),
243    #[fatal(
244        code = "exception",
245        diagnostic = "Reference contains invalid resource type."
246    )]
247    ResourceTypeError(#[from] ResourceTypeError),
248}
249
250// "http://hl7.org/fhirpath/System.String" => value
251//     .as_any()
252//     .downcast_ref::<String>()
253//     .map(|v| vec![v.clone()])
254//     .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string())),
255
256fn convert_fp_string(value: &FHIRString) -> Vec<String> {
257    value
258        .value
259        .as_ref()
260        .map_or_else(Vec::new, |v| vec![v.clone()])
261}
262
263fn convert_optional_fp_string(value: Option<&FHIRString>) -> Vec<String> {
264    value
265        .as_ref()
266        .map_or_else(Vec::new, |v| convert_fp_string(v))
267}
268
269fn convert_optional_fp_string_vec(value: Option<&Vec<FHIRString>>) -> Vec<String> {
270    value
271        .as_ref()
272        .map_or_else(Vec::new, |v| v.iter().flat_map(convert_fp_string).collect())
273}
274
275fn index_string(value: &dyn MetaValue) -> Result<Vec<String>, InsertableIndexError> {
276    match value.fhir_type() {
277        "string" => {
278            let fp_string = value.as_any().downcast_ref::<FHIRString>().ok_or_else(|| {
279                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
280            })?;
281            Ok(fp_string
282                .value
283                .as_ref()
284                .map_or_else(Vec::new, |v| vec![v.clone()]))
285        }
286        // Even though spec states won't encounter this it does. [ImplementationGuide.description]
287        "markdown" => {
288            let fp_markdown = value
289                .as_any()
290                .downcast_ref::<FHIRMarkdown>()
291                .ok_or_else(|| {
292                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
293                })?;
294            Ok(fp_markdown
295                .value
296                .as_ref()
297                .map_or_else(Vec::new, |v| vec![v.clone()]))
298        }
299        "HumanName" => {
300            let human_name = value.as_any().downcast_ref::<HumanName>().ok_or_else(|| {
301                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
302            })?;
303
304            let mut string_index = Vec::new();
305            string_index.extend(convert_optional_fp_string(human_name.text.as_deref()));
306            string_index.extend(convert_optional_fp_string(human_name.family.as_deref()));
307            string_index.extend(convert_optional_fp_string_vec(human_name.given.as_ref()));
308            string_index.extend(convert_optional_fp_string_vec(human_name.prefix.as_ref()));
309            string_index.extend(convert_optional_fp_string_vec(human_name.suffix.as_ref()));
310            Ok(string_index)
311        }
312        "Address" => {
313            let mut string_index = Vec::new();
314            let address = value.as_any().downcast_ref::<Address>().ok_or_else(|| {
315                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
316            })?;
317            string_index.extend(convert_optional_fp_string(address.text.as_deref()));
318            string_index.extend(convert_optional_fp_string_vec(address.line.as_ref()));
319            string_index.extend(convert_optional_fp_string(address.city.as_deref()));
320            string_index.extend(convert_optional_fp_string(address.district.as_deref()));
321            string_index.extend(convert_optional_fp_string(address.state.as_deref()));
322            string_index.extend(convert_optional_fp_string(address.postalCode.as_deref()));
323            string_index.extend(convert_optional_fp_string(address.country.as_deref()));
324
325            Ok(string_index)
326        }
327
328        type_name => Err(InsertableIndexError::FailedDowncast(type_name.to_string())),
329    }
330}
331
332fn index_number(value: &dyn MetaValue) -> Result<Vec<f64>, InsertableIndexError> {
333    match value.fhir_type() {
334        "integer" => {
335            let fp_integer = value
336                .as_any()
337                .downcast_ref::<FHIRInteger>()
338                .ok_or_else(|| {
339                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
340                })?;
341            #[allow(clippy::cast_precision_loss)]
342            Ok(fp_integer
343                .value
344                .as_ref()
345                .map_or_else(Vec::new, |v| vec![*v as f64]))
346        }
347        "decimal" => {
348            let fp_decimal = value
349                .as_any()
350                .downcast_ref::<FHIRDecimal>()
351                .ok_or_else(|| {
352                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
353                })?;
354            #[allow(clippy::cast_precision_loss)]
355            Ok(fp_decimal
356                .value
357                .as_ref()
358                .map_or_else(Vec::new, |v| vec![*v]))
359        }
360        "positiveInt" => {
361            let fp_positive_int = value
362                .as_any()
363                .downcast_ref::<FHIRPositiveInt>()
364                .ok_or_else(|| {
365                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
366                })?;
367
368            #[allow(clippy::cast_precision_loss)]
369            Ok(fp_positive_int
370                .value
371                .as_ref()
372                .map_or_else(Vec::new, |v| vec![*v as f64]))
373        }
374        "unsignedInt" => {
375            let fp_unsigned_int = value
376                .as_any()
377                .downcast_ref::<FHIRUnsignedInt>()
378                .ok_or_else(|| {
379                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
380                })?;
381
382            #[allow(clippy::cast_precision_loss)]
383            Ok(fp_unsigned_int
384                .value
385                .as_ref()
386                .map_or_else(Vec::new, |v| vec![*v as f64]))
387        }
388        type_name => Err(InsertableIndexError::FailedDowncast(type_name.to_string())),
389    }
390}
391
392fn index_uri(value: &dyn MetaValue) -> Result<Vec<String>, InsertableIndexError> {
393    match value.fhir_type() {
394        "url" => {
395            let fp_uri = value.as_any().downcast_ref::<FHIRUrl>().ok_or_else(|| {
396                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
397            })?;
398            Ok(fp_uri
399                .value
400                .as_ref()
401                .map_or_else(Vec::new, |v| vec![v.clone()]))
402        }
403        "uuid" => {
404            let fp_uri = value.as_any().downcast_ref::<FHIRUuid>().ok_or_else(|| {
405                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
406            })?;
407            Ok(fp_uri
408                .value
409                .as_ref()
410                .map_or_else(Vec::new, |v| vec![v.clone()]))
411        }
412        "canonical" => {
413            let fp_uri = value
414                .as_any()
415                .downcast_ref::<FHIRCanonical>()
416                .ok_or_else(|| {
417                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
418                })?;
419            Ok(fp_uri
420                .value
421                .as_ref()
422                .map_or_else(Vec::new, |v| vec![v.clone()]))
423        }
424        "uri" => {
425            let fp_uri = value.as_any().downcast_ref::<FHIRUri>().ok_or_else(|| {
426                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
427            })?;
428            Ok(fp_uri
429                .value
430                .as_ref()
431                .map_or_else(Vec::new, |v| vec![v.clone()]))
432        }
433        type_name => Err(InsertableIndexError::FailedDowncast(type_name.to_string())),
434    }
435}
436
437fn index_token(value: &dyn MetaValue) -> Result<Vec<TokenIndex>, InsertableIndexError> {
438    match value.fhir_type() {
439        "Coding" => index_token_coding(value),
440        "CodeableConcept" => index_token_codeable_concept(value),
441        "Identifier" => index_token_identifier(value),
442        "ContactPoint" => index_token_contact_point(value),
443        "code" => Ok(index_token_code(value)),
444        "boolean" => index_token_boolean(value),
445        "http://hl7.org/fhirpath/System.String" => index_token_system_string(value),
446        "string" => index_token_string(value),
447        "id" => index_token_id(value),
448        _ => Err(InsertableIndexError::FailedDowncast(
449            value.fhir_type().to_string(),
450        )),
451    }
452}
453
454fn index_token_coding(value: &dyn MetaValue) -> Result<Vec<TokenIndex>, InsertableIndexError> {
455    let coding = value
456        .as_any()
457        .downcast_ref::<Coding>()
458        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
459
460    Ok(vec![TokenIndex {
461        system: coding.system.as_ref().and_then(|s| s.value.clone()),
462        code: coding.code.as_ref().and_then(|v| v.value.clone()),
463    }])
464}
465
466fn index_token_codeable_concept(
467    value: &dyn MetaValue,
468) -> Result<Vec<TokenIndex>, InsertableIndexError> {
469    let codeable_concept = value
470        .as_any()
471        .downcast_ref::<CodeableConcept>()
472        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
473
474    Ok(codeable_concept
475        .coding
476        .as_ref()
477        .map(|coding| {
478            coding
479                .iter()
480                .map(|c| TokenIndex {
481                    system: c.system.as_ref().and_then(|s| s.value.clone()),
482                    code: c.code.as_ref().and_then(|v| v.value.clone()),
483                })
484                .collect()
485        })
486        .unwrap_or_default())
487}
488
489fn index_token_identifier(value: &dyn MetaValue) -> Result<Vec<TokenIndex>, InsertableIndexError> {
490    let identifier = value
491        .as_any()
492        .downcast_ref::<Identifier>()
493        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
494
495    Ok(vec![TokenIndex {
496        system: identifier.system.as_ref().and_then(|s| s.value.clone()),
497        code: identifier.value.as_ref().and_then(|v| v.value.clone()),
498    }])
499}
500
501fn index_token_contact_point(
502    value: &dyn MetaValue,
503) -> Result<Vec<TokenIndex>, InsertableIndexError> {
504    let contact_point = value
505        .as_any()
506        .downcast_ref::<ContactPoint>()
507        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
508
509    Ok(vec![TokenIndex {
510        system: None,
511        code: contact_point.value.as_ref().and_then(|v| v.value.clone()),
512    }])
513}
514
515fn index_token_code(value: &dyn MetaValue) -> Vec<TokenIndex> {
516    let code = value
517        .get_field("value")
518        .and_then(|v| v.as_any().downcast_ref::<String>());
519
520    vec![TokenIndex {
521        system: None,
522        code: code.cloned(),
523    }]
524}
525
526fn index_token_boolean(value: &dyn MetaValue) -> Result<Vec<TokenIndex>, InsertableIndexError> {
527    let boolean = value
528        .as_any()
529        .downcast_ref::<FHIRBoolean>()
530        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
531
532    Ok(vec![TokenIndex {
533        system: Some("http://hl7.org/fhir/special-values".to_string()),
534        code: boolean.value.as_ref().map(std::string::ToString::to_string),
535    }])
536}
537
538fn index_token_system_string(
539    value: &dyn MetaValue,
540) -> Result<Vec<TokenIndex>, InsertableIndexError> {
541    let string = value
542        .as_any()
543        .downcast_ref::<String>()
544        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
545
546    Ok(vec![TokenIndex {
547        system: None,
548        code: Some(string.clone()),
549    }])
550}
551
552fn index_token_string(value: &dyn MetaValue) -> Result<Vec<TokenIndex>, InsertableIndexError> {
553    let string = value
554        .as_any()
555        .downcast_ref::<FHIRString>()
556        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
557
558    Ok(vec![TokenIndex {
559        system: None,
560        code: string.value.clone(),
561    }])
562}
563
564fn index_token_id(value: &dyn MetaValue) -> Result<Vec<TokenIndex>, InsertableIndexError> {
565    let id = value
566        .as_any()
567        .downcast_ref::<FHIRId>()
568        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
569
570    Ok(vec![TokenIndex {
571        system: None,
572        code: id.value.clone(),
573    }])
574}
575
576fn get_decimal_precision(value: &str) -> u32 {
577    let value = value.to_string();
578    let decimal_characters = value.split('.').nth(1);
579    let mut digits = 0;
580    if let Some(decimal_part) = decimal_characters {
581        decimal_part.chars().for_each(|_| digits += 1);
582    }
583
584    digits
585}
586
587#[derive(Debug)]
588pub struct DecimalRange {
589    pub start: f64,
590    pub end: f64,
591}
592
593/// Calculates the decimal range used for indexing a number or quantity,
594/// based on the precision of the input value.
595///
596/// # Errors
597///
598/// Returns [`InsertableIndexError::FailedDowncast`] if `value` cannot be
599/// parsed as an `f64`.
600pub fn get_decimal_range(value: &str) -> Result<DecimalRange, InsertableIndexError> {
601    let decimal_precision = get_decimal_precision(value);
602    let parsed_v = value
603        .parse::<f64>()
604        .map_err(|_e| InsertableIndexError::FailedDowncast(value.to_string()))?;
605
606    Ok(DecimalRange {
607        start: parsed_v - 0.5 * 10f64.powi(-(decimal_precision.cast_signed())),
608        end: parsed_v + 0.5 * 10f64.powi(-(decimal_precision.cast_signed())),
609    })
610}
611
612fn fhirdecimal_to_quantity_range(value: Option<&FHIRDecimal>) -> Option<DecimalRange> {
613    value.as_ref().and_then(|v| {
614        v.value
615            .as_ref()
616            .and_then(|v| get_decimal_range(&v.to_string()).ok())
617    })
618}
619
620fn index_quantity(value: &dyn MetaValue) -> Result<Vec<QuantityRange>, InsertableIndexError> {
621    match value.fhir_type() {
622        "Range" => index_range_quantity(value),
623        "Age" => index_age_quantity(value),
624        "Money" => index_money_quantity(value),
625        "Duration" => index_duration_quantity(value),
626        "Quantity" => index_fhir_quantity(value),
627        _ => Err(InsertableIndexError::FailedDowncast(
628            value.fhir_type().to_string(),
629        )),
630    }
631}
632
633fn index_range_quantity(value: &dyn MetaValue) -> Result<Vec<QuantityRange>, InsertableIndexError> {
634    let fp_range = value
635        .as_any()
636        .downcast_ref::<Range>()
637        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
638
639    if fp_range.low.is_some() || fp_range.high.is_some() {
640        let start_value = fp_range
641            .low
642            .as_ref()
643            .and_then(|v| v.value.as_ref().and_then(|v| v.value));
644
645        let start_system = fp_range
646            .low
647            .as_ref()
648            .and_then(|v| v.system.as_ref().and_then(|s| s.value.clone()));
649
650        let start_code = fp_range
651            .low
652            .as_ref()
653            .and_then(|v| v.code.as_ref().and_then(|c| c.value.clone()));
654
655        let end_value = fp_range
656            .high
657            .as_ref()
658            .and_then(|v| v.value.as_ref().and_then(|v| v.value));
659
660        let end_system = fp_range
661            .high
662            .as_ref()
663            .and_then(|v| v.system.as_ref().and_then(|s| s.value.clone()));
664
665        let end_code = fp_range
666            .high
667            .as_ref()
668            .and_then(|v| v.code.as_ref().and_then(|c| c.value.clone()));
669
670        return Ok(vec![QuantityRange {
671            start_system,
672            start_code,
673            start_value: start_value.map_or(RangeValue::Infinity, RangeValue::Number),
674            end_system,
675            end_code,
676            end_value: end_value.map_or(RangeValue::Infinity, RangeValue::Number),
677        }]);
678    }
679
680    Ok(Vec::new())
681}
682
683fn index_age_quantity(value: &dyn MetaValue) -> Result<Vec<QuantityRange>, InsertableIndexError> {
684    let fp_age = value
685        .as_any()
686        .downcast_ref::<Age>()
687        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
688
689    Ok(fhirdecimal_to_quantity_range(fp_age.value.as_deref())
690        .map(|decimal_range| {
691            vec![QuantityRange {
692                start_system: fp_age.system.as_ref().and_then(|s| s.value.clone()),
693                start_code: fp_age.code.as_ref().and_then(|c| c.value.clone()),
694                start_value: RangeValue::Number(decimal_range.start),
695                end_system: fp_age.system.as_ref().and_then(|s| s.value.clone()),
696                end_code: fp_age.code.as_ref().and_then(|c| c.value.clone()),
697                end_value: RangeValue::Number(decimal_range.end),
698            }]
699        })
700        .unwrap_or_default())
701}
702
703fn index_money_quantity(value: &dyn MetaValue) -> Result<Vec<QuantityRange>, InsertableIndexError> {
704    let fp_money = value
705        .as_any()
706        .downcast_ref::<Money>()
707        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
708
709    Ok(fhirdecimal_to_quantity_range(fp_money.value.as_deref())
710        .map(|decimal_range| {
711            vec![QuantityRange {
712                start_system: Some("urn:iso:std:iso:4217".to_string()),
713                start_code: fp_money.currency.as_ref().and_then(|c| c.value.clone()),
714                start_value: RangeValue::Number(decimal_range.start),
715                end_system: Some("urn:iso:std:iso:4217".to_string()),
716                end_code: fp_money.currency.as_ref().and_then(|c| c.value.clone()),
717                end_value: RangeValue::Number(decimal_range.end),
718            }]
719        })
720        .unwrap_or_default())
721}
722
723fn index_duration_quantity(
724    value: &dyn MetaValue,
725) -> Result<Vec<QuantityRange>, InsertableIndexError> {
726    let fp_duration = value
727        .as_any()
728        .downcast_ref::<Duration>()
729        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
730
731    Ok(fhirdecimal_to_quantity_range(fp_duration.value.as_deref())
732        .map(|decimal_range| {
733            vec![QuantityRange {
734                start_system: fp_duration.system.as_ref().and_then(|s| s.value.clone()),
735                start_code: fp_duration.code.as_ref().and_then(|c| c.value.clone()),
736                start_value: RangeValue::Number(decimal_range.start),
737                end_system: fp_duration.system.as_ref().and_then(|s| s.value.clone()),
738                end_code: fp_duration.code.as_ref().and_then(|c| c.value.clone()),
739                end_value: RangeValue::Number(decimal_range.end),
740            }]
741        })
742        .unwrap_or_default())
743}
744
745fn index_fhir_quantity(value: &dyn MetaValue) -> Result<Vec<QuantityRange>, InsertableIndexError> {
746    let fp_quantity = value
747        .as_any()
748        .downcast_ref::<Quantity>()
749        .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?;
750
751    Ok(fhirdecimal_to_quantity_range(fp_quantity.value.as_deref())
752        .map(|decimal_range| {
753            vec![QuantityRange {
754                start_system: fp_quantity.system.as_ref().and_then(|s| s.value.clone()),
755                start_code: fp_quantity.code.as_ref().and_then(|c| c.value.clone()),
756                start_value: RangeValue::Number(decimal_range.start),
757                end_system: fp_quantity.system.as_ref().and_then(|s| s.value.clone()),
758                end_code: fp_quantity.code.as_ref().and_then(|c| c.value.clone()),
759                end_value: RangeValue::Number(decimal_range.end),
760            }]
761        })
762        .unwrap_or_default())
763}
764
765fn year_to_daterange(year: u16) -> Result<DateRange, InsertableIndexError> {
766    let start_date = chrono::NaiveDate::from_ymd_opt(i32::from(year), 1, 1)
767        .and_then(|d| d.and_hms_opt(0, 0, 0))
768        .ok_or_else(|| InsertableIndexError::FailedDowncast("Date".to_string()))?;
769
770    let end_date = chrono::NaiveDate::from_ymd_opt(i32::from(year) + 1, 1, 1)
771        .and_then(|d| d.pred_opt())
772        .and_then(|d| d.and_hms_milli_opt(23, 59, 59, 999))
773        .ok_or_else(|| InsertableIndexError::FailedDowncast("Date".to_string()))?;
774
775    Ok(DateRange {
776        start: chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(start_date, chrono::Utc)
777            .timestamp_millis(),
778        end: chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(end_date, chrono::Utc)
779            .timestamp_millis(),
780    })
781}
782
783fn year_month_to_daterange(year: u16, month: u8) -> Result<DateRange, InsertableIndexError> {
784    let start_date = chrono::NaiveDate::from_ymd_opt(i32::from(year), u32::from(month), 1)
785        .and_then(|d| d.and_hms_opt(0, 0, 0))
786        .ok_or_else(|| InsertableIndexError::FailedDowncast("Date".to_string()))?;
787
788    let end_date = if month < 12 {
789        chrono::NaiveDate::from_ymd_opt(i32::from(year), (month + 1).into(), 1)
790            .and_then(|d| d.pred_opt())
791            .and_then(|d| d.and_hms_milli_opt(23, 59, 59, 999))
792    } else {
793        chrono::NaiveDate::from_ymd_opt(i32::from(year) + 1, 1, 1)
794            .and_then(|d| d.pred_opt())
795            .and_then(|d| d.and_hms_milli_opt(23, 59, 59, 999))
796    }
797    .ok_or_else(|| InsertableIndexError::FailedDowncast("Date".to_string()))?;
798
799    Ok(DateRange {
800        start: chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(start_date, chrono::Utc)
801            .timestamp_millis(),
802        end: chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(end_date, chrono::Utc)
803            .timestamp_millis(),
804    })
805}
806
807fn year_month_day_to_daterange(
808    year: u16,
809    month: u8,
810    day: u8,
811) -> Result<DateRange, InsertableIndexError> {
812    let start_date =
813        chrono::NaiveDate::from_ymd_opt(i32::from(year), u32::from(month), u32::from(day))
814            .and_then(|d| d.and_hms_opt(0, 0, 0))
815            .ok_or_else(|| InsertableIndexError::FailedDowncast("Date".to_string()))?;
816
817    let end_date =
818        chrono::NaiveDate::from_ymd_opt(i32::from(year), u32::from(month), u32::from(day))
819            .and_then(|d| d.and_hms_milli_opt(23, 59, 59, 999))
820            .ok_or_else(|| InsertableIndexError::FailedDowncast("Date".to_string()))?;
821
822    Ok(DateRange {
823        start: chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(start_date, chrono::Utc)
824            .timestamp_millis(),
825        end: chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(end_date, chrono::Utc)
826            .timestamp_millis(),
827    })
828}
829
830/// Converts a FHIR date/time value into a date range suitable for indexing.
831///
832///
833/// # Errors
834///
835/// Returns an [`InsertableIndexError`] if the supplied year, year/month, or
836/// year/month/day value cannot be converted into a valid date range.
837pub fn date_time_range(value: &DateTime) -> Result<DateRange, InsertableIndexError> {
838    match value {
839        DateTime::Year(year) => Ok(year_to_daterange(*year)?),
840        DateTime::YearMonth(year, month) => Ok(year_month_to_daterange(*year, *month)?),
841        DateTime::YearMonthDay(year, month, day) => {
842            Ok(year_month_day_to_daterange(*year, *month, *day)?)
843        }
844        DateTime::Iso8601(date_time) => Ok(DateRange {
845            start: date_time.timestamp_millis(),
846            end: date_time.timestamp_millis(),
847        }),
848    }
849}
850
851fn index_date(value: &dyn MetaValue) -> Result<Vec<DateRange>, InsertableIndexError> {
852    match value.fhir_type() {
853        "Timing" => {
854            let fp_timing = value.as_any().downcast_ref::<Timing>().ok_or_else(|| {
855                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
856            })?;
857
858            if let Some(events) = fp_timing.event.as_ref() {
859                let date_ranges = events
860                    .iter()
861                    .map(|event| index_date(event))
862                    .collect::<Result<Vec<_>, _>>()?;
863                Ok(date_ranges.into_iter().flatten().collect())
864            } else {
865                Ok(Vec::new())
866            }
867        }
868        "date" => {
869            let fp_date = value
870                .as_any()
871                .downcast_ref::<FHIRDate>()
872                .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?
873                .value
874                .as_ref();
875
876            match &fp_date {
877                Some(Date::Year(year)) => Ok(vec![year_to_daterange(*year)?]),
878                Some(Date::YearMonth(year, month)) => {
879                    Ok(vec![year_month_to_daterange(*year, *month)?])
880                }
881                Some(Date::YearMonthDay(year, month, day)) => {
882                    Ok(vec![year_month_day_to_daterange(*year, *month, *day)?])
883                }
884                None => Ok(Vec::new()),
885            }
886        }
887        "dateTime" => {
888            let fp_datetime = value
889                .as_any()
890                .downcast_ref::<FHIRDateTime>()
891                .ok_or_else(|| InsertableIndexError::FailedDowncast(value.fhir_type().to_string()))?
892                .value
893                .as_ref();
894
895            match &fp_datetime {
896                Some(date_time) => date_time_range(date_time).map(|date_range| vec![date_range]),
897                None => Ok(Vec::new()),
898            }
899        }
900        "instant" => {
901            let fp_instant = value
902                .as_any()
903                .downcast_ref::<FHIRInstant>()
904                .ok_or_else(|| {
905                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
906                })?;
907
908            match &fp_instant.value {
909                Some(Instant::Iso8601(instant)) => {
910                    let timestamp = instant.timestamp_millis();
911                    Ok(vec![DateRange {
912                        start: timestamp,
913                        end: timestamp,
914                    }])
915                }
916                None => Ok(Vec::new()),
917            }
918        }
919        "Period" => {
920            let fp_period = value.as_any().downcast_ref::<Period>().ok_or_else(|| {
921                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
922            })?;
923            let fp_start = if let Some(date) = fp_period.start.as_ref() {
924                let date = date.as_ref();
925                let date_range = index_date(date)?;
926                date_range
927                    .first()
928                    .ok_or_else(|| {
929                        InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
930                    })?
931                    .start
932            } else {
933                0
934            };
935
936            let fp_end = if let Some(date) = fp_period.end.as_ref() {
937                let date = date.as_ref();
938                let date_range = index_date(date)?;
939                date_range
940                    .first()
941                    .ok_or_else(|| {
942                        InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
943                    })?
944                    .end
945            } else {
946                i64::MAX
947            };
948
949            Ok(vec![DateRange {
950                start: fp_start,
951                end: fp_end,
952            }])
953        }
954        _ => Err(InsertableIndexError::FailedDowncast(
955            value.fhir_type().to_string(),
956        )),
957    }
958}
959
960fn index_reference(value: &dyn MetaValue) -> Result<Vec<ReferenceIndex>, InsertableIndexError> {
961    match value.fhir_type() {
962        "Reference" => {
963            let fp_reference = value.as_any().downcast_ref::<Reference>().ok_or_else(|| {
964                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
965            })?;
966
967            if let Some(reference) = &fp_reference
968                .reference
969                .as_ref()
970                .and_then(|r| r.value.as_ref())
971            {
972                let parts: Vec<&str> = reference.split('/').collect();
973                if parts.len() == 2 {
974                    let resource_type = ResourceType::try_from(parts[0])?;
975                    let id = parts[1].to_string();
976                    return Ok(vec![ReferenceIndex {
977                        resource_type: Some(resource_type.as_ref().to_string()),
978                        id: Some(id),
979                        uri: None,
980                    }]);
981                }
982            }
983
984            Ok(vec![])
985        }
986        "canonical" => {
987            let fp_canonical = value
988                .as_any()
989                .downcast_ref::<FHIRCanonical>()
990                .ok_or_else(|| {
991                    InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
992                })?;
993            if let Some(canonical) = &fp_canonical.value {
994                return Ok(vec![ReferenceIndex {
995                    id: None,
996                    resource_type: None,
997                    uri: Some(canonical.clone()),
998                }]);
999            }
1000            Ok(vec![])
1001        }
1002        "uri" => {
1003            let fp_uri = value.as_any().downcast_ref::<FHIRUri>().ok_or_else(|| {
1004                InsertableIndexError::FailedDowncast(value.fhir_type().to_string())
1005            })?;
1006            if let Some(uri) = &fp_uri.value {
1007                return Ok(vec![ReferenceIndex {
1008                    id: None,
1009                    resource_type: None,
1010                    uri: Some(uri.clone()),
1011                }]);
1012            }
1013            Ok(vec![])
1014        }
1015        _ => Err(InsertableIndexError::FailedDowncast(
1016            value.fhir_type().to_string(),
1017        )),
1018    }
1019}
1020
1021/// Converts resolved search parameter values into an indexable representation.
1022///
1023/// Values that cannot be converted to the corresponding index type are
1024/// skipped.
1025///
1026/// # Errors
1027///
1028/// Returns [`OperationOutcomeError`] if the search parameter has an
1029/// unsupported or invalid type.
1030pub fn to_insertable_index(
1031    parameter: &ResolvedParameter,
1032    result: &[&dyn MetaValue],
1033) -> Result<InsertableIndex, OperationOutcomeError> {
1034    let search_parameter = &parameter.search_parameter;
1035    match &search_parameter.type_ {
1036        param_type if param_type == &SearchParamType::number() => {
1037            let numbers = result
1038                .iter()
1039                .filter_map(|v| index_number(*v).ok())
1040                .flatten()
1041                .collect::<Vec<_>>();
1042            Ok(InsertableIndex::Number(numbers))
1043        }
1044        param_type if param_type == &SearchParamType::string() => {
1045            let strings = result
1046                .iter()
1047                .filter_map(|v| index_string(*v).ok())
1048                .flatten()
1049                .collect();
1050            Ok(InsertableIndex::String(strings))
1051        }
1052        param_type if param_type == &SearchParamType::uri() => {
1053            let uris = result
1054                .iter()
1055                .filter_map(|v| index_uri(*v).ok())
1056                .flatten()
1057                .collect();
1058            Ok(InsertableIndex::URI(uris))
1059        }
1060        param_type if param_type == &SearchParamType::token() => {
1061            let tokens = result
1062                .iter()
1063                .filter_map(|v| index_token(*v).ok())
1064                .flatten()
1065                .collect();
1066            Ok(InsertableIndex::Token(tokens))
1067        }
1068        param_type if param_type == &SearchParamType::date() => {
1069            let dates = result
1070                .iter()
1071                .filter_map(|v| index_date(*v).ok())
1072                .flatten()
1073                .collect();
1074            Ok(InsertableIndex::Date(dates))
1075        }
1076        param_type if param_type == &SearchParamType::reference() => {
1077            let references = result
1078                .iter()
1079                .filter_map(|v: &&dyn MetaValue| index_reference(*v).ok())
1080                .flatten()
1081                .collect();
1082            Ok(InsertableIndex::Reference(references))
1083        }
1084        param_type if param_type == &SearchParamType::quantity() => {
1085            let quantities = result
1086                .iter()
1087                .filter_map(|v| index_quantity(*v).ok())
1088                .flatten()
1089                .collect();
1090            Ok(InsertableIndex::Quantity(quantities))
1091        }
1092        // Not Supported yet
1093        param_type if param_type == &SearchParamType::composite() => {
1094            Ok(InsertableIndex::Composite(vec![]))
1095        }
1096        param_type if param_type == &SearchParamType::special() => {
1097            Ok(InsertableIndex::Special(vec![]))
1098        }
1099        _ => {
1100            let type_name = search_parameter.type_.as_str();
1101            Err(InsertableIndexError::InvalidType(
1102                type_name.map_or("unknown".to_string(), std::string::ToString::to_string),
1103            )
1104            .into())
1105        }
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use super::*;
1112    use haste_fhir_model::r4::generated::types::{
1113        FHIRDate, FHIRDateTime, Period, Reference, Timing,
1114    };
1115
1116    #[test]
1117    fn test_year_month_to_daterange() {
1118        let year = 2023;
1119        let month: u8 = 5;
1120        let date_range = year_month_to_daterange(year, month).unwrap();
1121
1122        assert_eq!(
1123            date_range.start,
1124            chrono::DateTime::parse_from_rfc3339("2023-05-01T00:00:00Z")
1125                .unwrap()
1126                .timestamp_millis()
1127        );
1128        assert_eq!(
1129            date_range.end,
1130            chrono::DateTime::parse_from_rfc3339("2023-05-31T23:59:59.999Z")
1131                .unwrap()
1132                .timestamp_millis()
1133        );
1134    }
1135
1136    #[test]
1137    fn test_year_month_day_to_daterange() {
1138        let year = 2023;
1139        let month: u8 = 5;
1140        let day = 15;
1141        let date_range = year_month_day_to_daterange(year, month, day).unwrap();
1142
1143        assert_eq!(
1144            date_range.start,
1145            chrono::DateTime::parse_from_rfc3339("2023-05-15T00:00:00Z")
1146                .unwrap()
1147                .timestamp_millis()
1148        );
1149        assert_eq!(
1150            date_range.end,
1151            chrono::DateTime::parse_from_rfc3339("2023-05-15T23:59:59.999Z")
1152                .unwrap()
1153                .timestamp_millis()
1154        );
1155    }
1156
1157    #[test]
1158    fn test_year_to_daterange() {
1159        let year = 2023;
1160        let date_range = year_to_daterange(year).unwrap();
1161        assert_eq!(
1162            date_range.start,
1163            chrono::DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z")
1164                .unwrap()
1165                .timestamp_millis()
1166        );
1167        assert_eq!(
1168            date_range.end,
1169            chrono::DateTime::parse_from_rfc3339("2023-12-31T23:59:59.999Z")
1170                .unwrap()
1171                .timestamp_millis()
1172        );
1173    }
1174
1175    #[test]
1176    fn test_index_date() {
1177        let date_value = FHIRDate {
1178            id: None,
1179            extension: None,
1180            value: Some(Date::Year(2023)),
1181        };
1182        let result = index_date(&date_value).unwrap();
1183        assert_eq!(result.len(), 1);
1184        assert_eq!(
1185            result[0].start,
1186            chrono::DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z")
1187                .unwrap()
1188                .timestamp_millis()
1189        );
1190        assert_eq!(
1191            result[0].end,
1192            chrono::DateTime::parse_from_rfc3339("2023-12-31T23:59:59.999Z")
1193                .unwrap()
1194                .timestamp_millis()
1195        );
1196    }
1197
1198    #[test]
1199    fn date_range_instant() {
1200        let fhir_date = FHIRDateTime {
1201            id: None,
1202            extension: None,
1203            value: Some(DateTime::Iso8601(
1204                chrono::DateTime::parse_from_rfc3339("2023-05-14T11:25:25.234-05:00")
1205                    .unwrap()
1206                    .with_timezone(&chrono::Utc),
1207            )),
1208        };
1209
1210        let range = index_date(&fhir_date).unwrap();
1211        let date_range = range.first().unwrap();
1212
1213        assert_eq!(
1214            date_range.start,
1215            chrono::DateTime::parse_from_rfc3339("2023-05-14T11:25:25.234-05:00")
1216                .unwrap()
1217                .with_timezone(&chrono::Utc)
1218                .timestamp_millis()
1219        );
1220        assert_eq!(
1221            date_range.end,
1222            chrono::DateTime::parse_from_rfc3339("2023-05-14T11:25:25.234-05:00")
1223                .unwrap()
1224                .with_timezone(&chrono::Utc)
1225                .timestamp_millis()
1226        );
1227    }
1228
1229    #[test]
1230    fn date_range_period() {
1231        let start = FHIRDateTime {
1232            id: None,
1233            extension: None,
1234            value: Some(DateTime::Year(2023)),
1235        };
1236
1237        let end = FHIRDateTime {
1238            id: None,
1239            extension: None,
1240            value: Some(DateTime::YearMonthDay(2023, 5, 15)),
1241        };
1242
1243        let period = Period {
1244            id: None,
1245            extension: None,
1246            start: Some(Box::new(start)),
1247            end: Some(Box::new(end)),
1248        };
1249
1250        let range = index_date(&period).unwrap();
1251        let date_range = range.first().unwrap();
1252
1253        assert_eq!(
1254            date_range.start,
1255            chrono::DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z")
1256                .unwrap()
1257                .timestamp_millis()
1258        );
1259        assert_eq!(
1260            date_range.end,
1261            chrono::DateTime::parse_from_rfc3339("2023-05-15T23:59:59.999Z")
1262                .unwrap()
1263                .timestamp_millis()
1264        );
1265    }
1266
1267    #[test]
1268    fn date_range_missing() {
1269        let start = FHIRDateTime {
1270            id: None,
1271            extension: None,
1272            value: Some(DateTime::Year(2023)),
1273        };
1274
1275        let end = FHIRDateTime {
1276            id: None,
1277            extension: None,
1278            value: Some(DateTime::YearMonthDay(2023, 5, 15)),
1279        };
1280
1281        let period = Period {
1282            id: None,
1283            extension: None,
1284            start: None,
1285            end: Some(Box::new(end)),
1286        };
1287
1288        let range = index_date(&period).unwrap();
1289        let date_range = range.first().unwrap();
1290
1291        assert_eq!(date_range.start, 0);
1292        assert_eq!(
1293            date_range.end,
1294            chrono::DateTime::parse_from_rfc3339("2023-05-15T23:59:59.999Z")
1295                .unwrap()
1296                .timestamp_millis()
1297        );
1298
1299        let period = Period {
1300            id: None,
1301            extension: None,
1302            start: Some(Box::new(start)),
1303            end: None,
1304        };
1305
1306        let range = index_date(&period).unwrap();
1307        let date_range = range.first().unwrap();
1308
1309        assert_eq!(
1310            date_range.start,
1311            chrono::DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z")
1312                .unwrap()
1313                .timestamp_millis()
1314        );
1315        assert_eq!(date_range.end, i64::MAX);
1316    }
1317
1318    #[test]
1319    fn test_date_range_end() {
1320        let year = 2023;
1321        let month: u8 = 12;
1322        let day = 31;
1323        let date_range = year_month_day_to_daterange(year, month, day).unwrap();
1324
1325        assert_eq!(
1326            date_range.start,
1327            chrono::DateTime::parse_from_rfc3339("2023-12-31T00:00:00Z")
1328                .unwrap()
1329                .timestamp_millis()
1330        );
1331
1332        assert_eq!(
1333            date_range.end,
1334            chrono::DateTime::parse_from_rfc3339("2023-12-31T23:59:59.999Z")
1335                .unwrap()
1336                .timestamp_millis()
1337        );
1338
1339        let date_range = year_month_to_daterange(year, month).unwrap();
1340
1341        assert_eq!(
1342            date_range.start,
1343            chrono::DateTime::parse_from_rfc3339("2023-12-01T00:00:00Z")
1344                .unwrap()
1345                .timestamp_millis()
1346        );
1347
1348        assert_eq!(
1349            date_range.end,
1350            chrono::DateTime::parse_from_rfc3339("2023-12-31T23:59:59.999Z")
1351                .unwrap()
1352                .timestamp_millis()
1353        );
1354    }
1355
1356    #[test]
1357    fn test_timing() {
1358        let timing = Timing {
1359            event: Some(vec![
1360                FHIRDateTime {
1361                    id: None,
1362                    extension: None,
1363                    value: Some(DateTime::YearMonthDay(2023, 12, 31)),
1364                },
1365                FHIRDateTime {
1366                    id: None,
1367                    extension: None,
1368                    value: Some(DateTime::YearMonthDay(2024, 1, 1)),
1369                },
1370            ]),
1371            ..Default::default()
1372        };
1373
1374        let date_ranges = index_date(&timing).unwrap();
1375        assert_eq!(date_ranges.len(), 2);
1376
1377        assert_eq!(
1378            date_ranges[0],
1379            DateRange {
1380                start: chrono::DateTime::parse_from_rfc3339("2023-12-31T00:00:00Z")
1381                    .unwrap()
1382                    .timestamp_millis(),
1383                end: chrono::DateTime::parse_from_rfc3339("2023-12-31T23:59:59.999Z")
1384                    .unwrap()
1385                    .timestamp_millis(),
1386            }
1387        );
1388
1389        assert_eq!(
1390            date_ranges[1],
1391            DateRange {
1392                start: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
1393                    .unwrap()
1394                    .timestamp_millis(),
1395                end: chrono::DateTime::parse_from_rfc3339("2024-01-01T23:59:59.999Z")
1396                    .unwrap()
1397                    .timestamp_millis(),
1398            }
1399        );
1400    }
1401
1402    #[test]
1403    fn test_indexing_reference() {
1404        let reference = Reference {
1405            type_: None,
1406            identifier_: None,
1407            display: None,
1408            id: None,
1409            extension: None,
1410            reference: Some(Box::new(FHIRString {
1411                id: None,
1412                extension: None,
1413                value: Some("Patient/123".to_string()),
1414            })),
1415        };
1416
1417        let result = index_reference(&reference).unwrap();
1418        assert_eq!(result.len(), 1);
1419        assert_eq!(result[0].resource_type, Some("Patient".to_string()));
1420        assert_eq!(result[0].id, Some("123".to_string()));
1421        assert!(result[0].uri.is_none());
1422    }
1423
1424    #[test]
1425    fn test_indexing_invalid_reference() {
1426        let reference = Reference {
1427            type_: None,
1428            identifier_: None,
1429            display: None,
1430            id: None,
1431            extension: None,
1432            reference: Some(Box::new(FHIRString {
1433                id: None,
1434                extension: None,
1435                value: Some("BadType/123".to_string()),
1436            })),
1437        };
1438
1439        let result = index_reference(&reference);
1440
1441        assert!(result.is_err());
1442    }
1443
1444    #[test]
1445    fn test_canonical_index() {
1446        let canonical = FHIRCanonical {
1447            id: None,
1448            extension: None,
1449            value: Some("http://example.com/CanonicalResource".to_string()),
1450        };
1451        let result = index_reference(&canonical).unwrap();
1452        assert_eq!(result.len(), 1);
1453        assert!(result[0].id.is_none());
1454        assert!(result[0].resource_type.is_none());
1455        assert_eq!(
1456            result[0].uri,
1457            Some("http://example.com/CanonicalResource".to_string())
1458        );
1459    }
1460
1461    #[test]
1462    fn test_uri_indexing() {
1463        let uri = FHIRUri {
1464            id: None,
1465            extension: None,
1466            value: Some("http://example.com/URIResource".to_string()),
1467        };
1468        let result = index_reference(&uri).unwrap();
1469        assert_eq!(result.len(), 1);
1470        assert!(result[0].id.is_none());
1471        assert!(result[0].resource_type.is_none());
1472        assert_eq!(
1473            result[0].uri,
1474            Some("http://example.com/URIResource".to_string())
1475        );
1476    }
1477}