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    url::{Parameter, ParsedParameter, ParsedParameters},
10};
11use haste_fhir_model::r4::generated::{
12    resources::{Resource, ResourceType},
13    terminology::IssueType,
14    types::{Coding, FHIRCode, FHIRUri, Meta},
15};
16use haste_fhir_operation_error::OperationOutcomeError;
17use haste_jwt::{ProjectId, TenantId};
18
19use sha1::{Digest, Sha1};
20
21fn generate_sha256_hash(value: &Resource) -> String {
22    let json = haste_fhir_serialization_json::to_string(value).expect("failed to serialize value.");
23    let mut sha_hasher = Sha1::new();
24    sha_hasher.update(json.as_bytes());
25    let sha1 = sha_hasher.finalize();
26
27    let sha_string = URL_SAFE_NO_PAD.encode(&sha1);
28
29    sha_string
30}
31
32static HASH_TAG_SYSTEM: &str = "https://haste.health/fhir/CodeSystem/hash";
33
34fn _add_hash_tag(meta: &mut Option<Box<Meta>>, sha_hash: String) {
35    let hash_tag = Box::new(Coding {
36        system: Some(Box::new(FHIRUri {
37            value: Some(HASH_TAG_SYSTEM.to_string()),
38            ..Default::default()
39        })),
40        code: Some(Box::new(FHIRCode {
41            value: Some(sha_hash),
42            ..Default::default()
43        })),
44        ..Default::default()
45    });
46
47    let meta = if let Some(meta) = meta {
48        meta
49    } else {
50        *meta = Some(Box::new(Meta::default()));
51        meta.as_mut().unwrap()
52    };
53
54    match &mut meta.tag {
55        Some(tags) => tags.push(hash_tag),
56        None => meta.tag = Some(vec![hash_tag]),
57    }
58}
59
60fn add_hash_tag(resource: &mut Resource, sha_hash: String) {
61    match resource {
62        Resource::StructureDefinition(structure_definition) => {
63            _add_hash_tag(&mut structure_definition.meta, sha_hash)
64        }
65        Resource::CodeSystem(code_system) => _add_hash_tag(&mut code_system.meta, sha_hash),
66        Resource::ValueSet(value_set) => _add_hash_tag(&mut value_set.meta, sha_hash),
67        Resource::SearchParameter(search_parameter) => {
68            _add_hash_tag(&mut search_parameter.meta, sha_hash)
69        }
70        _ => {}
71    }
72}
73
74fn get_id(resource: &Resource) -> String {
75    match resource {
76        Resource::StructureDefinition(structure_definition) => {
77            structure_definition.id.clone().unwrap_or_default()
78        }
79        Resource::CodeSystem(code_system) => code_system.id.clone().unwrap_or_default(),
80        Resource::ValueSet(value_set) => value_set.id.clone().unwrap_or_default(),
81        Resource::SearchParameter(search_parameter) => {
82            search_parameter.id.clone().unwrap_or_default()
83        }
84        _ => todo!("Unsupported resource type"),
85    }
86}
87
88pub fn get_resource_type(resource: &Resource) -> ResourceType {
89    match resource {
90        Resource::StructureDefinition(_) => ResourceType::StructureDefinition,
91        Resource::CodeSystem(_) => ResourceType::CodeSystem,
92        Resource::ValueSet(_) => ResourceType::ValueSet,
93        Resource::SearchParameter(_) => ResourceType::SearchParameter,
94        _ => todo!("Unsupported resource type"),
95    }
96}
97
98pub async fn load_artifacts(
99    config: Arc<dyn Config<ServerEnvironmentVariables>>,
100) -> Result<(), OperationOutcomeError> {
101    let services = create_services(config.clone()).await?;
102
103    let ctx = Arc::new(ServerCTX::system(
104        TenantId::System,
105        ProjectId::System,
106        services.fhir_client.clone(),
107    ));
108
109    let mut hashes = HashSet::new();
110
111    for resource in ARTIFACT_RESOURCES.iter() {
112        let sha_hash = generate_sha256_hash(*&resource);
113        hashes.insert(sha_hash);
114
115        match &**resource {
116            Resource::SearchParameter(_)
117            | Resource::CodeSystem(_)
118            | Resource::ValueSet(_)
119            | Resource::StructureDefinition(_) => {
120                let mut resource = (**resource).clone();
121                let resource_type = get_resource_type(&resource);
122                let id = get_id(&resource);
123                let sha_hash = generate_sha256_hash(&resource);
124
125                add_hash_tag(&mut resource, sha_hash.clone());
126
127                let res = services
128                    .fhir_client
129                    .conditional_update(
130                        ctx.clone(),
131                        resource_type.clone(),
132                        ParsedParameters::new(vec![
133                            ParsedParameter::Resource(Parameter {
134                                name: "_id".to_string(),
135                                value: vec![id.clone()],
136                                modifier: None,
137                                chains: None,
138                            }),
139                            ParsedParameter::Resource(Parameter {
140                                name: "_tag".to_string(),
141                                value: vec![HASH_TAG_SYSTEM.to_string() + "|" + &sha_hash],
142                                modifier: Some("not".to_string()),
143                                chains: None,
144                            }),
145                        ]),
146                        resource.clone(),
147                    )
148                    .await;
149
150                if let Ok(_res) = res {
151                    println!("Updated {}", resource_type.as_ref());
152                } else if let Err(err) = res {
153                    if let IssueType::Invalid(_) = err.outcome().issue[0].code.as_ref() {
154                        println!("BACKTRACE: {}", err.backtrace().unwrap());
155                        panic!("INVALID");
156                    }
157                }
158            }
159            _ => {
160                // println!("Skipping resource.");
161            }
162        }
163    }
164
165    println!(
166        "Done loading artifacts. {} {}",
167        hashes.len(),
168        ARTIFACT_RESOURCES.len()
169    );
170
171    Ok(())
172}