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