use fhir_model::{ParsedReference, WrongResourceType};
use reqwest::{
StatusCode, Url,
header::{self, HeaderValue},
};
use serde::{Serialize, de::DeserializeOwned};
use super::{
Client, Error, SearchParameters, misc,
paging::Page,
patch::{PatchViaFhir, PatchViaJson},
transaction::BatchTransaction,
};
use crate::{
client::misc::make_uuid_header_value,
extensions::{AnyResource, GenericResource, ReferenceExt},
version::FhirVersion,
};
impl<V: FhirVersion> Client<V>
where
(StatusCode, V::OperationOutcome): Into<Error>,
{
pub async fn capabilities(&self) -> Result<V::CapabilityStatement, Error> {
let url = self.url(&["metadata"]);
let request = self.0.client.get(url).header(header::ACCEPT, V::MIME_TYPE);
let response = self.run_request(request).await?;
if response.status().is_success() {
let capability_statement: V::CapabilityStatement = response.json().await?;
Ok(capability_statement)
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub(crate) async fn read_generic<R: DeserializeOwned>(
&self,
url: Url,
correlation_id: Option<HeaderValue>,
) -> Result<Option<R>, Error> {
let mut request = self.0.client.get(url).header(header::ACCEPT, V::MIME_TYPE);
if let Some(correlation_id) = correlation_id {
request = request.header("X-Correlation-Id", correlation_id);
}
let response = self.run_request(request).await?;
if response.status().is_success() {
let resource: R = response.json().await?;
Ok(Some(resource))
} else if [StatusCode::NOT_FOUND, StatusCode::GONE].contains(&response.status()) {
Ok(None)
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub async fn read<R: AnyResource<V> + DeserializeOwned>(
&self,
id: &str,
) -> Result<Option<R>, Error> {
let url = self.url(&[R::TYPE_STR, id]);
self.read_generic(url, None).await
}
pub async fn read_version<R: AnyResource<V> + DeserializeOwned>(
&self,
id: &str,
version_id: &str,
) -> Result<Option<R>, Error> {
let url = self.url(&[R::TYPE_STR, id, "_history", version_id]);
self.read_generic(url, None).await
}
pub async fn read_referenced(&self, reference: &V::Reference) -> Result<V::Resource, Error> {
let parsed_reference = reference.parse().ok_or(Error::MissingReference)?;
let url = match parsed_reference {
ParsedReference::Local { .. } => return Err(Error::LocalReference),
ParsedReference::Relative { resource_type, id, version_id } => {
if let Some(version_id) = version_id {
self.url(&[resource_type, id, "_history", version_id])
} else {
self.url(&[resource_type, id])
}
}
ParsedReference::Absolute { url, .. } => {
url.parse().map_err(|_| Error::UrlParse(url.to_owned()))?
}
};
let resource: V::Resource = self
.read_generic(url.clone(), None)
.await?
.ok_or_else(|| Error::ResourceNotFound(url.to_string()))?;
if let Some(resource_type) = reference.r#type() {
if resource.resource_type_str() != resource_type {
return Err(Error::WrongResourceType(
resource.resource_type_str().to_owned(),
resource_type.to_owned(),
));
}
}
Ok(resource)
}
pub async fn history<R>(&self, id: Option<&str>) -> Result<Page<V, R>, Error>
where
R: AnyResource<V> + TryFrom<V::Resource, Error = WrongResourceType> + 'static,
for<'a> &'a R: TryFrom<&'a V::Resource>,
{
let correlation_id = make_uuid_header_value();
let url = {
if let Some(id) = id {
self.url(&[R::TYPE_STR, id, "_history"])
} else {
self.url(&[R::TYPE_STR, "_history"])
}
};
let request = self
.0
.client
.get(url)
.header(header::ACCEPT, V::MIME_TYPE)
.header("X-Correlation-Id", correlation_id.clone());
let response = self.run_request(request).await?;
if response.status().is_success() {
let bundle: V::Bundle = response.json().await?;
Ok(Page::new(self.clone(), bundle, correlation_id))
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub(crate) async fn create_generic<R: Serialize + Send + Sync>(
&self,
resource_type: &str,
resource: &R,
) -> Result<(String, Option<String>), Error> {
let url = self.url(&[resource_type]);
let request = self
.0
.client
.post(url)
.header(header::ACCEPT, V::MIME_TYPE)
.header(header::CONTENT_TYPE, V::MIME_TYPE)
.json(resource);
let response = self.run_request(request).await?;
if response.status().is_success() {
let (id, version_id) = misc::parse_location(response.headers())?;
let version_id = version_id.or_else(|| misc::parse_etag(response.headers()).ok());
Ok((id, version_id))
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub async fn create<R: AnyResource<V> + Serialize + Send + Sync>(
&self,
resource: &R,
) -> Result<(String, Option<String>), Error> {
self.create_generic(R::TYPE_STR, resource).await
}
pub(crate) async fn update_generic<R: Serialize + Send + Sync>(
&self,
resource_type: &str,
id: &str,
resource: &R,
version_id: Option<&str>,
) -> Result<(bool, String), Error> {
let url = self.url(&[resource_type, id]);
let mut request = self
.0
.client
.put(url)
.header(header::ACCEPT, V::MIME_TYPE)
.header(header::CONTENT_TYPE, V::MIME_TYPE)
.json(resource);
if let Some(version_id) = version_id {
let if_match = HeaderValue::from_str(&format!("W/\"{version_id}\""))
.map_err(|_| Error::MissingVersionId)?;
request = request.header(header::IF_MATCH, if_match);
}
let response = self.run_request(request).await?;
if response.status().is_success() {
let created = response.status() == StatusCode::CREATED;
let version_id = misc::parse_etag(response.headers())?;
Ok((created, version_id))
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub async fn update<R: AnyResource<V> + Serialize + Send + Sync>(
&self,
resource: &R,
conditional: bool,
) -> Result<(bool, String), Error> {
let id = resource.id().ok_or(Error::MissingId)?;
let version_id = conditional
.then(|| resource.version_id().ok_or(Error::MissingVersionId))
.transpose()?;
self.update_generic(R::TYPE_STR, id, resource, version_id).await
}
pub async fn delete(&self, resource_type: V::ResourceType, id: &str) -> Result<(), Error> {
let url = self.url(&[resource_type.as_ref(), id]);
let request = self.0.client.delete(url).header(header::ACCEPT, V::MIME_TYPE);
let response = self.run_request(request).await?;
if response.status().is_success() {
Ok(())
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub async fn search_all(
&self,
queries: SearchParameters,
) -> Result<Page<V, V::Resource>, Error> {
let correlation_id = make_uuid_header_value();
let url = self.url(&[]);
let request = self
.0
.client
.get(url)
.query(&queries.into_queries())
.header(header::ACCEPT, V::MIME_TYPE)
.header("X-Correlation-Id", correlation_id.clone());
let response = self.run_request(request).await?;
if response.status().is_success() {
let bundle: V::Bundle = response.json().await?;
Ok(Page::new(self.clone(), bundle, correlation_id))
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub async fn search<R>(&self, queries: SearchParameters) -> Result<Page<V, R>, Error>
where
R: AnyResource<V> + TryFrom<V::Resource, Error = WrongResourceType> + 'static,
for<'a> &'a R: TryFrom<&'a V::Resource>,
{
let correlation_id = make_uuid_header_value();
let url = self.url(&[R::TYPE_STR]);
let request = self
.0
.client
.get(url)
.query(&queries.into_queries())
.header(header::ACCEPT, V::MIME_TYPE)
.header("X-Correlation-Id", correlation_id.clone());
let response = self.run_request(request).await?;
if response.status().is_success() {
let bundle: V::Bundle = response.json().await?;
Ok(Page::new(self.clone(), bundle, correlation_id))
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub async fn search_custom<R, F>(&self, make_request: F) -> Result<Page<V, R>, Error>
where
R: TryFrom<V::Resource> + Send + Sync + 'static,
for<'a> &'a R: TryFrom<&'a V::Resource>,
F: FnOnce(&reqwest::Client) -> reqwest::RequestBuilder + Send,
{
let mut request_builder = (make_request)(&self.0.client);
let (client, request_result) = request_builder.build_split();
let mut request = request_result?;
let correlation_id = request
.headers_mut()
.entry("X-Correlation-Id")
.or_insert_with(make_uuid_header_value)
.clone();
request_builder = reqwest::RequestBuilder::from_parts(client, request);
let response = self.run_request(request_builder).await?;
if response.status().is_success() {
let bundle: V::Bundle = response.json().await?;
Ok(Page::new(self.clone(), bundle, correlation_id))
} else {
Err(Error::from_response::<V>(response).await)
}
}
pub fn patch_via_fhir<'a>(
&self,
resource_type: V::ResourceType,
id: &'a str,
) -> PatchViaFhir<'a, V> {
PatchViaFhir::new(self.clone(), resource_type, id)
}
pub fn patch_via_json<'a>(
&self,
resource_type: V::ResourceType,
id: &'a str,
) -> PatchViaJson<'a, V> {
PatchViaJson::new(self.clone(), resource_type, id)
}
pub fn batch(&self) -> BatchTransaction<V> {
BatchTransaction::new(self.clone(), false)
}
pub fn transaction(&self) -> BatchTransaction<V> {
BatchTransaction::new(self.clone(), true)
}
}