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