haste_server/fhir_client/middleware/operations/
mod.rs1use crate::fhir_client::{
2 ClientState, ServerCTX,
3 middleware::{Backend, Handler, Output, operation_definitions},
4};
5use haste_fhir_client::request::{
6 FHIRInvokeInstanceResponse, FHIRInvokeSystemResponse, FHIRInvokeTypeResponse, FHIRRequest,
7 FHIRResponse, InvocationRequest, InvokeResponse,
8};
9use haste_fhir_model::r4::generated::{
10 resources::{OperationDefinition, Resource, ResourceType},
11 terminology::IssueType,
12};
13use haste_fhir_operation_error::OperationOutcomeError;
14use haste_fhir_ops::{OperationInvocation, defines};
15use haste_operation_executor::{
16 providers::deno_embedded::pool::DenoPool, traits::OperationExecutor,
17};
18use std::{borrow::Cow, sync::Arc};
19
20mod custom_operations;
21
22pub struct ServerOperationContext<B: Backend> {
25 pub ctx: Arc<ServerCTX<B>>,
26 pub state: ClientState<B>,
27}
28
29type BuiltinOperation<B> = Box<dyn OperationInvocation<ServerOperationContext<B>>>;
30
31pub struct Operations<B: Backend> {
34 builtins: Vec<BuiltinOperation<B>>,
35 deno_pool: Arc<DenoPool>,
36}
37
38impl<B: Backend> Operations<B> {
39 pub fn new(deno_pool: Arc<DenoPool>) -> Self {
43 let builtins: Vec<BuiltinOperation<B>> = vec![
44 Box::new(custom_operations::view_definition_run()),
45 Box::new(custom_operations::valueset_expand_op()),
46 Box::new(custom_operations::project_information_op()),
47 Box::new(custom_operations::active_refresh_tokens_op()),
48 Box::new(custom_operations::approved_scopes_op()),
49 Box::new(custom_operations::delete_approved_scope_op()),
50 Box::new(custom_operations::delete_refresh_token_op()),
51 Box::new(custom_operations::endpoint_metadata_op()),
52 Box::new(custom_operations::idp_registration_info_op()),
53 Box::new(custom_operations::evaluate_policy_op()),
54 Box::new(custom_operations::valueset_validate_code_op()),
55 Box::new(custom_operations::hl7v2_parse_op()),
56 Box::new(custom_operations::indexing_errors_op()),
57 Box::new(custom_operations::tenant_customization_op()),
58 Box::new(custom_operations::tenant_branding_op()),
59 Box::new(custom_operations::patient_everything()),
60 Box::new(custom_operations::smart_launch_op()),
61 Box::new(custom_operations::implementation_guide_install_op()),
62 Box::new(custom_operations::implementation_guide_uninstall_op()),
63 ];
64
65 Operations {
66 builtins,
67 deno_pool,
68 }
69 }
70
71 pub fn operation_definitions(&self) -> operation_definitions::Builtins {
74 self.builtins
75 .iter()
76 .map(|operation| operation.operation_definition())
77 .collect()
78 }
79
80 fn find_operation(
83 &self,
84 request: &InvocationRequest,
85 ) -> Option<&dyn OperationInvocation<ServerOperationContext<B>>> {
86 self.builtins
87 .iter()
88 .find(|operation| defines(operation.operation_definition(), request))
89 .map(|operation| operation.as_ref())
90 }
91}
92
93async fn find_custom_operation<B: Backend>(
97 ctx: &Arc<ServerCTX<B>>,
98 request: &InvocationRequest,
99) -> Result<Option<OperationDefinition>, OperationOutcomeError> {
100 let (code, level, _) = invocation(request);
101 let bundle = ctx
102 .client
103 .search_type(
104 ctx.clone(),
105 ResourceType::OperationDefinition,
106 vec![
107 ("code".to_string(), vec![code.to_string()]),
108 (level.to_string(), vec!["true".to_string()]),
109 ]
110 .into(),
111 )
112 .await?;
113
114 Ok(bundle
115 .entry
116 .as_deref()
117 .unwrap_or_default()
118 .iter()
119 .filter_map(|entry| match entry.resource.as_deref() {
120 Some(Resource::OperationDefinition(definition)) => Some(definition),
121 _ => None,
122 })
123 .find(|definition| defines(definition, request))
124 .cloned())
125}
126
127pub async fn operation_definition<B: Backend>(
130 ctx: &Arc<ServerCTX<B>>,
131 request: &InvocationRequest,
132) -> Result<Cow<'static, OperationDefinition>, OperationOutcomeError> {
133 let builtins = ctx.client.builtin_operations();
134 if let Some(builtin) = builtins.iter().find(|builtin| defines(builtin, request)) {
135 return Ok(Cow::Borrowed(*builtin));
136 }
137 find_custom_operation(ctx, request)
138 .await?
139 .map(Cow::Owned)
140 .ok_or_else(|| not_defined(request))
141}
142
143fn not_defined(request: &InvocationRequest) -> OperationOutcomeError {
145 let (code, _, invoked) = invocation(request);
146 OperationOutcomeError::error(
147 IssueType::not_found(),
148 format!("No operation '{code}' is defined {invoked}."),
149 )
150}
151
152fn invocation(request: &InvocationRequest) -> (&str, &'static str, String) {
155 match request {
156 InvocationRequest::Instance(request) => (
157 request.operation.name(),
158 "instance",
159 format!("on {} instances", request.resource_type.as_ref()),
160 ),
161 InvocationRequest::Type(request) => (
162 request.operation.name(),
163 "type",
164 format!("on the {} type", request.resource_type.as_ref()),
165 ),
166 InvocationRequest::System(request) => (
167 request.operation.name(),
168 "system",
169 "at the system level".to_string(),
170 ),
171 }
172}
173
174fn invoke_response(request: &InvocationRequest, resource: Resource) -> FHIRResponse {
177 FHIRResponse::Invoke(match request {
178 InvocationRequest::Instance(_) => {
179 InvokeResponse::Instance(FHIRInvokeInstanceResponse { resource })
180 }
181 InvocationRequest::Type(_) => InvokeResponse::Type(FHIRInvokeTypeResponse { resource }),
182 InvocationRequest::System(_) => {
183 InvokeResponse::System(FHIRInvokeSystemResponse { resource })
184 }
185 })
186}
187
188impl<B: Backend> Handler<B> for Operations<B> {
189 fn handle<'a>(
190 &'a self,
191 state: &'a ClientState<B>,
192 ctx: Arc<ServerCTX<B>>,
193 request: FHIRRequest,
194 ) -> Output<'a> {
195 Box::pin(async move {
196 let FHIRRequest::Invocation(invoke_request) = &request else {
197 return Err(OperationOutcomeError::fatal(
198 IssueType::exception(),
199 "Operation not supported".to_string(),
200 ));
201 };
202
203 let builtin = self.find_operation(invoke_request);
204 let custom = match builtin {
205 Some(_) => None,
206 None => Some(
207 find_custom_operation(&ctx, invoke_request)
208 .await?
209 .ok_or_else(|| not_defined(invoke_request))?,
210 ),
211 };
212
213 if ctx.prefer_async {
217 return crate::jobs::operation::submit(
218 state.repo.as_ref(),
219 state.config.as_ref(),
220 ctx.as_ref(),
221 invoke_request,
222 )
223 .await;
224 }
225
226 if let Some(operation) = builtin {
227 let output = operation
228 .execute(
229 ServerOperationContext {
230 state: state.clone(),
231 ctx: ctx.clone(),
232 },
233 ctx.tenant.clone(),
234 ctx.project.clone(),
235 invoke_request,
236 )
237 .await?;
238
239 return Ok(invoke_response(invoke_request, output));
240 }
241
242 let operation_definition = custom.ok_or_else(|| not_defined(invoke_request))?;
243 let output = self
244 .deno_pool
245 .execute_operation(
246 ctx.clone(),
247 ctx.client.clone(),
248 &operation_definition,
249 invoke_request,
250 )
251 .await?;
252
253 Ok(invoke_response(
254 invoke_request,
255 Resource::Parameters(output),
256 ))
257 })
258 }
259}