Skip to main content

haste_server/fhir_client/
mod.rs

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            // Clinical resources.
338            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            // Artifact routes.
352            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            // Operation routes
374            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            // Authentication routes.
384            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            // Tenant auth routes.
397            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                    // Confirm in system project as above will only set to system if readonly.
410                    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}