Skip to main content

haste_fhir_client/
http.rs

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