Skip to main content

haste_server/
load_artifacts.rs

1use std::{collections::HashSet, sync::Arc};
2
3use crate::{ServerEnvironmentVariables, fhir_client::ServerCTX, services::create_services};
4use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
5use haste_artifacts::ARTIFACT_RESOURCES;
6use haste_config::Config;
7use haste_fhir_client::{
8    FHIRClient,
9    request::{FHIRSearchTypeRequest, SearchRequest},
10    url::{Parameter, ParsedParameter, ParsedParameters},
11};
12use haste_fhir_model::r4::generated::{
13    resources::{Resource, ResourceType, SearchParameter, StructureDefinition},
14    terminology::IssueType,
15    types::{Coding, FHIRCode, FHIRUri, Meta},
16};
17use haste_fhir_operation_error::OperationOutcomeError;
18use haste_fhir_search::{SearchEngine, SearchOptions};
19use haste_jwt::{ProjectId, TenantId};
20
21use haste_repository::{Repository, fhir::CachePolicy, types::SupportedFHIRVersions};
22use sha1::{Digest, Sha1};
23
24fn generate_sha256_hash(value: &Resource) -> String {
25    let json = haste_fhir_serialization_json::to_string(value).expect("failed to serialize value.");
26    let mut sha_hasher = Sha1::new();
27    sha_hasher.update(json.as_bytes());
28    let sha1 = sha_hasher.finalize();
29
30    let sha_string = URL_SAFE_NO_PAD.encode(&sha1);
31
32    sha_string
33}
34
35static HASH_TAG_SYSTEM: &str = "https://haste.health/fhir/CodeSystem/hash";
36
37fn _add_hash_tag(meta: &mut Option<Box<Meta>>, sha_hash: String) {
38    let hash_tag = Box::new(Coding {
39        system: Some(Box::new(FHIRUri {
40            value: Some(HASH_TAG_SYSTEM.to_string()),
41            ..Default::default()
42        })),
43        code: Some(Box::new(FHIRCode {
44            value: Some(sha_hash),
45            ..Default::default()
46        })),
47        ..Default::default()
48    });
49
50    let meta = if let Some(meta) = meta {
51        meta
52    } else {
53        *meta = Some(Box::new(Meta::default()));
54        meta.as_mut().unwrap()
55    };
56
57    match &mut meta.tag {
58        Some(tags) => tags.push(hash_tag),
59        None => meta.tag = Some(vec![hash_tag]),
60    }
61}
62
63fn add_hash_tag(resource: &mut Resource, sha_hash: String) {
64    match resource {
65        Resource::StructureDefinition(structure_definition) => {
66            _add_hash_tag(&mut structure_definition.meta, sha_hash)
67        }
68        Resource::CodeSystem(code_system) => _add_hash_tag(&mut code_system.meta, sha_hash),
69        Resource::ValueSet(value_set) => _add_hash_tag(&mut value_set.meta, sha_hash),
70        Resource::SearchParameter(search_parameter) => {
71            _add_hash_tag(&mut search_parameter.meta, sha_hash)
72        }
73        _ => {}
74    }
75}
76
77fn get_id(resource: &Resource) -> String {
78    match resource {
79        Resource::StructureDefinition(structure_definition) => {
80            structure_definition.id.clone().unwrap_or_default()
81        }
82        Resource::CodeSystem(code_system) => code_system.id.clone().unwrap_or_default(),
83        Resource::ValueSet(value_set) => value_set.id.clone().unwrap_or_default(),
84        Resource::SearchParameter(search_parameter) => {
85            search_parameter.id.clone().unwrap_or_default()
86        }
87        _ => todo!("Unsupported resource type"),
88    }
89}
90
91pub fn get_resource_type(resource: &Resource) -> ResourceType {
92    match resource {
93        Resource::StructureDefinition(_) => ResourceType::StructureDefinition,
94        Resource::CodeSystem(_) => ResourceType::CodeSystem,
95        Resource::ValueSet(_) => ResourceType::ValueSet,
96        Resource::SearchParameter(_) => ResourceType::SearchParameter,
97        _ => todo!("Unsupported resource type"),
98    }
99}
100
101pub async fn load_artifacts(
102    config: Arc<dyn Config<ServerEnvironmentVariables>>,
103) -> Result<(), OperationOutcomeError> {
104    let services = create_services(config.clone()).await?;
105
106    let ctx = Arc::new(ServerCTX::system(
107        TenantId::System,
108        ProjectId::System,
109        services.fhir_client.clone(),
110        services.rate_limit.clone(),
111    ));
112
113    let mut hashes = HashSet::new();
114
115    for resource in ARTIFACT_RESOURCES.iter() {
116        let sha_hash = generate_sha256_hash(*&resource);
117        hashes.insert(sha_hash);
118
119        match &**resource {
120            Resource::SearchParameter(_)
121            | Resource::CodeSystem(_)
122            | Resource::ValueSet(_)
123            | Resource::StructureDefinition(_) => {
124                let mut resource = (**resource).clone();
125                let resource_type = get_resource_type(&resource);
126                let id = get_id(&resource);
127                let sha_hash = generate_sha256_hash(&resource);
128
129                add_hash_tag(&mut resource, sha_hash.clone());
130
131                let res = services
132                    .fhir_client
133                    .conditional_update(
134                        ctx.clone(),
135                        resource_type.clone(),
136                        ParsedParameters::new(vec![
137                            ParsedParameter::Resource(Parameter {
138                                name: "_id".to_string(),
139                                value: vec![id.clone()],
140                                modifier: None,
141                                chains: None,
142                            }),
143                            ParsedParameter::Resource(Parameter {
144                                name: "_tag".to_string(),
145                                value: vec![HASH_TAG_SYSTEM.to_string() + "|" + &sha_hash],
146                                modifier: Some("not".to_string()),
147                                chains: None,
148                            }),
149                        ]),
150                        resource.clone(),
151                    )
152                    .await;
153
154                if let Ok(_res) = res {
155                    println!("Updated {}", resource_type.as_ref());
156                } else if let Err(err) = res {
157                    if let IssueType::Invalid(_) = err.outcome().issue[0].code.as_ref() {
158                        println!("BACKTRACE: {}", err.backtrace().unwrap());
159                        panic!("INVALID");
160                    }
161                }
162            }
163            _ => {
164                // println!("Skipping resource.");
165            }
166        }
167    }
168
169    println!(
170        "Loaded a total of '{}' artifacts with unique hashes '{}'",
171        ARTIFACT_RESOURCES.len(),
172        hashes.len(),
173    );
174
175    Ok(())
176}
177
178pub async fn get_all_sds<Repo: Repository, Search: SearchEngine>(
179    kinds: &[&str],
180    repo: &Repo,
181    search_engine: &Search,
182) -> Result<Vec<StructureDefinition>, OperationOutcomeError> {
183    let sd_search = FHIRSearchTypeRequest {
184        resource_type: ResourceType::StructureDefinition,
185        parameters: ParsedParameters::new(vec![
186            ParsedParameter::Resource(Parameter {
187                name: "kind".to_string(),
188                value: kinds.iter().map(|s| s.to_string()).collect(),
189                modifier: None,
190                chains: None,
191            }),
192            ParsedParameter::Resource(Parameter {
193                name: "abstract".to_string(),
194                value: vec!["false".to_string()],
195                modifier: None,
196                chains: None,
197            }),
198            ParsedParameter::Resource(Parameter {
199                name: "derivation".to_string(),
200                value: vec!["specialization".to_string()],
201                modifier: None,
202                chains: None,
203            }),
204            // ParsedParameter::Result(Parameter {
205            //     name: "_sort".to_string(),
206            //     value: vec!["url".to_string()],
207            //     modifier: None,
208            //     chains: None,
209            // }),
210        ]),
211    };
212    let sd_results = search_engine
213        .search(
214            &SupportedFHIRVersions::R4,
215            &TenantId::System,
216            &ProjectId::System,
217            &SearchRequest::Type(sd_search),
218            Some(SearchOptions { count_limit: false }),
219        )
220        .await?;
221
222    let version_ids = sd_results
223        .entries
224        .iter()
225        .map(|v| &v.version_id)
226        .collect::<Vec<_>>();
227
228    let sds = repo
229        .read_by_version_ids(
230            &TenantId::System,
231            &ProjectId::System,
232            version_ids.as_slice(),
233            CachePolicy::NoCache,
234        )
235        .await?
236        .into_iter()
237        .filter_map(|r| match r {
238            Resource::StructureDefinition(sd) => Some(sd),
239            _ => None,
240        });
241
242    Ok(sds.collect())
243}
244
245pub async fn get_all_sps<Repo: Repository, Search: SearchEngine>(
246    repo: &Repo,
247    search_engine: &Search,
248) -> Result<Vec<SearchParameter>, OperationOutcomeError> {
249    let sp_search = FHIRSearchTypeRequest {
250        resource_type: ResourceType::SearchParameter,
251        parameters: ParsedParameters::new(vec![]),
252    };
253    let sp_results = search_engine
254        .search(
255            &SupportedFHIRVersions::R4,
256            &TenantId::System,
257            &ProjectId::System,
258            &SearchRequest::Type(sp_search),
259            Some(SearchOptions { count_limit: false }),
260        )
261        .await?;
262
263    let version_ids = sp_results
264        .entries
265        .iter()
266        .map(|v| &v.version_id)
267        .collect::<Vec<_>>();
268
269    let sps = repo
270        .read_by_version_ids(
271            &TenantId::System,
272            &ProjectId::System,
273            version_ids.as_slice(),
274            CachePolicy::NoCache,
275        )
276        .await?
277        .into_iter()
278        .filter_map(|r| match r {
279            Resource::SearchParameter(sp) => Some(sp),
280            _ => None,
281        });
282
283    Ok(sps.collect())
284}