1use crate::{FHIRTerminology, resolvers::CanonicalResolver};
2use haste_fhir_generated_ops::generated::{CodeSystemLookup, ValueSetExpand, ValueSetValidateCode};
3use haste_fhir_model::r4::generated::{
4 resources::{
5 CodeSystem, CodeSystemConcept, Resource, ResourceType, ValueSet, ValueSetComposeInclude,
6 ValueSetComposeIncludeConceptDesignation, ValueSetExpansion, ValueSetExpansionContains,
7 },
8 terminology::{CodesystemContentMode, IssueType},
9 types::{FHIRString, FHIRUri},
10};
11use haste_fhir_operation_error::OperationOutcomeError;
12use std::{borrow::Cow, pin::Pin, sync::Arc};
13
14pub struct FHIRCanonicalTerminology<Resolver: CanonicalResolver> {
15 resolver: Arc<Resolver>,
16}
17
18impl<Resolver: CanonicalResolver> FHIRCanonicalTerminology<Resolver> {
19 pub fn new(resolver: Resolver) -> Self {
20 FHIRCanonicalTerminology {
21 resolver: Arc::new(resolver),
22 }
23 }
24}
25
26async fn resolve_valueset<Resolver: CanonicalResolver>(
27 canonical_resolution: Arc<Resolver>,
28 input: ValueSetExpand::Input,
29) -> Result<Option<ValueSet>, OperationOutcomeError> {
30 if let Some(valueset) = input.valueSet.as_ref() {
31 return Ok(Some(valueset.clone()));
32 } else if let Some(url) = &input.url.as_ref().and_then(|u| u.value.as_ref()) {
33 let Resource::ValueSet(value_set) = canonical_resolution
34 .resolve(ResourceType::ValueSet, url.to_string())
35 .await?
36 else {
37 return Ok(None);
38 };
39
40 return Ok(Some(value_set));
41 }
42 Ok(None)
43}
44
45fn are_codes_inline(include: &ValueSetComposeInclude) -> bool {
46 include.concept.is_some()
47}
48
49fn codes_inline_to_expansion(include: &ValueSetComposeInclude) -> Vec<ValueSetExpansionContains> {
50 include
51 .concept
52 .as_ref()
53 .map(|v| Cow::Borrowed(v))
54 .unwrap_or(Cow::Owned(vec![]))
55 .iter()
56 .map(|c| ValueSetExpansionContains {
57 system: include.system.clone(),
58 code: Some(c.code.clone()),
59 display: c.display.clone(),
60 ..Default::default()
61 })
62 .collect()
63}
64
65async fn resolve_codesystem<Resolver: CanonicalResolver>(
66 canonical_resolution: Arc<Resolver>,
67 url: &str,
68) -> Result<Option<CodeSystem>, OperationOutcomeError> {
69 let Resource::CodeSystem(code_system) = canonical_resolution
70 .resolve(ResourceType::CodeSystem, url.to_string())
71 .await?
72 else {
73 return Ok(None);
74 };
75
76 Ok(Some(code_system))
77}
78
79async fn get_concepts(
80 codesystem: CodeSystem,
81) -> Result<Vec<CodeSystemConcept>, OperationOutcomeError> {
82 match codesystem.content.as_ref() {
83 CodesystemContentMode::NotPresent(_) => Err(OperationOutcomeError::error(
84 IssueType::NotSupported(None),
85 "CodeSystem content is 'not-present'".to_string(),
86 )),
87 CodesystemContentMode::Fragment(_)
88 | CodesystemContentMode::Complete(_)
89 | CodesystemContentMode::Supplement(_) => {
90 Ok(codesystem.concept.clone().unwrap_or_default())
91 }
92 _ => Err(OperationOutcomeError::error(
93 IssueType::Invalid(None),
94 "CodeSystem content has invalid value".to_string(),
95 )),
96 }
97}
98
99fn code_system_concept_to_valueset_expansion(
100 url: Option<&str>,
101 version: Option<&str>,
102 codesystem_concept: Vec<CodeSystemConcept>,
103) -> Vec<ValueSetExpansionContains> {
104 codesystem_concept
105 .into_iter()
106 .map(|c| ValueSetExpansionContains {
107 system: url.map(|url| {
108 Box::new(FHIRUri {
109 value: Some(url.to_string()),
110 ..Default::default()
111 })
112 }),
113 version: version.map(|v| {
114 Box::new(FHIRString {
115 value: Some(v.to_string()),
116 ..Default::default()
117 })
118 }),
119 code: Some(c.code),
120 display: c.display,
121 designation: c.designation.map(|designations| {
122 designations
123 .into_iter()
124 .map(|d| ValueSetComposeIncludeConceptDesignation {
125 id: d.id,
126 extension: d.extension,
127 modifierExtension: d.modifierExtension,
128 language: d.language,
129 use_: d.use_,
130 value: d.value,
131 })
132 .collect::<Vec<_>>()
133 }),
134 contains: if let Some(concepts) = c.concept {
135 Some(code_system_concept_to_valueset_expansion(
136 url, version, concepts,
137 ))
138 } else {
139 None
140 },
141 ..Default::default()
142 })
143 .collect()
144}
145
146async fn get_valueset_expansion_contains<Resolver: CanonicalResolver + Send + Sync + 'static>(
147 canonical_resolution: Arc<Resolver>,
148 include: &ValueSetComposeInclude,
149) -> Result<Vec<ValueSetExpansionContains>, OperationOutcomeError> {
150 if are_codes_inline(include) {
151 Ok(codes_inline_to_expansion(include))
152 } else if let Some(valueset_uris) = include.valueSet.as_ref() {
153 let mut contains = vec![];
154 for valueset_uri in valueset_uris {
155 if let Some(valueset_uri) = valueset_uri.value.as_ref() {
156 let output = expand_valueset(
157 canonical_resolution.clone(),
158 ValueSetExpand::Input {
159 url: Some(FHIRUri {
160 value: Some(valueset_uri.to_string()),
161 ..Default::default()
162 }),
163 valueSet: None,
164 valueSetVersion: None,
165 context: None,
166 contextDirection: None,
167 filter: None,
168 date: None,
169 offset: None,
170 count: None,
171 includeDesignations: None,
172 designation: None,
173 includeDefinition: None,
174 activeOnly: None,
175 excludeNested: None,
176 excludeNotForUI: None,
177 excludePostCoordinated: None,
178 displayLanguage: None,
179 exclude_system: None,
180 system_version: None,
181 check_system_version: None,
182 force_system_version: None,
183 },
184 )
185 .await?;
186
187 contains.extend(
188 output
189 .return_
190 .expansion
191 .unwrap_or_default()
192 .contains
193 .unwrap_or_default(),
194 )
195 }
196 }
197 Ok(contains)
198 } else if let Some(system) = include.system.as_ref()
199 && let Some(uri) = system.value.as_ref()
200 && let Some(code_system) =
201 resolve_codesystem(canonical_resolution.clone(), uri.as_str()).await?
202 {
203 let url = code_system.url.clone();
204 let version = code_system.version.clone();
205
206 return Ok(code_system_concept_to_valueset_expansion(
207 url.and_then(|v| v.value).as_ref().map(|url| url.as_str()),
208 version.and_then(|v| v.value).as_ref().map(|v| v.as_str()),
209 get_concepts(code_system).await?,
210 ));
211 } else {
212 Ok(vec![])
213 }
214}
215
216async fn get_valueset_expansion<Resolver: CanonicalResolver + Sync + Send + 'static>(
217 canonical_resolution: Arc<Resolver>,
218 value_set: &ValueSet,
219) -> Result<Vec<ValueSetExpansionContains>, OperationOutcomeError> {
220 let mut result = Vec::new();
221 if let Some(compose) = value_set.compose.as_ref() {
222 for include in compose.include.iter() {
223 result.extend(
224 get_valueset_expansion_contains(canonical_resolution.clone(), include).await?,
225 );
226 }
227 }
228 Ok(result)
229}
230
231fn expand_valueset<Resolver: CanonicalResolver + Sync + Send + 'static>(
232 canonical_resolution: Arc<Resolver>,
233 input: ValueSetExpand::Input,
234) -> Pin<Box<dyn Future<Output = Result<ValueSetExpand::Output, OperationOutcomeError>> + Send>> {
235 Box::pin(async move {
237 let value_set = resolve_valueset(canonical_resolution.clone(), input).await?;
238
239 if let Some(mut value_set) = value_set {
240 let contains = get_valueset_expansion(canonical_resolution.clone(), &value_set).await?;
241 value_set.expansion = Some(ValueSetExpansion {
242 contains: Some(contains),
243 ..Default::default()
244 });
245
246 Ok(ValueSetExpand::Output { return_: value_set })
247 } else {
248 return Err(OperationOutcomeError::error(
249 IssueType::NotFound(None),
250 "ValueSet could not be resolved".to_string(),
251 ));
252 }
253 })
254}
255
256impl<Resolver: CanonicalResolver + Send + Sync + 'static> FHIRTerminology
257 for FHIRCanonicalTerminology<Resolver>
258{
259 async fn expand(
260 &self,
261 input: ValueSetExpand::Input,
262 ) -> Result<ValueSetExpand::Output, OperationOutcomeError> {
263 expand_valueset(self.resolver.clone(), input).await
264 }
265 async fn validate(
266 &self,
267 _input: ValueSetValidateCode::Input,
268 ) -> Result<ValueSetValidateCode::Output, OperationOutcomeError> {
269 unimplemented!()
271 }
272 async fn lookup(
273 &self,
274 _input: CodeSystemLookup::Input,
275 ) -> Result<CodeSystemLookup::Output, OperationOutcomeError> {
276 unimplemented!()
278 }
279}