haste_server/
load_artifacts.rs1use 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 }
162 }
163 }
164
165 println!(
166 "Done loading artifacts. {} {}",
167 hashes.len(),
168 ARTIFACT_RESOURCES.len()
169 );
170
171 Ok(())
172}