1use std::{pin::Pin, sync::Arc};
2
3use haste_fhir_client::request::InvocationRequest;
4use haste_fhir_model::r4::generated::{
5 resources::{OperationDefinition, Parameters, ParametersParameter, Resource},
6 terminology::{BoundCode, ResourceTypes},
7 types::FHIRBoolean,
8};
9use haste_fhir_operation_error::OperationOutcomeError;
10use haste_jwt::{ProjectId, TenantId};
11
12#[cfg(feature = "derive")]
13pub mod derive;
14
15pub enum Param<
16 T: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
17 + Into<Vec<ParametersParameter>>,
18> {
19 Value(T),
20 Parameters(Parameters),
21}
22
23impl<
24 T: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
25 + Into<Vec<ParametersParameter>>,
26> Param<T>
27{
28 pub fn as_parameters(self) -> Parameters {
29 match self {
30 Param::Value(v) => Parameters {
31 parameter: Some(v.into()),
32 ..Default::default()
33 },
34 Param::Parameters(p) => p,
35 }
36 }
37}
38
39fn allows(level: &FHIRBoolean) -> bool {
40 level.value.unwrap_or(false)
41}
42
43fn resource_types(definition: &OperationDefinition) -> Option<&[BoundCode<ResourceTypes>]> {
45 definition
46 .resource
47 .as_deref()
48 .filter(|types| !types.is_empty())
49}
50
51#[must_use]
54pub fn defines(definition: &OperationDefinition, request: &InvocationRequest) -> bool {
55 let (operation, level, resource_type) = match request {
56 InvocationRequest::System(request) => (&request.operation, &definition.system, None),
57 InvocationRequest::Type(request) => (
58 &request.operation,
59 &definition.type_,
60 Some(&request.resource_type),
61 ),
62 InvocationRequest::Instance(request) => (
63 &request.operation,
64 &definition.instance,
65 Some(&request.resource_type),
66 ),
67 };
68
69 definition.code.value.as_deref() == Some(operation.name())
70 && allows(level)
71 && resource_type.is_none_or(|resource_type| {
72 resource_types(definition).is_none_or(|types| {
73 types
74 .iter()
75 .any(|listed| listed.as_str() == Some(resource_type.as_ref()))
76 })
77 })
78}
79
80#[must_use]
83pub fn overlaps(a: &OperationDefinition, b: &OperationDefinition) -> bool {
84 let same_code = a.code.value.is_some() && a.code.value == b.code.value;
85 let shared_resource_type = match (resource_types(a), resource_types(b)) {
86 (Some(a), Some(b)) => a.iter().any(|resource_type| b.contains(resource_type)),
87 _ => true,
88 };
89
90 same_code
91 && ((allows(&a.system) && allows(&b.system))
92 || (shared_resource_type
93 && ((allows(&a.type_) && allows(&b.type_))
94 || (allows(&a.instance) && allows(&b.instance)))))
95}
96
97pub trait OperationInvocation<CTX: Send>: Send + Sync {
98 fn execute<'a>(
99 &self,
100 ctx: CTX,
101 tenant: TenantId,
102 project: ProjectId,
103 request: &'a InvocationRequest,
104 ) -> Pin<Box<dyn Future<Output = Result<Resource, OperationOutcomeError>> + Send + 'a>>;
105 fn operation_definition(&self) -> &'static OperationDefinition;
107}
108
109type Executor<CTX, I, O> = Box<
110 dyn Fn(
111 CTX,
112 TenantId,
113 ProjectId,
114 &InvocationRequest,
115 I,
116 ) -> Pin<Box<dyn Future<Output = Result<O, OperationOutcomeError>> + Send>>
117 + Send
118 + Sync,
119>;
120
121pub struct OperationExecutor<
122 CTX: Send,
123 I: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
124 + Into<Vec<ParametersParameter>>
125 + Send,
126 O: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError> + Into<Resource> + Send,
127> {
128 _ctx: std::marker::PhantomData<CTX>,
129 operation_definition: &'static OperationDefinition,
130 executor: Arc<Executor<CTX, I, O>>,
131}
132
133impl<
134 CTX: Send,
135 I: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
136 + Into<Vec<ParametersParameter>>
137 + Send,
138 O: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError> + Into<Resource> + Send,
139> OperationExecutor<CTX, I, O>
140{
141 #[must_use]
145 pub fn new(
146 operation_definition: &'static OperationDefinition,
147 executor: Executor<CTX, I, O>,
148 ) -> Self {
149 Self {
150 _ctx: std::marker::PhantomData,
151 executor: Arc::new(executor),
152 operation_definition,
153 }
154 }
155}
156
157impl<
158 CTX: Send + Sync + 'static,
159 I: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
160 + Into<Vec<ParametersParameter>>
161 + Send
162 + 'static,
163 O: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
164 + Into<Resource>
165 + Send
166 + 'static,
167> OperationInvocation<CTX> for OperationExecutor<CTX, I, O>
168{
169 fn execute<'a>(
170 &self,
171 ctx: CTX,
172 tenant: TenantId,
173 project: ProjectId,
174 request: &'a InvocationRequest,
175 ) -> Pin<Box<dyn Future<Output = Result<Resource, OperationOutcomeError>> + Send + 'a>> {
176 let executor = self.executor.clone();
177 Box::pin(async move {
178 let parameters = match request {
179 InvocationRequest::Instance(instance_request) => &instance_request.parameters,
180 InvocationRequest::Type(type_request) => &type_request.parameters,
181 InvocationRequest::System(system_request) => &system_request.parameters,
182 };
183
184 let input = I::try_from(parameters.parameter.clone().unwrap_or_default())?;
185
186 let output = (executor)(ctx, tenant, project, request, input).await?;
187
188 Ok(output.into())
189 })
190 }
191
192 fn operation_definition(&self) -> &'static OperationDefinition {
193 self.operation_definition
194 }
195}