1use crate::{
2 ServerEnvironmentVariables,
3 fhir_client::{
4 middleware::{
5 ServerMiddlewareContext, ServerMiddlewareNext, ServerMiddlewareOutput,
6 ServerMiddlewareState,
7 },
8 utilities::request_to_resource_type,
9 },
10};
11use haste_config::Config;
12use haste_fhir_client::{
13 FHIRClient,
14 middleware::{Middleware, MiddlewareChain},
15 request::{
16 FHIRBatchRequest, FHIRConditionalUpdateRequest, FHIRCreateRequest, FHIRReadRequest,
17 FHIRRequest, FHIRResponse, FHIRSearchTypeRequest, FHIRTransactionRequest,
18 FHIRUpdateInstanceRequest, SearchRequest, SearchResponse, UpdateRequest,
19 },
20 url::ParsedParameters,
21};
22use haste_fhir_model::r4::generated::resources::{
23 Bundle, CapabilityStatement, Parameters, Resource, ResourceType,
24};
25use haste_fhir_operation_error::{OperationOutcomeError, derive::OperationOutcomeError};
26use haste_fhir_search::SearchEngine;
27use haste_fhir_terminology::FHIRTerminology;
28use haste_jwt::{
29 AuthorId, AuthorKind, ProjectId, TenantId, UserRole,
30 scopes::{
31 SMARTResourceScope, Scope, Scopes, SmartResourceScopeLevel, SmartResourceScopePermission,
32 SmartResourceScopePermissions, SmartResourceScopeUser, SmartScope,
33 },
34};
35use haste_repository::{Repository, types::SupportedFHIRVersions};
36use std::sync::{Arc, LazyLock};
37
38mod batch_transaction_processing;
39mod middleware;
40mod utilities;
41
42#[derive(OperationOutcomeError, Debug)]
43pub enum StorageError {
44 #[error(
45 code = "not-supported",
46 diagnostic = "Storage not supported for fhir method."
47 )]
48 NotSupported,
49 #[error(
50 code = "exception",
51 diagnostic = "No response was returned from the request."
52 )]
53 NoResponse,
54 #[error(
55 code = "not-found",
56 diagnostic = "Resource '{arg0:?}' with id '{arg1}' not found."
57 )]
58 NotFound(ResourceType, String),
59 #[error(code = "invalid", diagnostic = "Invalid resource type.")]
60 InvalidType,
61}
62
63pub struct ServerCTX<
64 Repo: Repository + Send + Sync + 'static,
65 Search: SearchEngine + Send + Sync + 'static,
66 Terminology: FHIRTerminology + Send + Sync + 'static,
67> {
68 pub tenant: TenantId,
69 pub project: ProjectId,
70 pub fhir_version: SupportedFHIRVersions,
71 pub user: Arc<haste_jwt::claims::UserTokenClaims>,
72 pub client: Arc<FHIRServerClient<Repo, Search, Terminology>>,
73}
74
75impl<
76 Repo: Repository + Send + Sync + 'static,
77 Search: SearchEngine + Send + Sync + 'static,
78 Terminology: FHIRTerminology + Send + Sync + 'static,
79> ServerCTX<Repo, Search, Terminology>
80{
81 pub fn new(
82 tenant: TenantId,
83 project: ProjectId,
84 fhir_version: SupportedFHIRVersions,
85 user: Arc<haste_jwt::claims::UserTokenClaims>,
86 client: Arc<FHIRServerClient<Repo, Search, Terminology>>,
87 ) -> Self {
88 ServerCTX {
89 tenant,
90 project,
91 fhir_version,
92 user,
93 client,
94 }
95 }
96
97 pub fn system(
98 tenant: TenantId,
99 project: ProjectId,
100 client: Arc<FHIRServerClient<Repo, Search, Terminology>>,
101 ) -> Self {
102 ServerCTX {
103 tenant: tenant.clone(),
104 project: project.clone(),
105 fhir_version: SupportedFHIRVersions::R4,
106 user: Arc::new(haste_jwt::claims::UserTokenClaims {
107 sub: AuthorId::System,
108 exp: 0,
109 aud: AuthorKind::System.to_string(),
110 user_role: UserRole::Owner,
111 project: Some(project),
112 tenant,
113 scope: Scopes(vec![Scope::SMART(SmartScope::Resource(
114 SMARTResourceScope {
115 user: SmartResourceScopeUser::System,
116 level: SmartResourceScopeLevel::AllResources,
117 permissions: SmartResourceScopePermissions::new(vec![
118 SmartResourceScopePermission::Create,
119 SmartResourceScopePermission::Read,
120 SmartResourceScopePermission::Update,
121 SmartResourceScopePermission::Delete,
122 SmartResourceScopePermission::Search,
123 ]),
124 },
125 ))]),
126 user_id: AuthorId::System,
127 resource_type: AuthorKind::System,
128 access_policy_version_ids: vec![],
129 membership: None,
130 }),
131 client,
132 }
133 }
134}
135
136struct ClientState<
137 Repo: Repository + Send + Sync,
138 Search: SearchEngine + Send + Sync,
139 Terminology: FHIRTerminology + Send + Sync,
140> {
141 repo: Arc<Repo>,
142 search: Arc<Search>,
143 terminology: Arc<Terminology>,
144 config: Arc<dyn Config<ServerEnvironmentVariables>>,
145}
146
147pub struct Route<
148 Repo: Repository + Send + Sync + 'static,
149 Search: SearchEngine + Send + Sync + 'static,
150 Terminology: FHIRTerminology + Send + Sync + 'static,
151> {
152 filter: Box<dyn Fn(&FHIRRequest) -> bool + Send + Sync>,
153 middleware: Middleware<
154 Arc<ClientState<Repo, Search, Terminology>>,
155 Arc<ServerCTX<Repo, Search, Terminology>>,
156 FHIRRequest,
157 FHIRResponse,
158 OperationOutcomeError,
159 >,
160}
161
162pub struct FHIRServerClient<
163 Repo: Repository + Send + Sync + 'static,
164 Search: SearchEngine + Send + Sync + 'static,
165 Terminology: FHIRTerminology + Send + Sync + 'static,
166> {
167 state: Arc<ClientState<Repo, Search, Terminology>>,
168 middleware: Middleware<
169 Arc<ClientState<Repo, Search, Terminology>>,
170 Arc<ServerCTX<Repo, Search, Terminology>>,
171 FHIRRequest,
172 FHIRResponse,
173 OperationOutcomeError,
174 >,
175}
176
177pub struct RouterMiddleware<
178 Repo: Repository + Send + Sync + 'static,
179 Search: SearchEngine + Send + Sync + 'static,
180 Terminology: FHIRTerminology + Send + Sync + 'static,
181> {
182 routes: Arc<Vec<Route<Repo, Search, Terminology>>>,
183}
184
185impl<
186 Repo: Repository + Send + Sync + 'static,
187 Search: SearchEngine + Send + Sync + 'static,
188 Terminology: FHIRTerminology + Send + Sync + 'static,
189> RouterMiddleware<Repo, Search, Terminology>
190{
191 pub fn new(routes: Arc<Vec<Route<Repo, Search, Terminology>>>) -> Self {
192 RouterMiddleware { routes }
193 }
194}
195
196impl<
197 Repo: Repository + Send + Sync + 'static,
198 Search: SearchEngine + Send + Sync + 'static,
199 Terminology: FHIRTerminology + Send + Sync + 'static,
200>
201 MiddlewareChain<
202 ServerMiddlewareState<Repo, Search, Terminology>,
203 Arc<ServerCTX<Repo, Search, Terminology>>,
204 FHIRRequest,
205 FHIRResponse,
206 OperationOutcomeError,
207 > for RouterMiddleware<Repo, Search, Terminology>
208{
209 fn call(
210 &self,
211 state: ServerMiddlewareState<Repo, Search, Terminology>,
212 context: ServerMiddlewareContext<Repo, Search, Terminology>,
213 next: Option<Arc<ServerMiddlewareNext<Repo, Search, Terminology>>>,
214 ) -> ServerMiddlewareOutput<Repo, Search, Terminology> {
215 let routes = self.routes.clone();
216 Box::pin(async move {
217 let route = routes.iter().find(|r| (r.filter)(&context.request));
218 match route {
219 Some(route) => {
220 let context = route
221 .middleware
222 .call(state.clone(), context.ctx, context.request)
223 .await?;
224 if let Some(next) = next {
225 next(state, context).await
226 } else {
227 Ok(context)
228 }
229 }
230 None => {
231 if let Some(next) = next {
232 next(state, context).await
233 } else {
234 Ok(context)
235 }
236 }
237 }
238 })
239 }
240}
241
242static ARTIFACT_TYPES: &[ResourceType] = &[
243 ResourceType::ValueSet,
244 ResourceType::CodeSystem,
245 ResourceType::StructureDefinition,
246 ResourceType::SearchParameter,
247];
248
249static TENANT_AUTH_TYPES: &[ResourceType] = &[
250 ResourceType::User,
251 ResourceType::Project,
252 ResourceType::IdentityProvider,
253];
254static PROJECT_AUTH_TYPES: &[ResourceType] = &[ResourceType::Membership];
255
256static SPECIAL_TYPES: LazyLock<Vec<ResourceType>> = LazyLock::new(|| {
257 [
258 &TENANT_AUTH_TYPES[..],
259 &PROJECT_AUTH_TYPES[..],
260 &ARTIFACT_TYPES[..],
261 ]
262 .concat()
263});
264
265pub struct ServerClientConfig<
266 Repo: Repository + Send + Sync + 'static,
267 Search: SearchEngine + Send + Sync + 'static,
268 Terminology: FHIRTerminology + Send + Sync + 'static,
269> {
270 pub repo: Arc<Repo>,
271 pub search: Arc<Search>,
272 pub terminology: Arc<Terminology>,
273 pub mutate_artifacts: bool,
274 pub config: Arc<dyn Config<ServerEnvironmentVariables>>,
275}
276
277impl<
278 Repo: Repository + Send + Sync + 'static,
279 Search: SearchEngine + Send + Sync + 'static,
280 Terminology: FHIRTerminology + Send + Sync + 'static,
281> ServerClientConfig<Repo, Search, Terminology>
282{
283 pub fn new(
284 repo: Arc<Repo>,
285 search: Arc<Search>,
286 terminology: Arc<Terminology>,
287 config: Arc<dyn Config<ServerEnvironmentVariables>>,
288 ) -> Self {
289 ServerClientConfig {
290 repo,
291 search,
292 terminology,
293 mutate_artifacts: false,
294 config,
295 }
296 }
297
298 pub fn allow_mutate_artifacts(
299 repo: Arc<Repo>,
300 search: Arc<Search>,
301 terminology: Arc<Terminology>,
302 config: Arc<dyn Config<ServerEnvironmentVariables>>,
303 ) -> Self {
304 Self {
305 repo,
306 search,
307 terminology,
308 config,
309 mutate_artifacts: true,
310 }
311 }
312}
313
314impl<
315 Repo: Repository + Send + Sync + 'static,
316 Search: SearchEngine + Send + Sync + 'static,
317 Terminology: FHIRTerminology + Send + Sync + 'static,
318> FHIRServerClient<Repo, Search, Terminology>
319{
320 pub fn new(config: ServerClientConfig<Repo, Search, Terminology>) -> Self {
321 let clinical_resources_route = Route {
322 filter: Box::new(|req: &FHIRRequest| match req {
323 FHIRRequest::Invocation(_) | FHIRRequest::Capabilities => false,
324 _ => {
325 if let Some(resource_type) = request_to_resource_type(req) {
326 !SPECIAL_TYPES.contains(&resource_type)
327 } else {
328 true
329 }
330 }
331 }),
332 middleware: Middleware::new(vec![Box::new(middleware::storage::Middleware::new())]),
333 };
334
335 let operation_invocation_routes = Route {
336 filter: Box::new(|req: &FHIRRequest| match req {
337 FHIRRequest::Invocation(_) => true,
338 _ => false,
339 }),
340 middleware: Middleware::new(vec![Box::new(middleware::operations::Middleware::new())]),
341 };
342
343 let artifact_routes = Route {
344 filter: if config.mutate_artifacts {
345 Box::new(|req: &FHIRRequest| match req {
346 FHIRRequest::Update(_)
347 | FHIRRequest::Read(_)
348 | FHIRRequest::Search(SearchRequest::Type(_)) => {
349 if let Some(resource_type) = request_to_resource_type(req) {
350 ARTIFACT_TYPES.contains(&resource_type)
351 } else {
352 false
353 }
354 }
355 _ => false,
356 })
357 } else {
358 Box::new(|req: &FHIRRequest| match req {
359 FHIRRequest::Read(_) | FHIRRequest::Search(SearchRequest::Type(_)) => {
360 if let Some(resource_type) = request_to_resource_type(req) {
361 ARTIFACT_TYPES.contains(&resource_type)
362 } else {
363 false
364 }
365 }
366 _ => false,
367 })
368 },
369 middleware: Middleware::new(vec![
370 Box::new(middleware::set_artifact_tenant::Middleware::new()),
371 Box::new(middleware::storage::Middleware::new()),
372 ]),
373 };
374
375 let project_auth_routes = Route {
376 filter: Box::new(|req: &FHIRRequest| match req {
377 FHIRRequest::Invocation(_) => false,
378 _ => request_to_resource_type(req)
379 .map_or(false, |rt| PROJECT_AUTH_TYPES.contains(rt)),
380 }),
381 middleware: Middleware::new(vec![
382 Box::new(middleware::transaction::Middleware::new()),
383 Box::new(middleware::custom_models::membership::Middleware::new()),
384 Box::new(middleware::storage::Middleware::new()),
385 ]),
386 };
387
388 let tenant_auth_routes = Route {
389 filter: Box::new(|req: &FHIRRequest| match req {
390 FHIRRequest::Invocation(_) => false,
391 _ => {
392 request_to_resource_type(req).map_or(false, |rt| TENANT_AUTH_TYPES.contains(rt))
393 }
394 }),
395 middleware: Middleware::new(vec![
396 Box::new(
397 middleware::check_project::SetProjectReadOnlyMiddleware::new(ProjectId::System),
398 ),
399 Box::new(middleware::check_project::Middleware::new(
401 ProjectId::System,
402 )),
403 Box::new(middleware::transaction::Middleware::new()),
404 Box::new(middleware::custom_models::project::Middleware::new()),
405 Box::new(middleware::custom_models::user::Middleware::new()),
406 Box::new(middleware::storage::Middleware::new()),
407 ]),
408 };
409
410 let route_middleware = RouterMiddleware::new(Arc::new(vec![
411 clinical_resources_route,
412 artifact_routes,
413 operation_invocation_routes,
414 project_auth_routes,
416 tenant_auth_routes,
417 ]));
418
419 FHIRServerClient {
420 state: Arc::new(ClientState {
421 repo: config.repo,
422 search: config.search,
423 terminology: config.terminology,
424 config: config.config,
425 }),
426 middleware: Middleware::new(vec![
427 Box::new(middleware::auth_z::scope_check::SMARTScopeAccessMiddleware::new()),
428 Box::new(middleware::auth_z::access_control::AccessControlMiddleware::new()),
429 Box::new(route_middleware),
430 Box::new(middleware::capabilities::Middleware::new()),
431 ]),
432 }
433 }
434}
435
436impl<
437 Repo: Repository + Send + Sync + 'static,
438 Search: SearchEngine + Send + Sync + 'static,
439 Terminology: FHIRTerminology + Send + Sync + 'static,
440> FHIRClient<Arc<ServerCTX<Repo, Search, Terminology>>, OperationOutcomeError>
441 for FHIRServerClient<Repo, Search, Terminology>
442{
443 async fn request(
444 &self,
445 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
446 request: FHIRRequest,
447 ) -> Result<FHIRResponse, OperationOutcomeError> {
448 let response = self
449 .middleware
450 .call(self.state.clone(), _ctx, request)
451 .await?;
452
453 response
454 .response
455 .ok_or_else(|| StorageError::NoResponse.into())
456 }
457
458 async fn capabilities(
459 &self,
460 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
461 ) -> Result<CapabilityStatement, OperationOutcomeError> {
462 let res = self
463 .middleware
464 .call(self.state.clone(), _ctx, FHIRRequest::Capabilities)
465 .await?;
466
467 match res.response {
468 Some(FHIRResponse::Capabilities(capabilities_response)) => {
469 Ok(capabilities_response.capabilities)
470 }
471 _ => panic!("Unexpected response type"),
472 }
473 }
474
475 async fn search_system(
476 &self,
477 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
478 _parameters: ParsedParameters,
479 ) -> Result<Bundle, OperationOutcomeError> {
480 todo!()
481 }
482
483 async fn search_type(
484 &self,
485 ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
486 resource_type: ResourceType,
487 parameters: ParsedParameters,
488 ) -> Result<Bundle, OperationOutcomeError> {
489 let res = self
490 .middleware
491 .call(
492 self.state.clone(),
493 ctx,
494 FHIRRequest::Search(SearchRequest::Type(FHIRSearchTypeRequest {
495 resource_type,
496 parameters,
497 })),
498 )
499 .await?;
500
501 match res.response {
502 Some(FHIRResponse::Search(SearchResponse::Type(search_response))) => {
503 Ok(search_response.bundle)
504 }
505 _ => panic!("Unexpected response type"),
506 }
507 }
508
509 async fn create(
510 &self,
511 ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
512 resource_type: ResourceType,
513 resource: Resource,
514 ) -> Result<Resource, OperationOutcomeError> {
515 let res = self
516 .middleware
517 .call(
518 self.state.clone(),
519 ctx,
520 FHIRRequest::Create(FHIRCreateRequest {
521 resource_type,
522 resource,
523 }),
524 )
525 .await?;
526
527 match res.response {
528 Some(FHIRResponse::Create(create_response)) => Ok(create_response.resource),
529 _ => panic!("Unexpected response type"),
530 }
531 }
532
533 async fn update(
534 &self,
535 ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
536 resource_type: ResourceType,
537 id: String,
538 resource: Resource,
539 ) -> Result<Resource, OperationOutcomeError> {
540 let res = self
541 .middleware
542 .call(
543 self.state.clone(),
544 ctx,
545 FHIRRequest::Update(UpdateRequest::Instance(FHIRUpdateInstanceRequest {
546 resource_type,
547 id,
548 resource,
549 })),
550 )
551 .await?;
552
553 match res.response {
554 Some(FHIRResponse::Create(create_response)) => Ok(create_response.resource),
555 Some(FHIRResponse::Update(update_response)) => Ok(update_response.resource),
556 _ => panic!("Unexpected response type {:?}", res.response),
557 }
558 }
559
560 async fn conditional_update(
561 &self,
562 ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
563 resource_type: ResourceType,
564 parameters: ParsedParameters,
565 resource: Resource,
566 ) -> Result<Resource, OperationOutcomeError> {
567 let res = self
568 .middleware
569 .call(
570 self.state.clone(),
571 ctx,
572 FHIRRequest::Update(UpdateRequest::Conditional(FHIRConditionalUpdateRequest {
573 resource_type,
574 parameters,
575 resource,
576 })),
577 )
578 .await?;
579
580 match res.response {
581 Some(FHIRResponse::Create(create_response)) => Ok(create_response.resource),
582 Some(FHIRResponse::Update(update_response)) => Ok(update_response.resource),
583 _ => panic!("Unexpected response type {:?}", res.response),
584 }
585 }
586
587 async fn patch(
588 &self,
589 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
590 _resource_type: ResourceType,
591 _id: String,
592 _patches: json_patch::Patch,
593 ) -> Result<Resource, OperationOutcomeError> {
594 todo!()
595 }
596
597 async fn read(
598 &self,
599 ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
600 resource_type: ResourceType,
601 id: String,
602 ) -> Result<Option<Resource>, OperationOutcomeError> {
603 let res = self
604 .middleware
605 .call(
606 self.state.clone(),
607 ctx,
608 FHIRRequest::Read(FHIRReadRequest { resource_type, id }),
609 )
610 .await?;
611
612 match res.response {
613 Some(FHIRResponse::Read(read_response)) => Ok(read_response.resource),
614 _ => panic!("Unexpected response type"),
615 }
616 }
617
618 async fn vread(
619 &self,
620 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
621 _resource_type: ResourceType,
622 _id: String,
623 _version_id: String,
624 ) -> Result<Option<Resource>, OperationOutcomeError> {
625 todo!()
626 }
627
628 async fn delete_instance(
629 &self,
630 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
631 _resource_type: ResourceType,
632 _id: String,
633 ) -> Result<(), OperationOutcomeError> {
634 todo!()
635 }
636
637 async fn delete_type(
638 &self,
639 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
640 _resource_type: ResourceType,
641 _parameters: ParsedParameters,
642 ) -> Result<(), OperationOutcomeError> {
643 todo!()
644 }
645
646 async fn delete_system(
647 &self,
648 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
649 _parameters: ParsedParameters,
650 ) -> Result<(), OperationOutcomeError> {
651 todo!()
652 }
653
654 async fn history_system(
655 &self,
656 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
657 _parameters: ParsedParameters,
658 ) -> Result<Bundle, OperationOutcomeError> {
659 todo!()
660 }
661
662 async fn history_type(
663 &self,
664 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
665 _resource_type: ResourceType,
666 _parameters: ParsedParameters,
667 ) -> Result<Bundle, OperationOutcomeError> {
668 todo!()
669 }
670
671 async fn history_instance(
672 &self,
673 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
674 _resource_type: ResourceType,
675 _id: String,
676 _parameters: ParsedParameters,
677 ) -> Result<Bundle, OperationOutcomeError> {
678 todo!()
679 }
680
681 async fn invoke_instance(
682 &self,
683 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
684 _resource_type: ResourceType,
685 _id: String,
686 _operation: String,
687 _parameters: Parameters,
688 ) -> Result<Resource, OperationOutcomeError> {
689 todo!()
690 }
691
692 async fn invoke_type(
693 &self,
694 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
695 _resource_type: ResourceType,
696 _operation: String,
697 _parameters: Parameters,
698 ) -> Result<Resource, OperationOutcomeError> {
699 todo!()
700 }
701
702 async fn invoke_system(
703 &self,
704 _ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
705 _operation: String,
706 _parameters: Parameters,
707 ) -> Result<Resource, OperationOutcomeError> {
708 todo!()
709 }
710
711 async fn transaction(
712 &self,
713 ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
714 bundle: Bundle,
715 ) -> Result<Bundle, OperationOutcomeError> {
716 let res = self
717 .middleware
718 .call(
719 self.state.clone(),
720 ctx,
721 FHIRRequest::Transaction(FHIRTransactionRequest { resource: bundle }),
722 )
723 .await?;
724
725 match res.response {
726 Some(FHIRResponse::Transaction(transaction_response)) => {
727 Ok(transaction_response.resource)
728 }
729 _ => panic!("Unexpected response type"),
730 }
731 }
732
733 async fn batch(
734 &self,
735 ctx: Arc<ServerCTX<Repo, Search, Terminology>>,
736 bundle: Bundle,
737 ) -> Result<Bundle, OperationOutcomeError> {
738 let res = self
739 .middleware
740 .call(
741 self.state.clone(),
742 ctx,
743 FHIRRequest::Batch(FHIRBatchRequest { resource: bundle }),
744 )
745 .await?;
746
747 match res.response {
748 Some(FHIRResponse::Batch(batch_response)) => Ok(batch_response.resource),
749 _ => panic!("Unexpected response type"),
750 }
751 }
752}