Skip to main content

haste_server/fhir_client/
mod.rs

1use crate::{
2    auth_n::middleware::jwt::User,
3    config::ServerConfig,
4    fhir_client::{
5        middleware::{Handler, Output, Pipeline},
6        utilities::request_to_resource_type,
7    },
8};
9use derivative::Derivative;
10use haste_fhir_client::{
11    FHIRClient,
12    request::{
13        DeleteRequest, DeleteResponse, FHIRBatchRequest, FHIRConditionalUpdateRequest,
14        FHIRCreateRequest, FHIRDeleteInstanceRequest, FHIRDeleteSystemRequest,
15        FHIRDeleteTypeRequest, FHIRHistoryInstanceRequest, FHIRHistorySystemRequest,
16        FHIRHistoryTypeRequest, FHIRInvokeInstanceRequest, FHIRInvokeSystemRequest,
17        FHIRInvokeTypeRequest, FHIRPatchRequest, FHIRReadRequest, FHIRRequest, FHIRResponse,
18        FHIRSearchSystemRequest, FHIRSearchTypeRequest, FHIRTransactionRequest,
19        FHIRUpdateInstanceRequest, FHIRVersionReadRequest, HistoryRequest, HistoryResponse,
20        InvocationRequest, InvokeResponse, Operation, SearchRequest, SearchResponse, UpdateRequest,
21    },
22    url::ParsedParameters,
23};
24use haste_fhir_model::r4::generated::resources::{
25    Bundle, CapabilityStatement, Parameters, Resource, ResourceType,
26};
27use haste_fhir_operation_error::{OperationOutcomeError, derive::OperationOutcomeError};
28use haste_fhir_search::SearchEngine;
29use haste_fhir_terminology::FHIRTerminology;
30use haste_jwt::{
31    AuthorId, AuthorKind, ProjectId, TenantId, UserRole, VersionId,
32    claims::SubscriptionTier,
33    scopes::{
34        SMARTResourceScope, Scope, Scopes, SmartResourceScopeLevel, SmartResourceScopePermission,
35        SmartResourceScopePermissions, SmartResourceScopeUser, SmartScope,
36    },
37};
38use haste_operation_executor::providers::deno_embedded::pool::DenoPool;
39use haste_repository::{Repository, types::SupportedFHIRVersions};
40use middleware::{
41    auditing::Auditing,
42    auth_z::{access_control::AccessControl, scope_check::SmartScopeAccess},
43    canonical_cache::CanonicalCacheInvalidation,
44    capabilities::Capabilities,
45    check_project::{CheckProject, SetProjectReadOnly},
46    custom_models::{
47        access_policy_v3::AccessPolicyV3Validation,
48        deidentification_policy::DeidentificationPolicyValidation, membership::MembershipSync,
49        project::ProjectSync, user::UserSync,
50    },
51    element_filtering::ElementFiltering,
52    operation_definitions::{Builtins, OperationDefinitions},
53    operations::Operations,
54    patch_as_update::PatchAsUpdate,
55    rate_limit::RateLimit,
56    set_artifact_tenant::SetArtifactTenant,
57    storage::Storage,
58    tenant_tier_limits::TenantTierLimits,
59    transaction::Transaction,
60    validation::Validation,
61};
62use std::{fmt::Debug, sync::Arc};
63
64mod batch_transaction_processing;
65mod compartment;
66mod middleware;
67mod resolver;
68mod subscription_limits;
69mod utilities;
70
71pub use middleware::{Backend, operations::operation_definition};
72
73#[derive(OperationOutcomeError, Debug)]
74pub enum StorageError {
75    #[error(
76        code = "not-supported",
77        diagnostic = "Storage not supported for fhir method."
78    )]
79    NotSupported,
80    #[error(
81        code = "exception",
82        diagnostic = "The request returned a response of another kind."
83    )]
84    UnexpectedResponse,
85    #[error(
86        code = "not-found",
87        diagnostic = "Resource '{arg0:?}' with id '{arg1}' not found."
88    )]
89    NotFound(ResourceType, String),
90    #[error(code = "invalid", diagnostic = "Invalid resource type.")]
91    InvalidType,
92    #[error(
93        code = "invalid",
94        diagnostic = "The resource's type '{arg0}' does not match the requested type '{arg1}'."
95    )]
96    TypeMismatch(String, String),
97}
98
99#[derive(Derivative)]
100#[derivative(Debug)]
101pub struct ServerCTX<Client: FHIRClient<Arc<Self>, OperationOutcomeError>> {
102    pub tenant: TenantId,
103    pub project: ProjectId,
104    pub fhir_version: SupportedFHIRVersions,
105    pub user: Arc<User>,
106    #[derivative(Debug = "ignore")]
107    pub client: Arc<Client>,
108    #[derivative(Debug = "ignore")]
109    pub rate_limit: Arc<dyn haste_rate_limit::RateLimit>,
110    pub tracing_id: Option<String>,
111    /// The Patient whose compartment the caller is confined to, set by the
112    /// scope middleware when a `patient/` scope grants the request. Storage
113    /// filters every search by it.
114    pub compartment: Option<String>,
115    /// The request asked for `Prefer: respond-async`: an operation it invokes
116    /// is queued as a job and answered with `202 Accepted` instead of run in
117    /// the request. Other interactions ignore it, as the pattern allows.
118    pub prefer_async: bool,
119}
120
121impl<Client: FHIRClient<Arc<Self>, OperationOutcomeError>> ServerCTX<Client> {
122    pub fn swap_client<NewClient: FHIRClient<Arc<ServerCTX<NewClient>>, OperationOutcomeError>>(
123        &self,
124        new_client: Arc<NewClient>,
125    ) -> ServerCTX<NewClient> {
126        ServerCTX {
127            tenant: self.tenant.clone(),
128            project: self.project.clone(),
129            fhir_version: self.fhir_version.clone(),
130            user: self.user.clone(),
131            client: new_client,
132            rate_limit: self.rate_limit.clone(),
133            tracing_id: self.tracing_id.clone(),
134            compartment: self.compartment.clone(),
135            prefer_async: self.prefer_async,
136        }
137    }
138
139    /// This context confined to `patient_id`'s compartment.
140    #[must_use]
141    pub fn with_compartment(&self, patient_id: String) -> Self {
142        ServerCTX {
143            tenant: self.tenant.clone(),
144            project: self.project.clone(),
145            fhir_version: self.fhir_version.clone(),
146            user: self.user.clone(),
147            client: self.client.clone(),
148            rate_limit: self.rate_limit.clone(),
149            tracing_id: self.tracing_id.clone(),
150            compartment: Some(patient_id),
151            prefer_async: self.prefer_async,
152        }
153    }
154
155    pub fn new(
156        tenant: TenantId,
157        project: ProjectId,
158        fhir_version: SupportedFHIRVersions,
159        user: Arc<User>,
160        client: Arc<Client>,
161        rate_limit: Arc<dyn haste_rate_limit::RateLimit>,
162    ) -> Self {
163        ServerCTX {
164            tenant,
165            project,
166            fhir_version,
167            user,
168            client,
169            rate_limit,
170            tracing_id: None,
171            compartment: None,
172            prefer_async: false,
173        }
174    }
175
176    pub fn system(
177        tenant: TenantId,
178        project: ProjectId,
179        client: Arc<Client>,
180        rate_limit: Arc<dyn haste_rate_limit::RateLimit>,
181    ) -> Self {
182        ServerCTX {
183            tenant: tenant.clone(),
184            project: project.clone(),
185            fhir_version: SupportedFHIRVersions::R4,
186            user: Arc::new(User {
187                token: None,
188                claims: haste_jwt::claims::UserTokenClaims {
189                    sub: AuthorId::System,
190                    exp: 0,
191                    aud: AuthorKind::System.to_string(),
192                    user_role: UserRole::Owner,
193                    project: Some(project),
194                    tenant,
195                    subscription_tier: SubscriptionTier::Unlimited,
196                    scope: Scopes(vec![Scope::SMART(SmartScope::Resource(
197                        SMARTResourceScope {
198                            user: SmartResourceScopeUser::System,
199                            level: SmartResourceScopeLevel::AllResources,
200                            permissions: SmartResourceScopePermissions::new(vec![
201                                SmartResourceScopePermission::Create,
202                                SmartResourceScopePermission::Read,
203                                SmartResourceScopePermission::Update,
204                                SmartResourceScopePermission::Delete,
205                                SmartResourceScopePermission::Search,
206                            ]),
207                        },
208                    ))]),
209                    fhir_user: None,
210                    patient: None,
211                    encounter: None,
212                    client_id: None,
213                    user_id: AuthorId::System,
214                    resource_type: AuthorKind::System,
215                    access_policy_version_ids: vec![],
216                    membership: None,
217                    fhir_version: SupportedFHIRVersions::R4,
218                },
219            }),
220            client,
221            rate_limit,
222            tracing_id: None,
223            compartment: None,
224            prefer_async: false,
225        }
226    }
227
228    pub fn with_tracing_id(mut self, tracing_id: Option<String>) -> Self {
229        self.tracing_id = tracing_id;
230        self
231    }
232
233    #[must_use]
234    pub fn with_prefer_async(mut self, prefer_async: bool) -> Self {
235        self.prefer_async = prefer_async;
236        self
237    }
238}
239
240/// What every layer runs on. The transaction middleware swaps the repository
241/// for a transaction's; the rest is fixed for the client's lifetime.
242pub struct ClientState<B: Backend> {
243    repo: Arc<B::Repo>,
244    search: Arc<B::Search>,
245    terminology: Arc<B::Terminology>,
246    audit_repo: Option<Arc<B::Repo>>,
247    config: Arc<ServerConfig>,
248    deno_pool: Arc<DenoPool>,
249}
250
251impl<B: Backend> ClientState<B> {
252    /// This state reading and writing through `repo`.
253    fn with_repo(&self, repo: Arc<B::Repo>) -> Self {
254        ClientState {
255            repo,
256            search: self.search.clone(),
257            terminology: self.terminology.clone(),
258            audit_repo: self.audit_repo.clone(),
259            config: self.config.clone(),
260            deno_pool: self.deno_pool.clone(),
261        }
262    }
263}
264
265impl<B: Backend> Clone for ClientState<B> {
266    fn clone(&self) -> Self {
267        self.with_repo(self.repo.clone())
268    }
269}
270
271/// The pipelines a request is dispatched to after the cross-cutting chain.
272/// Each ends in storage, the operation executor or the capability statement.
273#[derive(Clone, Copy, Debug, PartialEq, Eq)]
274enum Lane {
275    Clinical,
276    Artifact,
277    OperationDefinition,
278    ProjectAuth,
279    TenantAuth,
280    AccessPolicy,
281    DeidentificationPolicy,
282    Operation,
283    Capabilities,
284}
285
286impl Lane {
287    /// The lane that stores `resource_type`.
288    fn of(resource_type: &ResourceType) -> Lane {
289        match resource_type {
290            ResourceType::ValueSet
291            | ResourceType::CodeSystem
292            | ResourceType::StructureDefinition
293            | ResourceType::SearchParameter => Lane::Artifact,
294            ResourceType::OperationDefinition => Lane::OperationDefinition,
295            ResourceType::Membership => Lane::ProjectAuth,
296            ResourceType::AccessPolicyV3 => Lane::AccessPolicy,
297            ResourceType::DeidentificationPolicy => Lane::DeidentificationPolicy,
298            ResourceType::User | ResourceType::Project | ResourceType::IdentityProvider => {
299                Lane::TenantAuth
300            }
301            _ => Lane::Clinical,
302        }
303    }
304
305    /// Whether this lane serves `request`. Artifacts are read from the system
306    /// tenant beside the project's, which only these interactions do.
307    fn allows(self, request: &FHIRRequest) -> bool {
308        match self {
309            Lane::Artifact => matches!(
310                request,
311                FHIRRequest::Create(_)
312                    | FHIRRequest::Read(_)
313                    | FHIRRequest::Update(_)
314                    | FHIRRequest::Patch(_)
315                    | FHIRRequest::Delete(_)
316                    | FHIRRequest::Search(SearchRequest::Type(_))
317            ),
318            _ => true,
319        }
320    }
321}
322
323/// Rejects a create or update whose body is another type than the request
324/// names. Access control, the lane and storage all go by the requested type,
325/// so a body of another type would be checked as one type and stored as
326/// another.
327fn check_body_type(request: &FHIRRequest) -> Result<(), StorageError> {
328    let (requested, resource) = match request {
329        FHIRRequest::Create(create) => (&create.resource_type, &create.resource),
330        FHIRRequest::Update(UpdateRequest::Instance(update)) => {
331            (&update.resource_type, &update.resource)
332        }
333        FHIRRequest::Update(UpdateRequest::Conditional(update)) => {
334            (&update.resource_type, &update.resource)
335        }
336        _ => return Ok(()),
337    };
338
339    let actual = resource.resource_type();
340    if &actual == requested {
341        Ok(())
342    } else {
343        Err(StorageError::TypeMismatch(
344            actual.as_ref().to_string(),
345            requested.as_ref().to_string(),
346        ))
347    }
348}
349
350/// The lane for `request`, or `NotSupported` when its lane does not serve
351/// the interaction. Every variant is matched: a new one fails to compile here.
352fn route(request: &FHIRRequest) -> Result<Lane, StorageError> {
353    match request {
354        FHIRRequest::Capabilities => Ok(Lane::Capabilities),
355        FHIRRequest::Invocation(_) => Ok(Lane::Operation),
356        // Bundle entries and compartment searches are re-dispatched through
357        // `ctx.client`, so each is routed on its own. System-level requests
358        // have no type to route by.
359        FHIRRequest::Batch(_)
360        | FHIRRequest::Transaction(_)
361        | FHIRRequest::Compartment(_)
362        | FHIRRequest::Search(SearchRequest::System(_))
363        | FHIRRequest::History(HistoryRequest::System(_))
364        | FHIRRequest::Delete(DeleteRequest::System(_)) => Ok(Lane::Clinical),
365        FHIRRequest::Create(_)
366        | FHIRRequest::Read(_)
367        | FHIRRequest::VersionRead(_)
368        | FHIRRequest::Update(_)
369        | FHIRRequest::Patch(_)
370        | FHIRRequest::Delete(DeleteRequest::Instance(_) | DeleteRequest::Type(_))
371        | FHIRRequest::History(HistoryRequest::Instance(_) | HistoryRequest::Type(_))
372        | FHIRRequest::Search(SearchRequest::Type(_)) => {
373            let resource_type =
374                request_to_resource_type(request).ok_or(StorageError::InvalidType)?;
375            let lane = Lane::of(resource_type);
376            if lane.allows(request) {
377                Ok(lane)
378            } else {
379                Err(StorageError::NotSupported)
380            }
381        }
382    }
383}
384
385/// Ends the cross-cutting pipeline: runs the request's lane.
386struct Router<B: Backend> {
387    clinical: Pipeline<B>,
388    artifact: Pipeline<B>,
389    operation_definition: Pipeline<B>,
390    project_auth: Pipeline<B>,
391    tenant_auth: Pipeline<B>,
392    access_policy: Pipeline<B>,
393    deidentification_policy: Pipeline<B>,
394    operation: Pipeline<B>,
395    capabilities: Pipeline<B>,
396}
397
398impl<B: Backend> Handler<B> for Router<B> {
399    fn handle<'a>(
400        &'a self,
401        state: &'a ClientState<B>,
402        ctx: Arc<ServerCTX<B>>,
403        request: FHIRRequest,
404    ) -> Output<'a> {
405        match route(&request) {
406            Ok(Lane::Clinical) => &self.clinical,
407            Ok(Lane::Artifact) => &self.artifact,
408            Ok(Lane::OperationDefinition) => &self.operation_definition,
409            Ok(Lane::ProjectAuth) => &self.project_auth,
410            Ok(Lane::TenantAuth) => &self.tenant_auth,
411            Ok(Lane::AccessPolicy) => &self.access_policy,
412            Ok(Lane::DeidentificationPolicy) => &self.deidentification_policy,
413            Ok(Lane::Operation) => &self.operation,
414            Ok(Lane::Capabilities) => &self.capabilities,
415            Err(error) => return Box::pin(std::future::ready(Err(error.into()))),
416        }
417        .run(state, ctx, request)
418    }
419}
420
421pub struct FHIRServerClient<
422    Repo: Repository + Send + Sync + 'static,
423    Search: SearchEngine + Send + Sync + 'static,
424    Terminology: FHIRTerminology + Send + Sync + 'static,
425> {
426    state: ClientState<Self>,
427    pipeline: Pipeline<Self>,
428    builtin_operations: Builtins,
429}
430
431impl<
432    Repo: Repository + Send + Sync + 'static,
433    Search: SearchEngine + Send + Sync + 'static,
434    Terminology: FHIRTerminology + Send + Sync + 'static,
435> Backend for FHIRServerClient<Repo, Search, Terminology>
436{
437    type Repo = Repo;
438    type Search = Search;
439    type Terminology = Terminology;
440
441    fn builtin_operations(&self) -> &Builtins {
442        &self.builtin_operations
443    }
444}
445
446pub struct ServerClientConfig<
447    Repo: Repository + Send + Sync + 'static,
448    Search: SearchEngine + Send + Sync + 'static,
449    Terminology: FHIRTerminology + Send + Sync + 'static,
450> {
451    pub repo: Arc<Repo>,
452    pub audit_repo: Option<Arc<Repo>>,
453    pub search: Arc<Search>,
454    pub terminology: Arc<Terminology>,
455    pub mutate_artifacts: bool,
456    pub config: Arc<ServerConfig>,
457    pub deno_pool: Arc<DenoPool>,
458}
459
460impl<
461    Repo: Repository + Send + Sync + 'static,
462    Search: SearchEngine + Send + Sync + 'static,
463    Terminology: FHIRTerminology + Send + Sync + 'static,
464> ServerClientConfig<Repo, Search, Terminology>
465{
466    pub fn new(
467        repo: Arc<Repo>,
468        search: Arc<Search>,
469        terminology: Arc<Terminology>,
470        config: Arc<ServerConfig>,
471        deno_pool: Arc<DenoPool>,
472    ) -> Self {
473        ServerClientConfig {
474            repo,
475            audit_repo: None,
476            search,
477            terminology,
478            mutate_artifacts: false,
479            config,
480            deno_pool,
481        }
482    }
483
484    pub fn with_mutate_artifacts(mut self, mutate_artifacts: bool) -> Self {
485        self.mutate_artifacts = mutate_artifacts;
486        self
487    }
488
489    pub fn with_audit_repo(mut self, audit_repo: Option<Arc<Repo>>) -> Self {
490        self.audit_repo = audit_repo;
491        self
492    }
493}
494
495impl<
496    Repo: Repository + Send + Sync + 'static,
497    Search: SearchEngine + Send + Sync + 'static,
498    Terminology: FHIRTerminology + Send + Sync + 'static,
499> FHIRServerClient<Repo, Search, Terminology>
500{
501    pub fn new(config: ServerClientConfig<Repo, Search, Terminology>) -> Self {
502        let operations = Operations::new(config.deno_pool.clone());
503        let builtin_operations = operations.operation_definitions();
504
505        let router = Router {
506            clinical: Pipeline::new(vec![], Storage),
507            artifact: Pipeline::new(
508                vec![
509                    Box::new(CanonicalCacheInvalidation),
510                    Box::new(SetArtifactTenant),
511                ],
512                Storage,
513            ),
514            // OperationDefinitions, served beside the built-in operations' own.
515            operation_definition: Pipeline::new(
516                vec![
517                    // A PATCH runs as an update, so the patched resource gets
518                    // the built-in collision checks.
519                    Box::new(PatchAsUpdate),
520                    Box::new(OperationDefinitions::new(builtin_operations.clone())),
521                ],
522                Storage,
523            ),
524            project_auth: Pipeline::new(
525                vec![
526                    Box::new(Transaction),
527                    // A PATCH runs as an update, so the membership row stays in sync.
528                    Box::new(PatchAsUpdate),
529                    Box::new(MembershipSync),
530                ],
531                Storage,
532            ),
533            tenant_auth: Pipeline::new(
534                vec![
535                    Box::new(SetProjectReadOnly::new(ProjectId::System)),
536                    // Confirm in system project as above will only set to system if readonly.
537                    Box::new(CheckProject::new(ProjectId::System)),
538                    Box::new(Transaction),
539                    // A PATCH runs as an update, so it gets the Project update
540                    // checks and the users table stays in sync.
541                    Box::new(PatchAsUpdate),
542                    Box::new(ProjectSync),
543                    Box::new(UserSync),
544                ],
545                Storage,
546            ),
547            access_policy: Pipeline::new(
548                vec![
549                    // A PATCH runs as an update, so the patched policy is checked too.
550                    Box::new(PatchAsUpdate),
551                    Box::new(AccessPolicyV3Validation),
552                ],
553                Storage,
554            ),
555            deidentification_policy: Pipeline::new(
556                vec![
557                    Box::new(PatchAsUpdate),
558                    Box::new(DeidentificationPolicyValidation),
559                ],
560                Storage,
561            ),
562            operation: Pipeline::new(vec![], operations),
563            capabilities: Pipeline::new(vec![], Capabilities),
564        };
565
566        FHIRServerClient {
567            state: ClientState {
568                repo: config.repo,
569                audit_repo: config.audit_repo,
570                search: config.search,
571                terminology: config.terminology,
572                config: config.config,
573                deno_pool: config.deno_pool,
574            },
575            pipeline: Pipeline::new(
576                vec![
577                    Box::new(Auditing),
578                    Box::new(TenantTierLimits),
579                    Box::new(RateLimit),
580                    Box::new(ElementFiltering),
581                    Box::new(SmartScopeAccess),
582                    Box::new(AccessControl),
583                    Box::new(Validation),
584                ],
585                router,
586            ),
587            builtin_operations,
588        }
589    }
590
591    /// The `DenoPool` backing this client's custom-operations middleware.
592    ///
593    /// Used to hand the same pool to derived clients (transaction-scoped,
594    /// batch-scoped, etc.) instead of each one spinning up its own set of
595    /// worker threads.
596    pub fn deno_pool(&self) -> Arc<DenoPool> {
597        self.state.deno_pool.clone()
598    }
599
600    /// Runs `request` through the pipeline.
601    fn run(&self, ctx: Arc<ServerCTX<Self>>, request: FHIRRequest) -> Output<'_> {
602        if let Err(error) = check_body_type(&request) {
603            return Box::pin(async move { Err(error.into()) });
604        }
605        self.pipeline.run(&self.state, ctx, request)
606    }
607}
608
609impl<
610    Repo: Repository + Send + Sync + 'static,
611    Search: SearchEngine + Send + Sync + 'static,
612    Terminology: FHIRTerminology + Send + Sync + 'static,
613> FHIRClient<Arc<ServerCTX<Self>>, OperationOutcomeError>
614    for FHIRServerClient<Repo, Search, Terminology>
615{
616    #[tracing::instrument(name = "fhir_server_client", skip(self))]
617    async fn request(
618        &self,
619        _ctx: Arc<ServerCTX<Self>>,
620        request: FHIRRequest,
621    ) -> Result<FHIRResponse, OperationOutcomeError> {
622        tracing::info!("Processing request");
623        self.run(_ctx, request).await
624    }
625
626    async fn capabilities(
627        &self,
628        _ctx: Arc<ServerCTX<Self>>,
629    ) -> Result<CapabilityStatement, OperationOutcomeError> {
630        match self.run(_ctx, FHIRRequest::Capabilities).await? {
631            FHIRResponse::Capabilities(response) => Ok(response.capabilities),
632            _ => Err(StorageError::UnexpectedResponse.into()),
633        }
634    }
635
636    async fn search_system(
637        &self,
638        ctx: Arc<ServerCTX<Self>>,
639        parameters: ParsedParameters,
640    ) -> Result<Bundle, OperationOutcomeError> {
641        let request = FHIRRequest::Search(SearchRequest::System(FHIRSearchSystemRequest {
642            parameters,
643        }));
644        match self.run(ctx, request).await? {
645            FHIRResponse::Search(SearchResponse::System(response)) => Ok(response.bundle),
646            _ => Err(StorageError::UnexpectedResponse.into()),
647        }
648    }
649
650    async fn search_type(
651        &self,
652        ctx: Arc<ServerCTX<Self>>,
653        resource_type: ResourceType,
654        parameters: ParsedParameters,
655    ) -> Result<Bundle, OperationOutcomeError> {
656        let request = FHIRRequest::Search(SearchRequest::Type(FHIRSearchTypeRequest {
657            resource_type,
658            parameters,
659        }));
660        match self.run(ctx, request).await? {
661            FHIRResponse::Search(SearchResponse::Type(response)) => Ok(response.bundle),
662            _ => Err(StorageError::UnexpectedResponse.into()),
663        }
664    }
665
666    async fn create(
667        &self,
668        ctx: Arc<ServerCTX<Self>>,
669        resource_type: ResourceType,
670        resource: Resource,
671    ) -> Result<Resource, OperationOutcomeError> {
672        let request = FHIRRequest::Create(FHIRCreateRequest {
673            resource_type,
674            resource,
675        });
676        match self.run(ctx, request).await? {
677            FHIRResponse::Create(response) => Ok(response.resource),
678            _ => Err(StorageError::UnexpectedResponse.into()),
679        }
680    }
681
682    async fn update(
683        &self,
684        ctx: Arc<ServerCTX<Self>>,
685        resource_type: ResourceType,
686        id: String,
687        resource: Resource,
688    ) -> Result<Resource, OperationOutcomeError> {
689        let request = FHIRRequest::Update(UpdateRequest::Instance(FHIRUpdateInstanceRequest {
690            resource_type,
691            id,
692            resource,
693        }));
694        match self.run(ctx, request).await? {
695            FHIRResponse::Create(response) => Ok(response.resource),
696            FHIRResponse::Update(response) => Ok(response.resource),
697            _ => Err(StorageError::UnexpectedResponse.into()),
698        }
699    }
700
701    async fn conditional_update(
702        &self,
703        ctx: Arc<ServerCTX<Self>>,
704        resource_type: ResourceType,
705        parameters: ParsedParameters,
706        resource: Resource,
707    ) -> Result<Resource, OperationOutcomeError> {
708        let request =
709            FHIRRequest::Update(UpdateRequest::Conditional(FHIRConditionalUpdateRequest {
710                resource_type,
711                parameters,
712                resource,
713            }));
714        match self.run(ctx, request).await? {
715            FHIRResponse::Create(response) => Ok(response.resource),
716            FHIRResponse::Update(response) => Ok(response.resource),
717            _ => Err(StorageError::UnexpectedResponse.into()),
718        }
719    }
720
721    async fn patch(
722        &self,
723        ctx: Arc<ServerCTX<Self>>,
724        resource_type: ResourceType,
725        id: String,
726        patches: json_patch::Patch,
727    ) -> Result<Resource, OperationOutcomeError> {
728        let request = FHIRRequest::Patch(FHIRPatchRequest {
729            resource_type,
730            id,
731            patch: patches,
732        });
733        match self.run(ctx, request).await? {
734            FHIRResponse::Patch(response) => Ok(response.resource),
735            _ => Err(StorageError::UnexpectedResponse.into()),
736        }
737    }
738
739    async fn read(
740        &self,
741        ctx: Arc<ServerCTX<Self>>,
742        resource_type: ResourceType,
743        id: String,
744    ) -> Result<Option<Resource>, OperationOutcomeError> {
745        let request = FHIRRequest::Read(FHIRReadRequest { resource_type, id });
746        match self.run(ctx, request).await? {
747            FHIRResponse::Read(response) => Ok(response.resource),
748            _ => Err(StorageError::UnexpectedResponse.into()),
749        }
750    }
751
752    async fn vread(
753        &self,
754        ctx: Arc<ServerCTX<Self>>,
755        resource_type: ResourceType,
756        id: String,
757        version_id: String,
758    ) -> Result<Option<Resource>, OperationOutcomeError> {
759        let request = FHIRRequest::VersionRead(FHIRVersionReadRequest {
760            resource_type,
761            id,
762            version_id: VersionId::new(version_id),
763        });
764        match self.run(ctx, request).await? {
765            FHIRResponse::VersionRead(response) => Ok(Some(response.resource)),
766            _ => Err(StorageError::UnexpectedResponse.into()),
767        }
768    }
769
770    async fn delete_instance(
771        &self,
772        ctx: Arc<ServerCTX<Self>>,
773        resource_type: ResourceType,
774        id: String,
775    ) -> Result<(), OperationOutcomeError> {
776        let request = FHIRRequest::Delete(DeleteRequest::Instance(FHIRDeleteInstanceRequest {
777            resource_type,
778            id,
779        }));
780        match self.run(ctx, request).await? {
781            FHIRResponse::Delete(DeleteResponse::Instance(_)) => Ok(()),
782            _ => Err(StorageError::UnexpectedResponse.into()),
783        }
784    }
785
786    async fn delete_type(
787        &self,
788        ctx: Arc<ServerCTX<Self>>,
789        resource_type: ResourceType,
790        parameters: ParsedParameters,
791    ) -> Result<(), OperationOutcomeError> {
792        let request = FHIRRequest::Delete(DeleteRequest::Type(FHIRDeleteTypeRequest {
793            resource_type,
794            parameters,
795        }));
796        match self.run(ctx, request).await? {
797            FHIRResponse::Delete(DeleteResponse::Type(_)) => Ok(()),
798            _ => Err(StorageError::UnexpectedResponse.into()),
799        }
800    }
801
802    async fn delete_system(
803        &self,
804        ctx: Arc<ServerCTX<Self>>,
805        parameters: ParsedParameters,
806    ) -> Result<(), OperationOutcomeError> {
807        let request = FHIRRequest::Delete(DeleteRequest::System(FHIRDeleteSystemRequest {
808            parameters,
809        }));
810        match self.run(ctx, request).await? {
811            FHIRResponse::Delete(DeleteResponse::System(_)) => Ok(()),
812            _ => Err(StorageError::UnexpectedResponse.into()),
813        }
814    }
815
816    async fn history_system(
817        &self,
818        ctx: Arc<ServerCTX<Self>>,
819        parameters: ParsedParameters,
820    ) -> Result<Bundle, OperationOutcomeError> {
821        let request = FHIRRequest::History(HistoryRequest::System(FHIRHistorySystemRequest {
822            parameters,
823        }));
824        match self.run(ctx, request).await? {
825            FHIRResponse::History(HistoryResponse::System(response)) => Ok(response.bundle),
826            _ => Err(StorageError::UnexpectedResponse.into()),
827        }
828    }
829
830    async fn history_type(
831        &self,
832        ctx: Arc<ServerCTX<Self>>,
833        resource_type: ResourceType,
834        parameters: ParsedParameters,
835    ) -> Result<Bundle, OperationOutcomeError> {
836        let request = FHIRRequest::History(HistoryRequest::Type(FHIRHistoryTypeRequest {
837            resource_type,
838            parameters,
839        }));
840        match self.run(ctx, request).await? {
841            FHIRResponse::History(HistoryResponse::Type(response)) => Ok(response.bundle),
842            _ => Err(StorageError::UnexpectedResponse.into()),
843        }
844    }
845
846    async fn history_instance(
847        &self,
848        ctx: Arc<ServerCTX<Self>>,
849        resource_type: ResourceType,
850        id: String,
851        parameters: ParsedParameters,
852    ) -> Result<Bundle, OperationOutcomeError> {
853        let request = FHIRRequest::History(HistoryRequest::Instance(FHIRHistoryInstanceRequest {
854            resource_type,
855            id,
856            parameters,
857        }));
858        match self.run(ctx, request).await? {
859            FHIRResponse::History(HistoryResponse::Instance(response)) => Ok(response.bundle),
860            _ => Err(StorageError::UnexpectedResponse.into()),
861        }
862    }
863
864    async fn invoke_instance(
865        &self,
866        ctx: Arc<ServerCTX<Self>>,
867        resource_type: ResourceType,
868        id: String,
869        operation: String,
870        parameters: Parameters,
871    ) -> Result<Resource, OperationOutcomeError> {
872        let request =
873            FHIRRequest::Invocation(InvocationRequest::Instance(FHIRInvokeInstanceRequest {
874                operation: Operation::new(&operation),
875                resource_type,
876                id,
877                parameters,
878            }));
879        match self.run(ctx, request).await? {
880            FHIRResponse::Invoke(InvokeResponse::Instance(response)) => Ok(response.resource),
881            _ => Err(StorageError::UnexpectedResponse.into()),
882        }
883    }
884
885    async fn invoke_type(
886        &self,
887        ctx: Arc<ServerCTX<Self>>,
888        resource_type: ResourceType,
889        operation: String,
890        parameters: Parameters,
891    ) -> Result<Resource, OperationOutcomeError> {
892        let request = FHIRRequest::Invocation(InvocationRequest::Type(FHIRInvokeTypeRequest {
893            operation: Operation::new(&operation),
894            resource_type,
895            parameters,
896        }));
897        match self.run(ctx, request).await? {
898            FHIRResponse::Invoke(InvokeResponse::Type(response)) => Ok(response.resource),
899            _ => Err(StorageError::UnexpectedResponse.into()),
900        }
901    }
902
903    async fn invoke_system(
904        &self,
905        ctx: Arc<ServerCTX<Self>>,
906        operation: String,
907        parameters: Parameters,
908    ) -> Result<Resource, OperationOutcomeError> {
909        let request = FHIRRequest::Invocation(InvocationRequest::System(FHIRInvokeSystemRequest {
910            operation: Operation::new(&operation),
911            parameters,
912        }));
913        match self.run(ctx, request).await? {
914            FHIRResponse::Invoke(InvokeResponse::System(response)) => Ok(response.resource),
915            _ => Err(StorageError::UnexpectedResponse.into()),
916        }
917    }
918
919    async fn transaction(
920        &self,
921        ctx: Arc<ServerCTX<Self>>,
922        bundle: Bundle,
923    ) -> Result<Bundle, OperationOutcomeError> {
924        let request = FHIRRequest::Transaction(FHIRTransactionRequest { resource: bundle });
925        match self.run(ctx, request).await? {
926            FHIRResponse::Transaction(response) => Ok(response.resource),
927            _ => Err(StorageError::UnexpectedResponse.into()),
928        }
929    }
930
931    async fn batch(
932        &self,
933        ctx: Arc<ServerCTX<Self>>,
934        bundle: Bundle,
935    ) -> Result<Bundle, OperationOutcomeError> {
936        let request = FHIRRequest::Batch(FHIRBatchRequest { resource: bundle });
937        match self.run(ctx, request).await? {
938            FHIRResponse::Batch(response) => Ok(response.resource),
939            _ => Err(StorageError::UnexpectedResponse.into()),
940        }
941    }
942}