Skip to main content

haste_fhir_client/
http.rs

1use crate::{
2    FHIRClient,
3    middleware::{Context, Middleware, MiddlewareChain, Next},
4    request::{
5        self, CompartmentRequest, DeleteRequest, DeleteResponse, FHIRBatchRequest,
6        FHIRBatchResponse, FHIRConditionalUpdateRequest, FHIRCreateRequest, FHIRCreateResponse,
7        FHIRDeleteInstanceRequest, FHIRDeleteSystemRequest, FHIRDeleteTypeRequest,
8        FHIRHistoryInstanceRequest, FHIRHistorySystemRequest, FHIRHistoryTypeRequest,
9        FHIRInvokeInstanceRequest, FHIRInvokeSystemRequest, FHIRInvokeTypeRequest,
10        FHIRPatchRequest, FHIRPatchResponse, FHIRReadRequest, FHIRReadResponse, FHIRRequest,
11        FHIRResponse, FHIRSearchSystemRequest, FHIRSearchTypeRequest, FHIRTransactionRequest,
12        FHIRUpdateInstanceRequest, FHIRVersionReadRequest, HistoryRequest, HistoryResponse,
13        InvocationRequest, InvokeResponse, Operation, SearchRequest, SearchResponse, UpdateRequest,
14    },
15    url::{ParsedParameter, ParsedParameters},
16};
17use base64::{Engine as _, engine::general_purpose::STANDARD};
18use derivative::Derivative;
19use haste_fhir_model::r4::generated::{
20    resources::{
21        Bundle, CapabilityStatement, OperationOutcome, Parameters, Resource, ResourceType,
22    },
23    terminology::IssueType,
24};
25use haste_fhir_operation_error::{OperationOutcomeError, derive::OperationOutcomeError};
26use haste_jwt::VersionId;
27pub use http::{HeaderMap, HeaderName, HeaderValue};
28use reqwest::{Request, RequestBuilder, Url};
29use std::future::Future;
30use std::{fmt::Debug, pin::Pin, sync::Arc};
31
32type AccessToken = dyn Fn() -> Pin<Box<dyn Future<Output = Result<String, OperationOutcomeError>> + Send + Sync>>
33    + Sync
34    + Send;
35
36pub struct BasicCredentials {
37    pub username: String,
38    pub password: String,
39}
40
41type GetBasicCredentials = dyn Fn() -> Pin<
42        Box<dyn Future<Output = Result<BasicCredentials, OperationOutcomeError>> + Send + Sync>,
43    > + Sync
44    + Send;
45
46#[derive(Clone)]
47pub enum FHIRHttpAuthenticationMethod {
48    BearerToken(Arc<AccessToken>),
49    Basic(Arc<GetBasicCredentials>),
50}
51
52/// Supplies additional HTTP headers to apply to an outgoing request.
53///
54/// Implemented on the client's context type rather than on [`FHIRHttpState`] so
55/// that headers may vary per call instead of being fixed for the lifetime of the
56/// client.
57pub trait HttpRequestHeaders {
58    /// Headers to apply to the request, or `None` to send it unchanged.
59    fn request_headers(&self) -> Option<&HeaderMap>;
60}
61
62pub trait WithRequestHeaders: HttpRequestHeaders + Sized {
63    #[must_use]
64    fn with_request_headers(self, headers: HeaderMap) -> Self;
65}
66
67impl HttpRequestHeaders for () {
68    fn request_headers(&self) -> Option<&HeaderMap> {
69        None
70    }
71}
72
73#[derive(Derivative)]
74#[derivative(Debug)]
75pub struct FHIRHttpState {
76    #[derivative(Debug = "ignore")]
77    client: reqwest::Client,
78    api_url: Url,
79    #[derivative(Debug = "ignore")]
80    http_authentication_method: Option<FHIRHttpAuthenticationMethod>,
81}
82
83impl FHIRHttpState {
84    /// Creates a new HTTP client.
85    ///
86    /// # Errors
87    ///
88    /// Returns an [`OperationOutcomeError`] if the provided `api_url` is invalid
89    /// or if the client cannot be initialized.
90    pub fn new(
91        api_url: &str,
92        http_authentication_method: Option<FHIRHttpAuthenticationMethod>,
93    ) -> Result<Self, OperationOutcomeError> {
94        let mut url =
95            Url::parse(api_url).map_err(|_| FHIRHTTPError::UrlParseError(api_url.to_string()))?;
96
97        if !url.path().ends_with('/') {
98            url.set_path(&format!("{}/", url.path()));
99        }
100
101        Ok(FHIRHttpState {
102            client: reqwest::Client::new(),
103            api_url: url,
104            http_authentication_method,
105        })
106    }
107}
108
109pub struct FHIRHttpClient<CTX: Debug> {
110    state: Arc<FHIRHttpState>,
111    middleware:
112        Middleware<Arc<FHIRHttpState>, CTX, FHIRRequest, FHIRResponse, OperationOutcomeError>,
113}
114
115#[derive(Debug, OperationOutcomeError)]
116pub enum FHIRHTTPError {
117    #[error(code = "exception", diagnostic = "Reqwest failed.")]
118    ReqwestError(#[from] reqwest::Error),
119    #[error(code = "not-supported", diagnostic = "Operation not supported.")]
120    NotSupported,
121    #[fatal(code = "exception", diagnostic = "No response received.")]
122    NoResponse,
123    #[fatal(
124        code = "exception",
125        diagnostic = "Invalid url that could not be parsed {arg0}"
126    )]
127    UrlParseError(String),
128    #[error(code = "invalid", diagnostic = "FHIR Deserialization Error '{arg0}'.")]
129    DeserializeError(#[from] haste_fhir_serialization_json::errors::DeserializeError),
130    #[error(code = "invalid", diagnostic = "FHIR Serialization Error.")]
131    JSONSerializeError(#[from] serde_json::Error),
132}
133
134fn fhir_parameter_to_query_parameters(http_url: &mut reqwest::Url, parameters: &ParsedParameters) {
135    let mut query_parameters = http_url.query_pairs_mut();
136    for parameter in parameters.parameters() {
137        let parameter = match parameter {
138            ParsedParameter::Result(parameter) | ParsedParameter::Resource(parameter) => parameter,
139        };
140
141        let mut query_param_name = parameter.name.clone();
142
143        if let Some(chains) = parameter.chains.as_ref() {
144            query_param_name = format!("{query_param_name}.{}", chains.join("."));
145        }
146
147        if let Some(modifier) = parameter.modifier.as_ref() {
148            query_param_name = format!("{query_param_name}:{modifier}");
149        }
150
151        query_parameters.append_pair(&query_param_name, parameter.value.join(",").as_str());
152    }
153}
154
155fn build_request(builder: RequestBuilder) -> Result<Request, OperationOutcomeError> {
156    builder
157        .header("Accept", "application/fhir+json")
158        .header("Content-Type", "application/fhir+json, application/json")
159        .build()
160        .map_err(FHIRHTTPError::from)
161        .map_err(Into::into)
162}
163
164fn build_get(state: &FHIRHttpState, url: reqwest::Url) -> Result<Request, OperationOutcomeError> {
165    build_request(state.client.get(url))
166}
167
168fn build_post(
169    state: &FHIRHttpState,
170    url: reqwest::Url,
171    body: String,
172) -> Result<Request, OperationOutcomeError> {
173    build_request(state.client.post(url).body(body))
174}
175
176fn build_put(
177    state: &FHIRHttpState,
178    url: reqwest::Url,
179    body: String,
180) -> Result<Request, OperationOutcomeError> {
181    build_request(state.client.put(url).body(body))
182}
183
184fn build_patch(
185    state: &FHIRHttpState,
186    url: reqwest::Url,
187    body: String,
188) -> Result<Request, OperationOutcomeError> {
189    build_request(state.client.patch(url).body(body))
190}
191
192fn build_delete(
193    state: &FHIRHttpState,
194    url: reqwest::Url,
195) -> Result<Request, OperationOutcomeError> {
196    build_request(state.client.delete(url))
197}
198
199fn serialize_json<T: serde::Serialize>(value: &T) -> Result<String, OperationOutcomeError> {
200    serde_json::to_string(value)
201        .map_err(FHIRHTTPError::from)
202        .map_err(Into::into)
203}
204
205fn fhir_request_to_http_request<'a>(
206    state: &'a FHIRHttpState,
207    request: &'a FHIRRequest,
208) -> Pin<Box<dyn Future<Output = Result<Request, OperationOutcomeError>> + Send + 'a>> {
209    Box::pin(async move {
210        let request = match request {
211            FHIRRequest::Read(request) => request_from_read(state, request),
212            FHIRRequest::Compartment(request) => request_from_compartment(state, request).await,
213            FHIRRequest::Create(request) => request_from_create(state, request),
214            FHIRRequest::Patch(request) => request_from_patch(state, request),
215            FHIRRequest::Transaction(request) => request_from_transaction(state, request),
216            FHIRRequest::VersionRead(request) => request_from_version_read(state, request),
217            FHIRRequest::Update(request) => request_from_update(state, request),
218            FHIRRequest::Search(request) => request_from_search(state, request),
219            FHIRRequest::Delete(request) => request_from_delete(state, request),
220            FHIRRequest::Capabilities => request_from_capabilities(state),
221            FHIRRequest::History(request) => request_from_history(state, request),
222            FHIRRequest::Invocation(request) => request_from_invocation(state, request),
223            FHIRRequest::Batch(request) => request_from_batch(state, request),
224        };
225
226        let mut request = request?;
227
228        if let Some(http_authentication_method) = state.http_authentication_method.as_ref() {
229            match http_authentication_method {
230                FHIRHttpAuthenticationMethod::BearerToken(get_access_token) => {
231                    let token = get_access_token().await?;
232
233                    request.headers_mut().insert(
234                        "Authorization",
235                        HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| {
236                            OperationOutcomeError::error(
237                                IssueType::invalid(),
238                                "Failed to create Authorization header.".to_string(),
239                            )
240                        })?,
241                    );
242                }
243                FHIRHttpAuthenticationMethod::Basic(get_basic_credentials) => {
244                    let credentials = get_basic_credentials().await?;
245
246                    let basic_credentials = STANDARD
247                        .encode(format!("{}:{}", credentials.username, credentials.password));
248
249                    request.headers_mut().insert(
250                        "Authorization",
251                        HeaderValue::from_str(&format!("Basic {basic_credentials}")).map_err(
252                            |_| {
253                                OperationOutcomeError::error(
254                                    IssueType::invalid(),
255                                    "Failed to create Authorization header.".to_string(),
256                                )
257                            },
258                        )?,
259                    );
260                }
261            }
262        }
263
264        Ok(request)
265    })
266}
267
268fn request_from_read(
269    state: &FHIRHttpState,
270    read_request: &FHIRReadRequest,
271) -> Result<reqwest::Request, OperationOutcomeError> {
272    let read_request_url = state
273        .api_url
274        .join(&format!(
275            "{}/{}",
276            read_request.resource_type.as_ref(),
277            read_request.id
278        ))
279        .map_err(|_| FHIRHTTPError::UrlParseError("Read request".to_string()))?;
280
281    build_get(state, read_request_url)
282}
283
284fn request_from_compartment<'a>(
285    state: &'a FHIRHttpState,
286    compartment_request: &'a CompartmentRequest,
287) -> Pin<Box<dyn Future<Output = Result<reqwest::Request, OperationOutcomeError>> + Send + 'a>> {
288    Box::pin(async move {
289        let compartment_url = state
290            .api_url
291            .join(&format!(
292                "{}/{}",
293                compartment_request.resource_type.as_ref(),
294                compartment_request.id
295            ))
296            .map_err(|_| FHIRHTTPError::UrlParseError("Compartment request".to_string()))?;
297
298        let compartment_state = FHIRHttpState {
299            api_url: compartment_url,
300            client: state.client.clone(),
301            http_authentication_method: state.http_authentication_method.clone(),
302        };
303
304        fhir_request_to_http_request(&compartment_state, &compartment_request.request).await
305    })
306}
307
308fn request_from_create(
309    state: &FHIRHttpState,
310    create_request: &FHIRCreateRequest,
311) -> Result<reqwest::Request, OperationOutcomeError> {
312    let create_request_url = state
313        .api_url
314        .join(create_request.resource_type.as_ref())
315        .map_err(|_| FHIRHTTPError::UrlParseError("Create request".to_string()))?;
316
317    let body = serialize_json(&create_request.resource)?;
318
319    build_post(state, create_request_url, body)
320}
321
322fn request_from_patch(
323    state: &FHIRHttpState,
324    patch_request: &FHIRPatchRequest,
325) -> Result<reqwest::Request, OperationOutcomeError> {
326    let patch_request_url = state
327        .api_url
328        .join(&format!(
329            "{}/{}",
330            patch_request.resource_type.as_ref(),
331            patch_request.id
332        ))
333        .map_err(|_| FHIRHTTPError::UrlParseError("Patch request".to_string()))?;
334
335    let body = serialize_json(&patch_request.patch)?;
336
337    build_patch(state, patch_request_url, body)
338}
339
340fn request_from_transaction(
341    state: &FHIRHttpState,
342    transaction_request: &FHIRTransactionRequest,
343) -> Result<reqwest::Request, OperationOutcomeError> {
344    let body = serialize_json(&transaction_request.resource)?;
345
346    build_post(state, state.api_url.clone(), body)
347}
348
349fn request_from_version_read(
350    state: &FHIRHttpState,
351    version_request: &FHIRVersionReadRequest,
352) -> Result<reqwest::Request, OperationOutcomeError> {
353    let version_request_url = state
354        .api_url
355        .join(&format!(
356            "{}/{}/_history/{}",
357            version_request.resource_type.as_ref(),
358            version_request.id,
359            version_request.version_id.as_ref(),
360        ))
361        .map_err(|_| FHIRHTTPError::UrlParseError("Version read request".to_string()))?;
362
363    build_get(state, version_request_url)
364}
365
366fn request_from_capabilities(
367    state: &FHIRHttpState,
368) -> Result<reqwest::Request, OperationOutcomeError> {
369    let url = state
370        .api_url
371        .join("metadata")
372        .map_err(|_| FHIRHTTPError::UrlParseError("Capabilities request".to_string()))?;
373
374    build_get(state, url)
375}
376
377fn request_from_batch(
378    state: &FHIRHttpState,
379    batch_request: &FHIRBatchRequest,
380) -> Result<reqwest::Request, OperationOutcomeError> {
381    let body = serialize_json(&batch_request.resource)?;
382
383    build_post(state, state.api_url.clone(), body)
384}
385
386fn request_from_update(
387    state: &FHIRHttpState,
388    update_request: &UpdateRequest,
389) -> Result<reqwest::Request, OperationOutcomeError> {
390    match update_request {
391        UpdateRequest::Instance(request) => request_from_update_instance(state, request),
392        UpdateRequest::Conditional(request) => request_from_update_conditional(state, request),
393    }
394}
395
396fn request_from_update_instance(
397    state: &FHIRHttpState,
398    update_request: &FHIRUpdateInstanceRequest,
399) -> Result<reqwest::Request, OperationOutcomeError> {
400    let update_request_url = state
401        .api_url
402        .join(&format!(
403            "{}/{}",
404            update_request.resource_type.as_ref(),
405            update_request.id
406        ))
407        .map_err(|_| FHIRHTTPError::UrlParseError("Update request".to_string()))?;
408
409    let body = serialize_json(&update_request.resource)?;
410
411    build_put(state, update_request_url, body)
412}
413
414fn request_from_update_conditional(
415    state: &FHIRHttpState,
416    update_request: &FHIRConditionalUpdateRequest,
417) -> Result<reqwest::Request, OperationOutcomeError> {
418    let mut request_url = state
419        .api_url
420        .join(update_request.resource_type.as_ref())
421        .map_err(|_| FHIRHTTPError::UrlParseError("ConditionalUpdate request".to_string()))?;
422
423    fhir_parameter_to_query_parameters(&mut request_url, &update_request.parameters);
424
425    let body = serialize_json(&update_request.resource)?;
426
427    build_put(state, request_url, body)
428}
429
430fn request_from_search(
431    state: &FHIRHttpState,
432    search_request: &SearchRequest,
433) -> Result<reqwest::Request, OperationOutcomeError> {
434    match search_request {
435        SearchRequest::Type(request) => request_from_search_type(state, request),
436        SearchRequest::System(request) => request_from_search_system(state, request),
437    }
438}
439
440fn request_from_search_type(
441    state: &FHIRHttpState,
442    search_request: &FHIRSearchTypeRequest,
443) -> Result<reqwest::Request, OperationOutcomeError> {
444    let mut request_url = state
445        .api_url
446        .join(search_request.resource_type.as_ref())
447        .map_err(|_| FHIRHTTPError::UrlParseError("SearchType request".to_string()))?;
448
449    fhir_parameter_to_query_parameters(&mut request_url, &search_request.parameters);
450
451    build_get(state, request_url)
452}
453
454fn request_from_search_system(
455    state: &FHIRHttpState,
456    search_request: &FHIRSearchSystemRequest,
457) -> Result<reqwest::Request, OperationOutcomeError> {
458    let mut request_url = state.api_url.clone();
459
460    fhir_parameter_to_query_parameters(&mut request_url, &search_request.parameters);
461
462    build_get(state, request_url)
463}
464
465fn request_from_delete(
466    state: &FHIRHttpState,
467    delete_request: &DeleteRequest,
468) -> Result<reqwest::Request, OperationOutcomeError> {
469    match delete_request {
470        DeleteRequest::Instance(request) => request_from_delete_instance(state, request),
471        DeleteRequest::Type(request) => request_from_delete_type(state, request),
472        DeleteRequest::System(request) => request_from_delete_system(state, request),
473    }
474}
475
476fn request_from_delete_instance(
477    state: &FHIRHttpState,
478    delete_request: &FHIRDeleteInstanceRequest,
479) -> Result<reqwest::Request, OperationOutcomeError> {
480    let delete_request_url = state
481        .api_url
482        .join(&format!(
483            "{}/{}",
484            delete_request.resource_type.as_ref(),
485            delete_request.id
486        ))
487        .map_err(|_| FHIRHTTPError::UrlParseError("DeleteInstance request".to_string()))?;
488
489    build_delete(state, delete_request_url)
490}
491
492fn request_from_delete_type(
493    state: &FHIRHttpState,
494    delete_request: &FHIRDeleteTypeRequest,
495) -> Result<reqwest::Request, OperationOutcomeError> {
496    let mut request_url = state
497        .api_url
498        .join(delete_request.resource_type.as_ref())
499        .map_err(|_| FHIRHTTPError::UrlParseError("DeleteType request".to_string()))?;
500
501    fhir_parameter_to_query_parameters(&mut request_url, &delete_request.parameters);
502
503    build_delete(state, request_url)
504}
505
506fn request_from_delete_system(
507    state: &FHIRHttpState,
508    delete_request: &FHIRDeleteSystemRequest,
509) -> Result<reqwest::Request, OperationOutcomeError> {
510    let mut request_url = state.api_url.clone();
511
512    fhir_parameter_to_query_parameters(&mut request_url, &delete_request.parameters);
513
514    build_delete(state, request_url)
515}
516
517fn request_from_history(
518    state: &FHIRHttpState,
519    history_request: &HistoryRequest,
520) -> Result<reqwest::Request, OperationOutcomeError> {
521    match history_request {
522        HistoryRequest::Instance(request) => request_from_history_instance(state, request),
523        HistoryRequest::Type(request) => request_from_history_type(state, request),
524        HistoryRequest::System(request) => request_from_history_system(state, request),
525    }
526}
527
528fn request_from_history_instance(
529    state: &FHIRHttpState,
530    history_request: &FHIRHistoryInstanceRequest,
531) -> Result<reqwest::Request, OperationOutcomeError> {
532    let mut request_url = state
533        .api_url
534        .join(&format!(
535            "{}/{}/_history",
536            history_request.resource_type.as_ref(),
537            history_request.id
538        ))
539        .map_err(|_| FHIRHTTPError::UrlParseError("HistoryInstance request".to_string()))?;
540
541    fhir_parameter_to_query_parameters(&mut request_url, &history_request.parameters);
542
543    build_get(state, request_url)
544}
545
546fn request_from_history_type(
547    state: &FHIRHttpState,
548    history_request: &FHIRHistoryTypeRequest,
549) -> Result<reqwest::Request, OperationOutcomeError> {
550    let mut request_url = state
551        .api_url
552        .join(&format!(
553            "{}/_history",
554            history_request.resource_type.as_ref(),
555        ))
556        .map_err(|_| FHIRHTTPError::UrlParseError("HistoryType request".to_string()))?;
557
558    fhir_parameter_to_query_parameters(&mut request_url, &history_request.parameters);
559
560    build_get(state, request_url)
561}
562
563fn request_from_history_system(
564    state: &FHIRHttpState,
565    history_request: &FHIRHistorySystemRequest,
566) -> Result<reqwest::Request, OperationOutcomeError> {
567    let mut request_url = state
568        .api_url
569        .join("_history")
570        .map_err(|_| FHIRHTTPError::UrlParseError("HistorySystem request".to_string()))?;
571
572    fhir_parameter_to_query_parameters(&mut request_url, &history_request.parameters);
573
574    build_get(state, request_url)
575}
576
577fn request_from_invocation(
578    state: &FHIRHttpState,
579    invocation_request: &InvocationRequest,
580) -> Result<reqwest::Request, OperationOutcomeError> {
581    match invocation_request {
582        InvocationRequest::Instance(request) => request_from_invocation_instance(state, request),
583        InvocationRequest::Type(request) => request_from_invocation_type(state, request),
584        InvocationRequest::System(request) => request_from_invocation_system(state, request),
585    }
586}
587
588fn request_from_invocation_instance(
589    state: &FHIRHttpState,
590    invocation_request: &FHIRInvokeInstanceRequest,
591) -> Result<reqwest::Request, OperationOutcomeError> {
592    let request_url = state
593        .api_url
594        .join(&format!(
595            "{}/{}/${}",
596            invocation_request.resource_type.as_ref(),
597            invocation_request.id,
598            invocation_request.operation.name(),
599        ))
600        .map_err(|_| FHIRHTTPError::UrlParseError("InvokeInstance request".to_string()))?;
601
602    let body = serialize_json(&invocation_request.parameters)?;
603
604    build_post(state, request_url, body)
605}
606
607fn request_from_invocation_type(
608    state: &FHIRHttpState,
609    invocation_request: &FHIRInvokeTypeRequest,
610) -> Result<reqwest::Request, OperationOutcomeError> {
611    let request_url = state
612        .api_url
613        .join(&format!(
614            "{}/${}",
615            invocation_request.resource_type.as_ref(),
616            invocation_request.operation.name(),
617        ))
618        .map_err(|_| FHIRHTTPError::UrlParseError("InvokeType request".to_string()))?;
619
620    let body = serialize_json(&invocation_request.parameters)?;
621
622    build_post(state, request_url, body)
623}
624
625fn request_from_invocation_system(
626    state: &FHIRHttpState,
627    invocation_request: &FHIRInvokeSystemRequest,
628) -> Result<reqwest::Request, OperationOutcomeError> {
629    let request_url = state
630        .api_url
631        .join(invocation_request.operation.name())
632        .map_err(|_| FHIRHTTPError::UrlParseError("InvokeSystem request".to_string()))?;
633
634    let body = serialize_json(&invocation_request.parameters)?;
635
636    build_post(state, request_url, body)
637}
638
639#[derive(Clone, Copy)]
640enum FHIRResponseRequest<'a> {
641    Read,
642    Create,
643    Patch,
644    Transaction,
645    VersionRead,
646    Update(&'a UpdateRequest),
647    Delete(&'a DeleteRequest),
648    Capabilities,
649    Search(&'a SearchRequest),
650    History(&'a HistoryRequest),
651    Invocation(&'a InvocationRequest),
652    Batch,
653}
654
655impl<'a> FHIRResponseRequest<'a> {
656    const fn response_request(request: &'a FHIRRequest) -> Self {
657        match request {
658            FHIRRequest::Compartment(request) => Self::response_request(&request.request),
659            FHIRRequest::Read(_) => Self::Read,
660            FHIRRequest::Create(_) => Self::Create,
661            FHIRRequest::Patch(_) => Self::Patch,
662            FHIRRequest::Transaction(_) => Self::Transaction,
663            FHIRRequest::VersionRead(_) => Self::VersionRead,
664            FHIRRequest::Update(request) => Self::Update(request),
665            FHIRRequest::Delete(request) => Self::Delete(request),
666            FHIRRequest::Capabilities => Self::Capabilities,
667            FHIRRequest::Search(request) => Self::Search(request),
668            FHIRRequest::History(request) => Self::History(request),
669            FHIRRequest::Invocation(request) => Self::Invocation(request),
670            FHIRRequest::Batch(_) => Self::Batch,
671        }
672    }
673}
674
675fn http_response_to_fhir_response<'a>(
676    fhir_request: &'a FHIRRequest,
677    response: reqwest::Response,
678) -> Pin<Box<dyn Future<Output = Result<FHIRResponse, OperationOutcomeError>> + Send + 'a>> {
679    Box::pin(async move {
680        let response_request = FHIRResponseRequest::response_request(fhir_request);
681        let body = read_response(response).await?;
682
683        build_response(&response_request, &body)
684    })
685}
686
687fn check_for_errors(
688    status: reqwest::StatusCode,
689    body: Option<&[u8]>,
690) -> Result<(), OperationOutcomeError> {
691    if !status.is_success() {
692        if let Some(body) = body
693            && let Ok(operation_outcome) = serde_json::from_slice::<OperationOutcome>(body)
694        {
695            return Err(OperationOutcomeError::new(None, operation_outcome));
696        }
697
698        return Err(OperationOutcomeError::error(
699            IssueType::exception(),
700            format!("HTTP returned error '{status}'."),
701        ));
702    }
703    Ok(())
704}
705
706async fn read_response(response: reqwest::Response) -> Result<bytes::Bytes, OperationOutcomeError> {
707    let status = response.status();
708
709    let body = response
710        .bytes()
711        .await
712        .map_err(FHIRHTTPError::ReqwestError)?;
713
714    check_for_errors(status, Some(&body))?;
715
716    Ok(body)
717}
718
719fn build_response(
720    request: &FHIRResponseRequest<'_>,
721    body: &[u8],
722) -> Result<FHIRResponse, OperationOutcomeError> {
723    match request {
724        FHIRResponseRequest::Read => build_read_response(body),
725        FHIRResponseRequest::Create => build_create_response(body),
726        FHIRResponseRequest::Patch => build_patch_response(body),
727        FHIRResponseRequest::Transaction => build_transaction_response(body),
728        FHIRResponseRequest::VersionRead => build_version_read_response(body),
729        FHIRResponseRequest::Update(request) => build_update_response(request, body),
730        FHIRResponseRequest::Delete(request) => build_delete_response(request, body),
731        FHIRResponseRequest::Capabilities => build_capabilities_response(body),
732        FHIRResponseRequest::Search(request) => build_search_response(request, body),
733        FHIRResponseRequest::History(request) => build_history_response(request, body),
734        FHIRResponseRequest::Invocation(request) => build_invocation_response(request, body),
735        FHIRResponseRequest::Batch => build_batch_response(body),
736    }
737}
738
739fn deserialize<T: serde::de::DeserializeOwned>(body: &[u8]) -> Result<T, OperationOutcomeError> {
740    serde_json::from_slice(body)
741        .map_err(FHIRHTTPError::from)
742        .map_err(Into::into)
743}
744
745fn build_read_response(body: &[u8]) -> Result<FHIRResponse, OperationOutcomeError> {
746    Ok(FHIRResponse::Read(FHIRReadResponse {
747        resource: Some(deserialize(body)?),
748    }))
749}
750
751fn build_create_response(body: &[u8]) -> Result<FHIRResponse, OperationOutcomeError> {
752    Ok(FHIRResponse::Create(FHIRCreateResponse {
753        resource: deserialize(body)?,
754    }))
755}
756
757fn build_patch_response(body: &[u8]) -> Result<FHIRResponse, OperationOutcomeError> {
758    Ok(FHIRResponse::Patch(FHIRPatchResponse {
759        resource: deserialize(body)?,
760    }))
761}
762
763fn build_transaction_response(body: &[u8]) -> Result<FHIRResponse, OperationOutcomeError> {
764    Ok(FHIRResponse::Transaction(
765        request::FHIRTransactionResponse {
766            resource: deserialize(body)?,
767        },
768    ))
769}
770
771fn build_batch_response(body: &[u8]) -> Result<FHIRResponse, OperationOutcomeError> {
772    Ok(FHIRResponse::Batch(FHIRBatchResponse {
773        resource: deserialize(body)?,
774    }))
775}
776
777fn build_version_read_response(body: &[u8]) -> Result<FHIRResponse, OperationOutcomeError> {
778    Ok(FHIRResponse::VersionRead(
779        request::FHIRVersionReadResponse {
780            resource: deserialize(body)?,
781        },
782    ))
783}
784
785fn build_update_response(
786    _request: &UpdateRequest,
787    body: &[u8],
788) -> Result<FHIRResponse, OperationOutcomeError> {
789    Ok(FHIRResponse::Update(request::FHIRUpdateResponse {
790        resource: deserialize(body)?,
791    }))
792}
793
794fn build_capabilities_response(body: &[u8]) -> Result<FHIRResponse, OperationOutcomeError> {
795    Ok(FHIRResponse::Capabilities(
796        request::FHIRCapabilitiesResponse {
797            capabilities: deserialize(body)?,
798        },
799    ))
800}
801
802fn build_search_response(
803    request: &SearchRequest,
804    body: &[u8],
805) -> Result<FHIRResponse, OperationOutcomeError> {
806    let bundle = deserialize(body)?;
807
808    match request {
809        SearchRequest::Type(_) => Ok(FHIRResponse::Search(SearchResponse::Type(
810            request::FHIRSearchTypeResponse { bundle },
811        ))),
812        SearchRequest::System(_) => Ok(FHIRResponse::Search(SearchResponse::System(
813            request::FHIRSearchSystemResponse { bundle },
814        ))),
815    }
816}
817
818fn build_delete_response(
819    request: &DeleteRequest,
820    body: &[u8],
821) -> Result<FHIRResponse, OperationOutcomeError> {
822    match request {
823        DeleteRequest::Instance(_) => Ok(FHIRResponse::Delete(DeleteResponse::Instance(Box::new(
824            request::FHIRDeleteInstanceResponse {
825                resource: deserialize(body)?,
826            },
827        )))),
828
829        DeleteRequest::Type(_) => Ok(FHIRResponse::Delete(DeleteResponse::Type(
830            request::FHIRDeleteTypeResponse {},
831        ))),
832
833        DeleteRequest::System(_) => Ok(FHIRResponse::Delete(DeleteResponse::System(
834            request::FHIRDeleteSystemResponse {},
835        ))),
836    }
837}
838
839fn build_history_response(
840    request: &HistoryRequest,
841    body: &[u8],
842) -> Result<FHIRResponse, OperationOutcomeError> {
843    let bundle = deserialize(body)?;
844
845    match request {
846        HistoryRequest::Instance(_) => Ok(FHIRResponse::History(HistoryResponse::Instance(
847            request::FHIRHistoryInstanceResponse { bundle },
848        ))),
849        HistoryRequest::Type(_) => Ok(FHIRResponse::History(HistoryResponse::Type(
850            request::FHIRHistoryTypeResponse { bundle },
851        ))),
852        HistoryRequest::System(_) => Ok(FHIRResponse::History(HistoryResponse::System(
853            request::FHIRHistorySystemResponse { bundle },
854        ))),
855    }
856}
857
858fn build_invocation_response(
859    request: &InvocationRequest,
860    body: &[u8],
861) -> Result<FHIRResponse, OperationOutcomeError> {
862    let resource = deserialize(body)?;
863
864    match request {
865        InvocationRequest::Instance(_) => Ok(FHIRResponse::Invoke(InvokeResponse::Instance(
866            request::FHIRInvokeInstanceResponse { resource },
867        ))),
868        InvocationRequest::Type(_) => Ok(FHIRResponse::Invoke(InvokeResponse::Type(
869            request::FHIRInvokeTypeResponse { resource },
870        ))),
871        InvocationRequest::System(_) => Ok(FHIRResponse::Invoke(InvokeResponse::System(
872            request::FHIRInvokeSystemResponse { resource },
873        ))),
874    }
875}
876
877struct HTTPMiddleware {}
878impl HTTPMiddleware {
879    fn new() -> Self {
880        HTTPMiddleware {}
881    }
882}
883impl<CTX: Send + 'static + Debug + HttpRequestHeaders>
884    MiddlewareChain<Arc<FHIRHttpState>, CTX, FHIRRequest, FHIRResponse, OperationOutcomeError>
885    for HTTPMiddleware
886{
887    fn call(
888        &self,
889        state: Arc<FHIRHttpState>,
890        context: Context<CTX, FHIRRequest, FHIRResponse>,
891        _next: Option<
892            Arc<
893                Next<
894                    Arc<FHIRHttpState>,
895                    Context<CTX, FHIRRequest, FHIRResponse>,
896                    OperationOutcomeError,
897                >,
898            >,
899        >,
900    ) -> Pin<
901        Box<
902            dyn Future<
903                    Output = Result<Context<CTX, FHIRRequest, FHIRResponse>, OperationOutcomeError>,
904                > + Send,
905        >,
906    > {
907        Box::pin(async move {
908            let mut http_request = fhir_request_to_http_request(&state, &context.request).await?;
909
910            // Applied after the request is built so a caller-supplied header takes
911            // precedence over the configured authentication and the default
912            // `Accept`/`Content-Type`. `insert` replaces rather than appends, so a
913            // repeated name overwrites instead of stacking values.
914            if let Some(headers) = context.ctx.request_headers() {
915                let request_headers = http_request.headers_mut();
916                for (name, value) in headers {
917                    request_headers.insert(name, value.clone());
918                }
919            }
920
921            let response = state
922                .client
923                .execute(http_request)
924                .await
925                .map_err(FHIRHTTPError::ReqwestError)?;
926
927            let mut next_context = context;
928            let fhir_response =
929                http_response_to_fhir_response(&next_context.request, response).await?;
930            next_context.response = Some(fhir_response);
931
932            Ok(next_context)
933        })
934    }
935}
936
937impl<CTX: 'static + Send + Sync + Debug + HttpRequestHeaders> FHIRHttpClient<CTX> {
938    #[must_use]
939    pub fn new(state: FHIRHttpState) -> Self {
940        let middleware = Middleware::new(vec![Box::new(HTTPMiddleware::new())]);
941        FHIRHttpClient {
942            state: Arc::new(state),
943            middleware,
944        }
945    }
946}
947
948impl<CTX: 'static + Send + Sync + Debug + HttpRequestHeaders> FHIRClient<CTX, OperationOutcomeError>
949    for FHIRHttpClient<CTX>
950{
951    async fn request(
952        &self,
953        ctx: CTX,
954        request: crate::request::FHIRRequest,
955    ) -> Result<crate::request::FHIRResponse, OperationOutcomeError> {
956        let response = self
957            .middleware
958            .call(self.state.clone(), ctx, request)
959            .await?;
960
961        response
962            .response
963            .ok_or_else(|| FHIRHTTPError::NoResponse.into())
964    }
965
966    async fn capabilities(&self, ctx: CTX) -> Result<CapabilityStatement, OperationOutcomeError> {
967        let res = self
968            .middleware
969            .call(self.state.clone(), ctx, FHIRRequest::Capabilities)
970            .await?;
971
972        match res.response {
973            Some(FHIRResponse::Capabilities(capabilities_response)) => {
974                Ok(capabilities_response.capabilities)
975            }
976            _ => Err(FHIRHTTPError::NoResponse.into()),
977        }
978    }
979
980    async fn search_system(
981        &self,
982        ctx: CTX,
983        parameters: crate::ParsedParameters,
984    ) -> Result<Bundle, OperationOutcomeError> {
985        let res = self
986            .middleware
987            .call(
988                self.state.clone(),
989                ctx,
990                FHIRRequest::Search(SearchRequest::System(request::FHIRSearchSystemRequest {
991                    parameters,
992                })),
993            )
994            .await?;
995        match res.response {
996            Some(FHIRResponse::Search(SearchResponse::System(search_system_response))) => {
997                Ok(search_system_response.bundle)
998            }
999            _ => Err(FHIRHTTPError::NoResponse.into()),
1000        }
1001    }
1002
1003    async fn search_type(
1004        &self,
1005        ctx: CTX,
1006        resource_type: ResourceType,
1007        parameters: crate::ParsedParameters,
1008    ) -> Result<Bundle, OperationOutcomeError> {
1009        let res = self
1010            .middleware
1011            .call(
1012                self.state.clone(),
1013                ctx,
1014                FHIRRequest::Search(SearchRequest::Type(request::FHIRSearchTypeRequest {
1015                    resource_type,
1016                    parameters,
1017                })),
1018            )
1019            .await?;
1020        match res.response {
1021            Some(FHIRResponse::Search(SearchResponse::Type(search_type_response))) => {
1022                Ok(search_type_response.bundle)
1023            }
1024            _ => Err(FHIRHTTPError::NoResponse.into()),
1025        }
1026    }
1027
1028    async fn create(
1029        &self,
1030        ctx: CTX,
1031        resource_type: ResourceType,
1032        resource: Resource,
1033    ) -> Result<Resource, OperationOutcomeError> {
1034        let res = self
1035            .middleware
1036            .call(
1037                self.state.clone(),
1038                ctx,
1039                FHIRRequest::Create(request::FHIRCreateRequest {
1040                    resource_type,
1041                    resource,
1042                }),
1043            )
1044            .await?;
1045
1046        match res.response {
1047            Some(FHIRResponse::Create(create_response)) => Ok(create_response.resource),
1048            _ => Err(FHIRHTTPError::NoResponse.into()),
1049        }
1050    }
1051
1052    async fn update(
1053        &self,
1054        ctx: CTX,
1055        resource_type: ResourceType,
1056        id: String,
1057        resource: Resource,
1058    ) -> Result<Resource, OperationOutcomeError> {
1059        let res = self
1060            .middleware
1061            .call(
1062                self.state.clone(),
1063                ctx,
1064                FHIRRequest::Update(UpdateRequest::Instance(
1065                    request::FHIRUpdateInstanceRequest {
1066                        resource_type,
1067                        id,
1068                        resource,
1069                    },
1070                )),
1071            )
1072            .await?;
1073        match res.response {
1074            Some(FHIRResponse::Update(update_response)) => Ok(update_response.resource),
1075            _ => Err(FHIRHTTPError::NoResponse.into()),
1076        }
1077    }
1078
1079    async fn conditional_update(
1080        &self,
1081        ctx: CTX,
1082        resource_type: ResourceType,
1083        parameters: crate::ParsedParameters,
1084        resource: Resource,
1085    ) -> Result<Resource, OperationOutcomeError> {
1086        let res = self
1087            .middleware
1088            .call(
1089                self.state.clone(),
1090                ctx,
1091                FHIRRequest::Update(UpdateRequest::Conditional(
1092                    request::FHIRConditionalUpdateRequest {
1093                        resource_type,
1094                        parameters,
1095                        resource,
1096                    },
1097                )),
1098            )
1099            .await?;
1100        match res.response {
1101            Some(FHIRResponse::Update(update_response)) => Ok(update_response.resource),
1102            _ => Err(FHIRHTTPError::NoResponse.into()),
1103        }
1104    }
1105
1106    async fn patch(
1107        &self,
1108        ctx: CTX,
1109        resource_type: ResourceType,
1110        id: String,
1111        patch: json_patch::Patch,
1112    ) -> Result<Resource, OperationOutcomeError> {
1113        let res = self
1114            .middleware
1115            .call(
1116                self.state.clone(),
1117                ctx,
1118                FHIRRequest::Patch(request::FHIRPatchRequest {
1119                    resource_type,
1120                    id,
1121                    patch,
1122                }),
1123            )
1124            .await?;
1125
1126        match res.response {
1127            Some(FHIRResponse::Patch(patch_response)) => Ok(patch_response.resource),
1128            _ => Err(FHIRHTTPError::NoResponse.into()),
1129        }
1130    }
1131
1132    async fn read(
1133        &self,
1134        ctx: CTX,
1135        resource_type: ResourceType,
1136        id: String,
1137    ) -> Result<Option<Resource>, OperationOutcomeError> {
1138        let res = self
1139            .middleware
1140            .call(
1141                self.state.clone(),
1142                ctx,
1143                FHIRRequest::Read(request::FHIRReadRequest { resource_type, id }),
1144            )
1145            .await?;
1146
1147        match res.response {
1148            Some(FHIRResponse::Read(read_response)) => Ok(read_response.resource),
1149            _ => Err(FHIRHTTPError::NoResponse.into()),
1150        }
1151    }
1152
1153    async fn vread(
1154        &self,
1155        ctx: CTX,
1156        resource_type: ResourceType,
1157        id: String,
1158        version_id: String,
1159    ) -> Result<Option<Resource>, OperationOutcomeError> {
1160        let res = self
1161            .middleware
1162            .call(
1163                self.state.clone(),
1164                ctx,
1165                FHIRRequest::VersionRead(request::FHIRVersionReadRequest {
1166                    resource_type,
1167                    id,
1168                    version_id: VersionId::new(version_id),
1169                }),
1170            )
1171            .await?;
1172
1173        match res.response {
1174            Some(FHIRResponse::VersionRead(version_read_response)) => {
1175                Ok(Some(version_read_response.resource))
1176            }
1177            _ => Err(FHIRHTTPError::NoResponse.into()),
1178        }
1179    }
1180
1181    async fn delete_instance(
1182        &self,
1183        ctx: CTX,
1184        resource_type: ResourceType,
1185        id: String,
1186    ) -> Result<(), OperationOutcomeError> {
1187        let res = self
1188            .middleware
1189            .call(
1190                self.state.clone(),
1191                ctx,
1192                FHIRRequest::Delete(DeleteRequest::Instance(
1193                    request::FHIRDeleteInstanceRequest { resource_type, id },
1194                )),
1195            )
1196            .await?;
1197
1198        match res.response {
1199            Some(FHIRResponse::Delete(_delete_instance_response)) => Ok(()),
1200            _ => Err(FHIRHTTPError::NoResponse.into()),
1201        }
1202    }
1203
1204    async fn delete_type(
1205        &self,
1206        ctx: CTX,
1207        resource_type: ResourceType,
1208        parameters: crate::ParsedParameters,
1209    ) -> Result<(), OperationOutcomeError> {
1210        let res = self
1211            .middleware
1212            .call(
1213                self.state.clone(),
1214                ctx,
1215                FHIRRequest::Delete(DeleteRequest::Type(request::FHIRDeleteTypeRequest {
1216                    resource_type,
1217                    parameters,
1218                })),
1219            )
1220            .await?;
1221        match res.response {
1222            Some(FHIRResponse::Delete(_delete_type_response)) => Ok(()),
1223            _ => Err(FHIRHTTPError::NoResponse.into()),
1224        }
1225    }
1226
1227    async fn delete_system(
1228        &self,
1229        ctx: CTX,
1230        parameters: crate::ParsedParameters,
1231    ) -> Result<(), OperationOutcomeError> {
1232        let res = self
1233            .middleware
1234            .call(
1235                self.state.clone(),
1236                ctx,
1237                FHIRRequest::Delete(DeleteRequest::System(request::FHIRDeleteSystemRequest {
1238                    parameters,
1239                })),
1240            )
1241            .await?;
1242        match res.response {
1243            Some(FHIRResponse::Delete(_delete_system_response)) => Ok(()),
1244            _ => Err(FHIRHTTPError::NoResponse.into()),
1245        }
1246    }
1247
1248    async fn history_system(
1249        &self,
1250        ctx: CTX,
1251        parameters: crate::ParsedParameters,
1252    ) -> Result<Bundle, OperationOutcomeError> {
1253        let res = self
1254            .middleware
1255            .call(
1256                self.state.clone(),
1257                ctx,
1258                FHIRRequest::History(HistoryRequest::System(request::FHIRHistorySystemRequest {
1259                    parameters,
1260                })),
1261            )
1262            .await?;
1263
1264        match res.response {
1265            Some(FHIRResponse::History(HistoryResponse::System(history_system_response))) => {
1266                Ok(history_system_response.bundle)
1267            }
1268            _ => Err(FHIRHTTPError::NoResponse.into()),
1269        }
1270    }
1271
1272    async fn history_type(
1273        &self,
1274        ctx: CTX,
1275        resource_type: ResourceType,
1276        parameters: crate::ParsedParameters,
1277    ) -> Result<Bundle, OperationOutcomeError> {
1278        let res = self
1279            .middleware
1280            .call(
1281                self.state.clone(),
1282                ctx,
1283                FHIRRequest::History(HistoryRequest::Type(request::FHIRHistoryTypeRequest {
1284                    resource_type,
1285                    parameters,
1286                })),
1287            )
1288            .await?;
1289
1290        match res.response {
1291            Some(FHIRResponse::History(HistoryResponse::Type(history_type_response))) => {
1292                Ok(history_type_response.bundle)
1293            }
1294            _ => Err(FHIRHTTPError::NoResponse.into()),
1295        }
1296    }
1297
1298    async fn history_instance(
1299        &self,
1300        ctx: CTX,
1301        resource_type: ResourceType,
1302        id: String,
1303        parameters: crate::ParsedParameters,
1304    ) -> Result<Bundle, OperationOutcomeError> {
1305        let res = self
1306            .middleware
1307            .call(
1308                self.state.clone(),
1309                ctx,
1310                FHIRRequest::History(HistoryRequest::Instance(
1311                    request::FHIRHistoryInstanceRequest {
1312                        resource_type,
1313                        id,
1314                        parameters,
1315                    },
1316                )),
1317            )
1318            .await?;
1319
1320        match res.response {
1321            Some(FHIRResponse::History(HistoryResponse::Instance(history_instance_response))) => {
1322                Ok(history_instance_response.bundle)
1323            }
1324            _ => Err(FHIRHTTPError::NoResponse.into()),
1325        }
1326    }
1327
1328    async fn invoke_instance(
1329        &self,
1330        ctx: CTX,
1331        resource_type: ResourceType,
1332        id: String,
1333        operation: String,
1334        parameters: Parameters,
1335    ) -> Result<Resource, OperationOutcomeError> {
1336        let res = self
1337            .middleware
1338            .call(
1339                self.state.clone(),
1340                ctx,
1341                FHIRRequest::Invocation(InvocationRequest::Instance(
1342                    request::FHIRInvokeInstanceRequest {
1343                        resource_type,
1344                        id,
1345                        operation: Operation::new(&operation),
1346                        parameters,
1347                    },
1348                )),
1349            )
1350            .await?;
1351
1352        match res.response {
1353            Some(FHIRResponse::Invoke(InvokeResponse::Instance(invoke_instance_response))) => {
1354                Ok(invoke_instance_response.resource)
1355            }
1356            _ => Err(FHIRHTTPError::NoResponse.into()),
1357        }
1358    }
1359
1360    async fn invoke_type(
1361        &self,
1362        ctx: CTX,
1363        resource_type: ResourceType,
1364        operation: String,
1365        parameters: Parameters,
1366    ) -> Result<Resource, OperationOutcomeError> {
1367        let res = self
1368            .middleware
1369            .call(
1370                self.state.clone(),
1371                ctx,
1372                FHIRRequest::Invocation(InvocationRequest::Type(request::FHIRInvokeTypeRequest {
1373                    resource_type,
1374                    operation: Operation::new(&operation),
1375                    parameters,
1376                })),
1377            )
1378            .await?;
1379
1380        match res.response {
1381            Some(FHIRResponse::Invoke(InvokeResponse::Type(invoke_type_response))) => {
1382                Ok(invoke_type_response.resource)
1383            }
1384            _ => Err(FHIRHTTPError::NoResponse.into()),
1385        }
1386    }
1387
1388    async fn invoke_system(
1389        &self,
1390        ctx: CTX,
1391        operation: String,
1392        parameters: Parameters,
1393    ) -> Result<Resource, OperationOutcomeError> {
1394        let res = self
1395            .middleware
1396            .call(
1397                self.state.clone(),
1398                ctx,
1399                FHIRRequest::Invocation(InvocationRequest::System(
1400                    request::FHIRInvokeSystemRequest {
1401                        operation: Operation::new(&operation),
1402                        parameters,
1403                    },
1404                )),
1405            )
1406            .await?;
1407
1408        match res.response {
1409            Some(FHIRResponse::Invoke(InvokeResponse::System(invoke_system_response))) => {
1410                Ok(invoke_system_response.resource)
1411            }
1412            _ => Err(FHIRHTTPError::NoResponse.into()),
1413        }
1414    }
1415
1416    async fn transaction(&self, ctx: CTX, bundle: Bundle) -> Result<Bundle, OperationOutcomeError> {
1417        let res = self
1418            .middleware
1419            .call(
1420                self.state.clone(),
1421                ctx,
1422                FHIRRequest::Transaction(request::FHIRTransactionRequest { resource: bundle }),
1423            )
1424            .await?;
1425
1426        match res.response {
1427            Some(FHIRResponse::Transaction(transaction_response)) => {
1428                Ok(transaction_response.resource)
1429            }
1430            _ => Err(FHIRHTTPError::NoResponse.into()),
1431        }
1432    }
1433
1434    async fn batch(&self, ctx: CTX, bundle: Bundle) -> Result<Bundle, OperationOutcomeError> {
1435        let res = self
1436            .middleware
1437            .call(
1438                self.state.clone(),
1439                ctx,
1440                FHIRRequest::Batch(request::FHIRBatchRequest { resource: bundle }),
1441            )
1442            .await?;
1443
1444        match res.response {
1445            Some(FHIRResponse::Batch(batch_response)) => Ok(batch_response.resource),
1446            _ => Err(FHIRHTTPError::NoResponse.into()),
1447        }
1448    }
1449}