1use base64::{Engine as _, engine::general_purpose};
2use futures::future::{BoxFuture, try_join, try_join_all};
3use haste_fhir_client::{FHIRClient, url::ParsedParameters};
4use haste_fhir_generated_ops::generated::ViewDefinitionRun;
5use haste_fhir_model::r4::{
6 datetime::Instant,
7 generated::{
8 resources::{
9 Binary, BundleEntry, Resource, ResourceType, ViewDefinition, ViewDefinitionSelect,
10 },
11 terminology::{BoundCode, HttpVerb, IssueType, OutputFormatCodes},
12 types::{FHIRBase64Binary, FHIRBoolean, FHIRCode, FHIRInstant, FHIRString, Reference},
13 },
14};
15use haste_fhir_operation_error::OperationOutcomeError;
16use haste_fhirpath::{Config, Context, FPEngine};
17use haste_reflect::MetaValue;
18use itertools::Itertools as _;
19use ordermap::OrderMap;
20use serde::{Deserialize, Serialize};
21use std::{
22 borrow::Cow,
23 collections::{HashMap, HashSet},
24 sync::Arc,
25};
26
27use crate::conversions::primitives::PrimitiveValue;
28
29mod compartment;
30mod conversions;
31mod output;
32mod union;
33
34fn fhirpath_issue_type(e: &haste_fhirpath::FHIRPathError) -> BoundCode<IssueType> {
37 match e {
38 haste_fhirpath::FHIRPathError::UndefinedConstant(_)
39 | haste_fhirpath::FHIRPathError::ParseError(_) => IssueType::invalid(),
40 _ => IssueType::exception(),
41 }
42}
43
44fn reference_value(reference: &Reference) -> Result<String, OperationOutcomeError> {
45 reference
46 .reference
47 .as_ref()
48 .and_then(|r| r.value.clone())
49 .ok_or_else(|| {
50 OperationOutcomeError::error(
51 IssueType::invalid(),
52 "Reference.reference is required".to_string(),
53 )
54 })
55}
56
57fn invalid(message: String) -> OperationOutcomeError {
58 OperationOutcomeError::error(IssueType::invalid(), message)
59}
60
61fn reference_id<'a>(reference: &'a str, resource_type: &str) -> Option<&'a str> {
63 reference
64 .strip_prefix(resource_type)
65 .and_then(|rest| rest.strip_prefix('/'))
66 .and_then(|rest| rest.split('/').next())
67 .filter(|id| !id.is_empty())
68}
69
70pub type ReadMany<CTX> = Arc<
74 dyn Fn(
75 CTX,
76 Vec<(ResourceType, String)>,
77 ) -> BoxFuture<'static, Result<Vec<Resource>, OperationOutcomeError>>
78 + Send
79 + Sync,
80>;
81
82const SCOPE_KEYS_PER_READ: usize = 1000;
84
85fn scope_id(
87 parameter: &str,
88 reference: &Reference,
89 resource_type: &ResourceType,
90) -> Result<String, OperationOutcomeError> {
91 let value = reference_value(reference)?;
92 reference_id(&value, resource_type.as_ref())
93 .map(str::to_string)
94 .ok_or_else(|| {
95 invalid(format!(
96 "'{parameter}' must reference a {}, not '{value}'.",
97 resource_type.as_ref()
98 ))
99 })
100}
101
102async fn patient_scope<CTX: Clone>(
110 context: CTX,
111 read_many: &ReadMany<CTX>,
112 input: &ViewDefinitionRun::Input,
113) -> Result<Option<Vec<String>>, OperationOutcomeError> {
114 if input.resource.is_some() {
115 return Ok(None);
116 }
117
118 let patient = input
119 .patient
120 .as_ref()
121 .map(|reference| scope_id("patient", reference, &ResourceType::Patient))
122 .transpose()?;
123 let groups = input
124 .group
125 .iter()
126 .flatten()
127 .map(|reference| scope_id("group", reference, &ResourceType::Group))
128 .collect::<Result<Vec<_>, _>>()?;
129 if patient.is_none() && groups.is_empty() {
130 return Ok(None);
131 }
132
133 let keys = patient
134 .iter()
135 .map(|id| (ResourceType::Patient, id.clone()))
136 .chain(groups.iter().map(|id| (ResourceType::Group, id.clone())))
137 .collect::<Vec<_>>();
138 let mut found = HashMap::new();
139 for chunk in keys.chunks(SCOPE_KEYS_PER_READ) {
140 for resource in read_many(context.clone(), chunk.to_vec()).await? {
141 if let Some(id) = resource.id().clone() {
142 found.insert((resource.resource_type(), id), resource);
143 }
144 }
145 }
146
147 if let Some(id) = &patient
148 && !found.contains_key(&(ResourceType::Patient, id.clone()))
149 {
150 return Err(invalid(format!("Patient with id '{id}' not found.")));
151 }
152
153 let mut members = Vec::new();
154 for id in &groups {
155 let Some(Resource::Group(group)) = found.get(&(ResourceType::Group, id.clone())) else {
156 return Err(invalid(format!("Group with id '{id}' not found.")));
157 };
158 members.extend(group.member.iter().flatten().filter_map(|member| {
159 let entity = member.entity.reference.as_ref()?.value.as_ref()?;
160 reference_id(entity, ResourceType::Patient.as_ref()).map(str::to_string)
161 }));
162 }
163 members.sort_unstable();
164 members.dedup();
165
166 Ok(Some(match patient {
167 Some(patient) if groups.is_empty() => vec![patient],
168 Some(patient) => members
169 .into_iter()
170 .filter(|member| *member == patient)
171 .collect(),
172 None => members,
173 }))
174}
175
176async fn resolve_view_definition<
177 'a,
178 CTX: Send + Sync + Clone + 'static,
179 Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
180>(
181 context: CTX,
182 client: &Client,
183 input: &'a ViewDefinitionRun::Input,
184) -> Result<Cow<'a, ViewDefinition>, OperationOutcomeError> {
185 if input.viewResource.is_some() && input.viewReference.is_some() {
186 return Err(invalid(
187 "Only one of viewResource and viewReference may be provided.".to_string(),
188 ));
189 }
190
191 if let Some(view_definition) = &input.viewResource {
192 Ok(Cow::Borrowed(view_definition))
193 } else if let Some(view_definition_reference) = input.viewReference.as_ref() {
194 let view_definition_reference = view_definition_reference
195 .reference
196 .as_ref()
197 .ok_or_else(|| {
198 OperationOutcomeError::error(
199 IssueType::invalid(),
200 "viewReference.reference is required".to_string(),
201 )
202 })?
203 .value
204 .as_ref()
205 .ok_or_else(|| {
206 OperationOutcomeError::error(
207 IssueType::invalid(),
208 "viewReference.reference.value is required".to_string(),
209 )
210 })?;
211
212 let reference_pieces = view_definition_reference.split('/').collect::<Vec<_>>();
213
214 let view_definition_id = reference_pieces
215 .last()
216 .ok_or_else(|| {
217 OperationOutcomeError::error(
218 IssueType::invalid(),
219 "Invalid viewReference.reference format".to_string(),
220 )
221 })?
222 .to_string();
223
224 let result = client
225 .read(
226 context,
227 ResourceType::ViewDefinition,
228 view_definition_id.clone(),
229 )
230 .await?
231 .ok_or_else(|| {
232 OperationOutcomeError::error(
233 IssueType::not_found(),
234 format!("ViewDefinition not found with id '{view_definition_id:?}'"),
235 )
236 })?;
237
238 if let Resource::ViewDefinition(view_definition) = result {
239 Ok(Cow::Owned(view_definition))
240 } else {
241 Err(OperationOutcomeError::error(
242 IssueType::invalid(),
243 "Referenced resource is not a ViewDefinition".to_string(),
244 ))
245 }
246 } else {
247 Err(OperationOutcomeError::error(
248 IssueType::invalid(),
249 "Either viewResource or viewReference must be provided".to_string(),
250 ))
251 }
252}
253
254#[derive(Debug, Clone, Copy)]
257pub struct RunLimits {
258 pub max_rows: usize,
260 pub max_resources: usize,
262}
263
264impl Default for RunLimits {
265 fn default() -> Self {
266 Self {
267 max_rows: 10_000,
268 max_resources: 50_000,
269 }
270 }
271}
272
273const PAGE_SIZE: usize = 1000;
275
276const PATIENTS_PER_SEARCH: usize = 100;
278
279const MAX_SEARCH_DEPTH: usize = 10_000;
282
283fn too_costly(what: &str) -> OperationOutcomeError {
286 let error = OperationOutcomeError::error(
287 IssueType::too_costly(),
288 format!("{what} Narrow the run with _limit, patient, group or _since."),
289 );
290 OperationOutcomeError::unprocessable(error.outcome().clone())
291}
292
293fn check_parameters(
295 input: &ViewDefinitionRun::Input,
296 limits: &RunLimits,
297) -> Result<(), OperationOutcomeError> {
298 if input.source.is_some() {
299 return Err(invalid(
300 "The server does not support the source parameter.".to_string(),
301 ));
302 }
303
304 match requested_limit(input) {
305 Some(limit) if limit > limits.max_rows => Err(invalid(format!(
306 "_limit {limit} is above {}, the most rows one run returns.",
307 limits.max_rows
308 ))),
309 _ => Ok(()),
310 }
311}
312
313struct Run<'v> {
315 fp_engine: &'v FPEngine,
316 variables: Arc<HashMap<String, &'v dyn MetaValue>>,
317 view_definition: &'v ViewDefinition,
318 where_clauses: Vec<&'v str>,
319 resource_type: ResourceType,
320 limit: Option<usize>,
322 limits: RunLimits,
323}
324
325async fn add_rows<'v>(
328 run: &Run<'v>,
329 resources: &[Resource],
330 rows: &mut Vec<Row<'v>>,
331) -> Result<bool, OperationOutcomeError> {
332 for resource in resources_of_type(resources, &run.resource_type) {
333 if !passes_where_clauses(
334 run.fp_engine,
335 run.variables.clone(),
336 &run.where_clauses,
337 resource,
338 )
339 .await?
340 {
341 continue;
342 }
343
344 rows.extend(
345 process_resource(
346 run.fp_engine,
347 run.variables.clone(),
348 run.view_definition,
349 resource,
350 )
351 .await?,
352 );
353
354 match run.limit {
355 Some(limit) if rows.len() >= limit => {
356 rows.truncate(limit);
357 return Ok(true);
358 }
359 None if rows.len() > run.limits.max_rows => {
360 return Err(too_costly(&format!(
361 "The view gives more than {} rows, the most one run returns.",
362 run.limits.max_rows
363 )));
364 }
365 _ => {}
366 }
367 }
368
369 Ok(false)
370}
371
372fn count_read(
374 read: &mut usize,
375 count: usize,
376 limits: &RunLimits,
377) -> Result<(), OperationOutcomeError> {
378 *read += count;
379 if *read > limits.max_resources {
380 return Err(too_costly(&format!(
381 "The run reads more than {} resources, the most one run reads.",
382 limits.max_resources
383 )));
384 }
385 Ok(())
386}
387
388async fn view_rows<
391 'v,
392 CTX: Send + Sync + Clone + 'static,
393 Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
394>(
395 context: CTX,
396 client: &Client,
397 run: &Run<'v>,
398 input: &ViewDefinitionRun::Input,
399 scope: Option<Vec<String>>,
400) -> Result<Vec<Row<'v>>, OperationOutcomeError> {
401 let mut rows = Vec::new();
402
403 if let Some(resources) = &input.resource {
404 add_rows(run, resources, &mut rows).await?;
405 return Ok(rows);
406 }
407
408 let since = input.since.as_ref().and_then(|since| since.value.as_ref());
409
410 match scope {
411 None => rows_from_history(context, client, run, since, &mut rows).await?,
412 Some(patients) => {
413 rows_from_compartments(context, client, run, &patients, since, &mut rows).await?;
414 }
415 }
416
417 Ok(rows)
418}
419
420async fn rows_from_history<
423 'v,
424 CTX: Send + Sync + Clone + 'static,
425 Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
426>(
427 context: CTX,
428 client: &Client,
429 run: &Run<'v>,
430 since: Option<&Instant>,
431 rows: &mut Vec<Row<'v>>,
432) -> Result<(), OperationOutcomeError> {
433 let mut seen = HashSet::new();
434 let mut read = 0;
435 let mut offset = 0;
436
437 loop {
438 let mut parameters = vec![
439 ("_count".to_string(), vec![PAGE_SIZE.to_string()]),
440 ("_offset".to_string(), vec![offset.to_string()]),
441 ];
442 if let Some(since) = since {
443 parameters.push(("_since".to_string(), vec![since.to_string()]));
444 }
445
446 let entries = client
447 .history_type(
448 context.clone(),
449 run.resource_type.clone(),
450 parameters.into(),
451 )
452 .await?
453 .entry
454 .unwrap_or_default();
455 let last_page = entries.len() < PAGE_SIZE;
456
457 count_read(&mut read, entries.len(), &run.limits)?;
458 let current = current_versions(entries, &mut seen, since);
459 if add_rows(run, ¤t, rows).await? || last_page {
460 return Ok(());
461 }
462
463 offset += PAGE_SIZE;
464 }
465}
466
467async fn rows_from_compartments<
470 'v,
471 CTX: Send + Sync + Clone + 'static,
472 Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
473>(
474 context: CTX,
475 client: &Client,
476 run: &Run<'v>,
477 patients: &[String],
478 since: Option<&Instant>,
479 rows: &mut Vec<Row<'v>>,
480) -> Result<(), OperationOutcomeError> {
481 let mut seen = HashSet::new();
482 let mut read = 0;
483
484 for parameter in compartment::patient_search_parameters(&run.resource_type)? {
485 for patients in patients.chunks(PATIENTS_PER_SEARCH) {
486 let mut offset = 0;
487
488 loop {
489 if offset >= MAX_SEARCH_DEPTH {
490 return Err(too_costly(&format!(
491 "More than {MAX_SEARCH_DEPTH} {} resources match one search of these \
492 patients.",
493 run.resource_type.as_ref()
494 )));
495 }
496
497 let resources: Vec<Resource> = client
498 .search_type(
499 context.clone(),
500 run.resource_type.clone(),
501 compartment_search(¶meter, patients, since, offset),
502 )
503 .await?
504 .entry
505 .into_iter()
506 .flatten()
507 .filter_map(|entry| entry.resource.map(|resource| *resource))
508 .collect();
509 let last_page = resources.len() < PAGE_SIZE;
510
511 count_read(&mut read, resources.len(), &run.limits)?;
512 let unseen: Vec<Resource> = resources
513 .into_iter()
514 .filter(|resource| seen.insert(resource.id().clone()))
515 .collect();
516 if add_rows(run, &unseen, rows).await? {
517 return Ok(());
518 }
519 if last_page {
520 break;
521 }
522
523 offset += PAGE_SIZE;
524 }
525 }
526 }
527
528 Ok(())
529}
530
531fn compartment_search(
534 parameter: &str,
535 patients: &[String],
536 since: Option<&Instant>,
537 offset: usize,
538) -> ParsedParameters {
539 let values = patients
540 .iter()
541 .map(|id| {
542 if parameter == "_id" {
543 id.clone()
544 } else {
545 format!("{}/{id}", ResourceType::Patient.as_ref())
546 }
547 })
548 .collect();
549
550 let mut parameters = vec![
551 (parameter.to_string(), values),
552 ("_sort".to_string(), vec!["_id".to_string()]),
553 ("_count".to_string(), vec![PAGE_SIZE.to_string()]),
554 ("_offset".to_string(), vec![offset.to_string()]),
555 ];
556 if let Some(since) = since {
557 parameters.push((
558 "_lastUpdated".to_string(),
559 vec![format!("gt{}", since.to_string())],
560 ));
561 }
562
563 parameters.into()
564}
565
566fn requested_limit(input: &ViewDefinitionRun::Input) -> Option<usize> {
568 input
569 .limit
570 .as_ref()
571 .and_then(|limit| limit.value)
572 .and_then(|limit| usize::try_from(limit).ok())
573}
574
575fn view_resource_type(
577 view_definition: &ViewDefinition,
578) -> Result<ResourceType, OperationOutcomeError> {
579 let Some(resource_type) = view_definition.resource.as_str() else {
580 return Err(OperationOutcomeError::error(
581 IssueType::invalid(),
582 "ViewDefinition.resource is required".to_string(),
583 ));
584 };
585
586 ResourceType::try_from(resource_type).map_err(|e| {
587 OperationOutcomeError::error(IssueType::invalid(), format!("Invalid resource type: {e}"))
588 })
589}
590
591fn current_versions(
596 entries: Vec<BundleEntry>,
597 seen: &mut HashSet<Option<String>>,
598 since: Option<&Instant>,
599) -> Vec<Resource> {
600 entries
601 .into_iter()
602 .filter_map(|entry| {
603 let resource = entry.resource?;
604 if !seen.insert(resource.id().clone()) {
605 return None;
606 }
607
608 let deleted = entry
609 .request
610 .is_some_and(|request| request.method == HttpVerb::delete());
611
612 (!deleted && changed_after(&resource, since)).then_some(*resource)
613 })
614 .collect()
615}
616
617fn changed_after(resource: &Resource, since: Option<&Instant>) -> bool {
620 let Some(Instant::Iso8601(since)) = since else {
621 return true;
622 };
623
624 resource
625 .get_field("meta")
626 .and_then(|meta| meta.get_field("lastUpdated"))
627 .and_then(|updated| updated.as_any().downcast_ref::<FHIRInstant>())
628 .and_then(|updated| updated.value.as_ref())
629 .is_none_or(|Instant::Iso8601(updated)| updated > since)
630}
631
632fn build_hashmap_fp_variables(viewdefinition: &ViewDefinition) -> HashMap<String, &dyn MetaValue> {
633 let mut hashmap = HashMap::new();
634
635 if let Some(constants) = &viewdefinition.constant {
636 for constant in constants {
637 if let (Some(name), Some(value)) = (
639 constant.name.value.as_ref(),
640 constant.value.flatten().first().copied(),
641 ) {
642 hashmap.insert(name.clone(), value);
643 }
644 }
645 }
646
647 hashmap
648}
649
650type Row<'a> = OrderMap<&'a str, OutputResults>;
652
653fn join<'a>(left: &[Row<'a>], right: Vec<Row<'a>>) -> Vec<Row<'a>> {
655 let copy = |(&name, value): (&&'a str, &OutputResults)| (name, value.clone());
656
657 match left {
658 [row] if row.is_empty() => right,
660 [row] => right
662 .into_iter()
663 .map(|r| row.iter().map(copy).chain(r).collect())
664 .collect(),
665 _ => left
666 .iter()
667 .cartesian_product(&right)
668 .map(|(l, r)| l.iter().chain(r).map(copy).collect())
669 .collect(),
670 }
671}
672
673#[derive(Debug, Clone, Deserialize, Serialize)]
675#[serde(untagged)]
676enum OutputResults {
677 Scalar(Option<PrimitiveValue>),
678 Collection(Vec<Option<PrimitiveValue>>),
679}
680
681async fn process_resource<'r, 'v: 'r>(
684 fp_engine: &'r FPEngine,
685 variables: Arc<HashMap<String, &'r dyn MetaValue>>,
686 view_definition: &'v ViewDefinition,
687 input: &'r Resource,
688) -> Result<Vec<Row<'v>>, OperationOutcomeError> {
689 let fp_config = Arc::new(
690 Config::builder()
691 .with_variable_resolver(haste_fhirpath::ExternalConstantResolver::Variable(
692 variables,
693 ))
694 .with_resource_id(input.id().clone().unwrap_or_default()),
695 );
696
697 let per_select = select_rows(fp_engine, &fp_config, &view_definition.select, input).await?;
698
699 Ok(per_select
700 .into_iter()
701 .fold(vec![Row::new()], |rows, more| join(&rows, more)))
702}
703
704async fn evaluate<'a>(
706 fp_engine: &FPEngine,
707 fp_config: &Arc<Config<'a>>,
708 path: &str,
709 node: &'a dyn MetaValue,
710 what: &str,
711) -> Result<Context<'a>, OperationOutcomeError> {
712 fp_engine
713 .evaluate_with_config(path, vec![node], fp_config.clone())
714 .await
715 .map_err(|e| {
716 OperationOutcomeError::error(
717 fhirpath_issue_type(&e),
718 format!("Error evaluating {what}: {e}"),
719 )
720 })
721}
722
723type Nodes<'a> = (Vec<&'a dyn MetaValue>, Vec<Context<'a>>);
726
727fn nodes_of(found: Context<'_>) -> Nodes<'_> {
728 (found.values().to_vec(), vec![found])
729}
730
731const REPEAT_ITEM_LIMIT: usize = 10_000;
733
734async fn walk_repeat<'a>(
737 fp_engine: &FPEngine,
738 fp_config: &Arc<Config<'a>>,
739 paths: &[FHIRString],
740 input: &'a dyn MetaValue,
741) -> Result<Nodes<'a>, OperationOutcomeError> {
742 let (mut nodes, mut arenas) = (Vec::new(), Vec::new());
743 let mut seen = HashSet::new();
744 let mut stack = vec![(input, false)];
746
747 while let Some((node, is_item)) = stack.pop() {
748 if is_item {
749 if !seen.insert((
751 std::ptr::from_ref(node).cast::<()>().addr(),
752 node.fhir_type(),
753 )) {
754 continue;
755 }
756 if nodes.len() >= REPEAT_ITEM_LIMIT {
757 return Err(OperationOutcomeError::error(
758 IssueType::exception(),
759 format!("repeat produced more than {REPEAT_ITEM_LIMIT} items"),
760 ));
761 }
762 nodes.push(node);
763 }
764
765 let start = stack.len();
767 for path in paths.iter().filter_map(|path| path.value.as_deref()) {
768 let found = evaluate(fp_engine, fp_config, path, node, "repeat expression").await?;
769 if !found.is_empty() {
770 stack.extend(found.values().iter().map(|&child| (child, true)));
771 arenas.push(found);
772 }
773 }
774 stack[start..].reverse();
775 }
776
777 Ok((nodes, arenas))
778}
779
780async fn select_rows<'r, 'v: 'r>(
782 fp_engine: &'r FPEngine,
783 fp_config: &Arc<Config<'r>>,
784 selects: &'v [ViewDefinitionSelect],
785 node: &'r dyn MetaValue,
786) -> Result<Vec<Vec<Row<'v>>>, OperationOutcomeError> {
787 try_join_all(
788 selects
789 .iter()
790 .map(|select| process_select(fp_engine, fp_config.clone(), select, node)),
791 )
792 .await
793}
794
795fn process_select<'r, 'v: 'r>(
798 fp_engine: &'r FPEngine,
799 fp_config: Arc<Config<'r>>,
800 select: &'v ViewDefinitionSelect,
801 input: &'r dyn MetaValue,
802) -> BoxFuture<'r, Result<Vec<Row<'v>>, OperationOutcomeError>> {
803 Box::pin(async move {
804 let for_each = select.forEach.as_ref().and_then(|p| p.value.as_deref());
805 let for_each_or_null = select
806 .forEachOrNull
807 .as_ref()
808 .and_then(|p| p.value.as_deref());
809
810 let (nodes, _arenas) = match (for_each, for_each_or_null, select.repeat.as_deref()) {
812 (Some(path), _, _) => {
813 nodes_of(evaluate(fp_engine, &fp_config, path, input, "forEach expression").await?)
814 }
815 (None, Some(path), _) => nodes_of(
816 evaluate(
817 fp_engine,
818 &fp_config,
819 path,
820 input,
821 "forEachOrNull expression",
822 )
823 .await?,
824 ),
825 (None, None, Some(paths)) => walk_repeat(fp_engine, &fp_config, paths, input).await?,
826 (None, None, None) => (vec![input], Vec::new()),
827 };
828
829 if nodes.is_empty() && for_each.is_none() && for_each_or_null.is_some() {
830 let mut null_row = Row::new();
831 collect_null_columns(select, &mut null_row)?;
832 return Ok(vec![null_row]);
833 }
834
835 let mut rows = Vec::with_capacity(nodes.len());
836 for node in nodes {
837 let columns = column_row(fp_engine, &fp_config, select, node).await?;
838 let nested = select_rows(
839 fp_engine,
840 &fp_config,
841 select.select.as_deref().unwrap_or_default(),
842 node,
843 )
844 .await?;
845 let union = match select.unionAll.as_deref() {
846 Some(branches) => Some(
847 select_rows(fp_engine, &fp_config, branches, node)
848 .await?
849 .concat(),
850 ),
851 None => None,
852 };
853
854 rows.extend(
855 nested
856 .into_iter()
857 .chain(union)
858 .fold(vec![columns], |rows, more| join(&rows, more)),
859 );
860 }
861
862 Ok(rows)
863 })
864}
865
866fn collect_null_columns<'a>(
868 select_statement: &'a ViewDefinitionSelect,
869 row: &mut Row<'a>,
870) -> Result<(), OperationOutcomeError> {
871 for column in select_statement.column.iter().flatten() {
872 let Some(name) = column.name.value.as_deref() else {
873 return Err(OperationOutcomeError::error(
874 IssueType::invalid(),
875 "Column name is required".to_string(),
876 ));
877 };
878
879 row.insert(name, OutputResults::Scalar(None));
880 }
881
882 for nested in select_statement
883 .select
884 .iter()
885 .flatten()
886 .chain(select_statement.unionAll.iter().flatten())
887 {
888 collect_null_columns(nested, row)?;
889 }
890
891 Ok(())
892}
893
894async fn column_row<'r, 'v>(
896 fp_engine: &FPEngine,
897 fp_config: &Arc<Config<'r>>,
898 select: &'v ViewDefinitionSelect,
899 node: &'r dyn MetaValue,
900) -> Result<Row<'v>, OperationOutcomeError> {
901 let mut row = Row::new();
902
903 for column in select.column.iter().flatten() {
904 let Some(path) = column.path.value.as_deref() else {
905 return Err(OperationOutcomeError::error(
906 IssueType::invalid(),
907 "Column path is required".to_string(),
908 ));
909 };
910
911 let Some(name) = column.name.value.as_deref() else {
912 return Err(OperationOutcomeError::error(
913 IssueType::invalid(),
914 "Column name is required".to_string(),
915 ));
916 };
917
918 let found = evaluate(fp_engine, fp_config, path, node, "expression").await?;
919 let values = found.values();
920
921 let column_type = column
922 .type_
923 .as_ref()
924 .and_then(|t| t.value.as_deref())
925 .unwrap_or_else(|| values.first().map_or("string", |value| value.fhir_type()));
926 let convert = |value: &&dyn MetaValue| {
927 conversions::primitives::convert_meta_value(column_type, *value)
928 };
929
930 let value = if column.collection.as_ref().and_then(|c| c.value) == Some(true) {
931 OutputResults::Collection(values.iter().map(convert).collect::<Result<_, _>>()?)
932 } else {
933 match values {
934 [] => OutputResults::Scalar(None),
935 [value] => OutputResults::Scalar(convert(value)?),
936 _ => {
937 return Err(OperationOutcomeError::error(
938 IssueType::invalid(),
939 "Column result is a collection but the column is not marked as a collection"
940 .to_string(),
941 ));
942 }
943 }
944 };
945
946 row.insert(name, value);
947 }
948
949 Ok(row)
950}
951
952fn resources_of_type<'r>(
956 resources: &'r [Resource],
957 resource_type: &ResourceType,
958) -> Vec<&'r Resource> {
959 let mut matching = Vec::new();
960 for resource in resources {
961 if &resource.resource_type() == resource_type {
962 matching.push(resource);
963 } else if let Resource::Bundle(bundle) = resource {
964 matching.extend(
965 bundle
966 .entry
967 .iter()
968 .flatten()
969 .filter_map(|entry| entry.resource.as_deref())
970 .filter(|resource| &resource.resource_type() == resource_type),
971 );
972 }
973 }
974
975 matching
976}
977
978async fn passes_where_clauses(
979 fp_engine: &FPEngine,
980 variables: Arc<HashMap<String, &dyn MetaValue>>,
981 where_clauses: &[&str],
982 resource: &Resource,
983) -> Result<bool, OperationOutcomeError> {
984 for where_clause in where_clauses {
985 let result = fp_engine
986 .evaluate_with_config(
987 where_clause,
988 vec![resource],
989 Arc::new(Config::builder().with_variable_resolver(
990 haste_fhirpath::ExternalConstantResolver::Variable(variables.clone()),
991 )),
992 )
993 .await
994 .map_err(|e| {
995 OperationOutcomeError::error(
996 fhirpath_issue_type(&e),
997 format!("Error evaluating where clause expression: {e}"),
998 )
999 })?;
1000
1001 if result.is_empty() {
1003 return Ok(false);
1004 }
1005
1006 let bool_results = result
1007 .iter()
1008 .map(|v| match v.fhir_type() {
1009 "boolean" => Ok(v
1010 .as_any()
1011 .downcast_ref::<FHIRBoolean>()
1012 .and_then(|b| b.value.as_ref())
1013 .unwrap_or(&false)),
1014 "http://hl7.org/fhirpath/System.Boolean" => {
1015 Ok(v.as_any().downcast_ref::<bool>().unwrap_or(&false))
1016 }
1017 _ => Err(OperationOutcomeError::error(
1018 IssueType::invalid(),
1019 format!(
1020 "Where clause expression must evaluate to a boolean, got: {}",
1021 v.fhir_type()
1022 ),
1023 )),
1024 })
1025 .collect::<Result<Vec<_>, _>>()?;
1026
1027 if bool_results.iter().any(|v| !**v) {
1028 return Ok(false);
1029 }
1030 }
1031
1032 Ok(true)
1033}
1034
1035async fn process_view_definition<
1036 CTX: Send + Sync + Clone + 'static,
1037 Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
1038>(
1039 context: CTX,
1040 output_format: &BoundCode<OutputFormatCodes>,
1041 client: Arc<Client>,
1042 view_definition: &ViewDefinition,
1043 input: &ViewDefinitionRun::Input,
1044 scope: Option<Vec<String>>,
1045 limits: RunLimits,
1046) -> Result<Binary, OperationOutcomeError> {
1047 union::check_unions(&view_definition.select)?;
1048
1049 let fp_engine = FPEngine::new();
1050 let run = Run {
1051 fp_engine: &fp_engine,
1052 variables: Arc::new(build_hashmap_fp_variables(view_definition)),
1053 view_definition,
1054 where_clauses: view_definition
1055 .where_
1056 .iter()
1057 .flatten()
1058 .filter_map(|w| w.path.value.as_deref())
1059 .collect(),
1060 resource_type: view_resource_type(view_definition)?,
1061 limit: requested_limit(input),
1062 limits,
1063 };
1064
1065 let results = view_rows(context, client.as_ref(), &run, input, scope).await?;
1066
1067 let include_header = input
1068 .header
1069 .as_ref()
1070 .and_then(|header| header.value)
1071 .unwrap_or(true);
1072
1073 let (content_type, data) = match output_format {
1076 binding if binding == &OutputFormatCodes::csv() => {
1077 ("text/csv", output::csv::csv(&results, include_header)?)
1078 }
1079 binding if binding == &OutputFormatCodes::json() => {
1080 ("application/json", output::json::json(&results)?)
1081 }
1082 binding if binding == &OutputFormatCodes::ndjson() => {
1083 ("application/x-ndjson", output::ndjson::ndjson(results)?)
1084 }
1085 _ => {
1086 return Err(OperationOutcomeError::error(
1087 IssueType::not_supported(),
1088 format!("Output format '{output_format:?}' is not supported"),
1089 ));
1090 }
1091 };
1092
1093 Ok(Binary {
1094 contentType: Box::new(FHIRCode {
1095 value: Some(content_type.to_string()),
1096 ..Default::default()
1097 }),
1098 data: Some(Box::new(FHIRBase64Binary {
1099 value: Some(general_purpose::STANDARD.encode(&data)),
1100 ..Default::default()
1101 })),
1102 ..Default::default()
1103 })
1104}
1105
1106pub async fn view_definition_run<
1123 CTX: Send + Sync + Clone + 'static,
1124 Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
1125>(
1126 context: CTX,
1127 client: Arc<Client>,
1128 read_many: &ReadMany<CTX>,
1129 input: &ViewDefinitionRun::Input,
1130 limits: RunLimits,
1131) -> Result<ViewDefinitionRun::Output, OperationOutcomeError> {
1132 check_parameters(input, &limits)?;
1133
1134 let output_format = input
1135 .format
1136 .as_ref()
1137 .and_then(|v| v.value.as_ref())
1138 .and_then(|s| BoundCode::<OutputFormatCodes>::new(s))
1139 .unwrap_or(OutputFormatCodes::ndjson());
1140
1141 let (view_definition, scope) = try_join(
1143 resolve_view_definition(context.clone(), client.as_ref(), input),
1144 patient_scope(context.clone(), read_many, input),
1145 )
1146 .await?;
1147
1148 let output = process_view_definition(
1149 context,
1150 &output_format,
1151 client,
1152 &view_definition,
1153 input,
1154 scope,
1155 limits,
1156 )
1157 .await?;
1158
1159 Ok(ViewDefinitionRun::Output { return_: output })
1160}