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
52pub trait HttpRequestHeaders {
58 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 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(¶meter.key(), ¶meter.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
197async 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 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 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
916fn 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#[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
945pub trait FHIRHttpExchange<CTX>: FHIRClient<CTX, OperationOutcomeError> {
948 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 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 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}