Skip to main content

haste_fhir_search/memory/
mod.rs

1use crate::{ParameterLevel, ResolvedParameter, SearchParameterResolve};
2use haste_artifacts::R4_SEARCH_PARAMETERS;
3use haste_fhir_model::r4::generated::resources::{Resource, ResourceType, SearchParameter};
4use haste_fhir_operation_error::OperationOutcomeError;
5use haste_jwt::{ProjectId, TenantId};
6use std::{
7    collections::HashMap,
8    sync::{Arc, LazyLock},
9};
10
11#[derive(Debug)]
12pub enum ArtifactError {
13    InvalidResource(String),
14}
15
16#[derive(Default, Clone)]
17pub struct SearchParametersIndex {
18    by_url: HashMap<String, ResolvedParameter>,
19    by_resource_type: HashMap<String, HashMap<String, ResolvedParameter>>,
20}
21
22impl SearchParametersIndex {
23    /// Every indexed parameter, resolved synchronously.
24    ///
25    /// `SearchParameterResolve::all` is async only to accommodate resolvers
26    /// that hit a database; this index is pure in-memory, so callers that run
27    /// outside a runtime (a `LazyLock` initializer, for instance) can read it
28    /// directly.
29    #[must_use]
30    pub fn all_parameters(&self) -> Vec<ResolvedParameter> {
31        self.by_url.values().cloned().collect()
32    }
33}
34
35impl SearchParameterResolve for SearchParametersIndex {
36    fn by_resource_type(
37        &self,
38        _tenant: &TenantId,
39        _project: &ProjectId,
40        resource_type: &ResourceType,
41    ) -> impl Future<Output = Result<Vec<ResolvedParameter>, OperationOutcomeError>> {
42        let mut return_vec = Vec::new();
43
44        if let Some(domain_params) = self
45            .by_resource_type
46            .get("DomainResource")
47            .map(|d| d.values().cloned())
48        {
49            return_vec.extend(domain_params);
50        }
51
52        if let Some(resource_params) = self
53            .by_resource_type
54            .get("Resource")
55            .map(|r| r.values().cloned())
56        {
57            return_vec.extend(resource_params);
58        }
59
60        if let Some(params) = self.by_resource_type.get(resource_type.as_ref()) {
61            return_vec.extend(params.values().cloned());
62        }
63
64        std::future::ready(Ok(return_vec))
65    }
66
67    fn by_name(
68        &self,
69        _tenant: &TenantId,
70        _project: &ProjectId,
71        resource_type: Option<&ResourceType>,
72        name: &str,
73    ) -> impl Future<Output = Result<Option<ResolvedParameter>, OperationOutcomeError>> {
74        std::future::ready(Ok(resource_type
75            .and_then(|resource_type| self.by_resource_type.get(resource_type.as_ref()))
76            .and_then(|params| params.get(name))
77            .or_else(|| {
78                self.by_resource_type
79                    .get("Resource")
80                    .and_then(|params| params.get(name))
81            })
82            .or_else(|| {
83                self.by_resource_type
84                    .get("DomainResource")
85                    .and_then(|params| params.get(name))
86            })
87            .cloned()))
88    }
89
90    fn all(
91        &self,
92        _tenant: &TenantId,
93        _project: &ProjectId,
94    ) -> impl Future<Output = Result<Vec<ResolvedParameter>, OperationOutcomeError>> {
95        std::future::ready(Ok(self.all_parameters()))
96    }
97}
98
99fn build_search_parameter_index_map(
100    level: &ParameterLevel,
101    index: &mut SearchParametersIndex,
102    resource: Resource,
103) -> Result<(), ArtifactError> {
104    match resource {
105        Resource::Bundle(bundle) => {
106            let params = bundle
107                .entry
108                .unwrap_or(vec![])
109                .into_iter()
110                .filter_map(|e| e.resource)
111                .filter_map(|resource| match *resource {
112                    Resource::SearchParameter(search_param) => Some(Arc::new(search_param)),
113                    _ => None,
114                });
115
116            for param in params {
117                index.by_url.insert(
118                    param.id.clone().unwrap(),
119                    ResolvedParameter::new(level.clone(), param.clone()),
120                );
121                for resource_type in &param.base {
122                    let resource_type = (*resource_type).as_str();
123                    if let Some(resource_type) = resource_type {
124                        index
125                            .by_resource_type
126                            .entry(resource_type.to_string())
127                            .or_default()
128                            .insert(
129                                param.code.value.as_ref().unwrap().clone(),
130                                ResolvedParameter::new(level.clone(), param.clone()),
131                            );
132                    }
133                }
134            }
135
136            Ok(())
137        }
138        Resource::SearchParameter(search_param) => {
139            let param = Arc::new(search_param);
140            index.by_url.insert(
141                param.id.clone().unwrap(),
142                ResolvedParameter::new(level.clone(), param.clone()),
143            );
144            for resource_type in &param.base {
145                let resource_type = (*resource_type).as_str();
146                if let Some(resource_type) = resource_type.as_ref() {
147                    index
148                        .by_resource_type
149                        .entry(resource_type.to_string())
150                        .or_default()
151                        .insert(
152                            param.code.value.as_ref().unwrap().clone(),
153                            ResolvedParameter::new(level.clone(), param.clone()),
154                        );
155                }
156            }
157            Ok(())
158        }
159        _ => Err(ArtifactError::InvalidResource(
160            "Expected a Bundle resource".to_string(),
161        )),
162    }
163}
164
165pub static R4_SEARCH_PARAMETERS_INDEX: LazyLock<Arc<SearchParametersIndex>> = LazyLock::new(|| {
166    Arc::new(create_index_map(
167        &ParameterLevel::System,
168        R4_SEARCH_PARAMETERS.iter().cloned().collect(),
169    ))
170});
171
172/// Creates an index of search parameters for the specified parameter level.
173///
174/// # Panics
175///
176/// Panics if a search parameter cannot be added to the index.
177#[must_use]
178pub fn create_index_map(
179    level: &ParameterLevel,
180    search_parameters: Vec<SearchParameter>,
181) -> SearchParametersIndex {
182    let mut index = SearchParametersIndex::default();
183
184    for param in search_parameters {
185        build_search_parameter_index_map(level, &mut index, Resource::SearchParameter(param))
186            .expect("Failed to build search parameter index");
187    }
188
189    index
190}