1use fhir_model::{ParsedReference, WrongResourceType};
4use reqwest::{
5 StatusCode, Url,
6 header::{self, HeaderValue},
7};
8use serde::{Serialize, de::DeserializeOwned};
9
10use super::{
11 Client, Error, SearchParameters, misc,
12 paging::Page,
13 patch::{PatchViaFhir, PatchViaJson},
14 transaction::BatchTransaction,
15};
16use crate::{
17 client::misc::make_uuid_header_value,
18 extensions::{AnyResource, GenericResource, ReferenceExt},
19 version::FhirVersion,
20};
21
22impl<V: FhirVersion> Client<V>
23where
24 (StatusCode, V::OperationOutcome): Into<Error>,
25{
26 pub async fn capabilities(&self) -> Result<V::CapabilityStatement, Error> {
29 let url = self.url(&["metadata"]);
30 let request = self.0.client.get(url).header(header::ACCEPT, V::MIME_TYPE);
31
32 let response = self.run_request(request).await?;
33 if response.status().is_success() {
34 let capability_statement: V::CapabilityStatement = response.json().await?;
35 Ok(capability_statement)
36 } else {
37 Err(Error::from_response::<V>(response).await)
38 }
39 }
40
41 pub(crate) async fn read_generic<R: DeserializeOwned>(
43 &self,
44 url: Url,
45 correlation_id: Option<HeaderValue>,
46 ) -> Result<Option<R>, Error> {
47 let mut request = self.0.client.get(url).header(header::ACCEPT, V::MIME_TYPE);
48 if let Some(correlation_id) = correlation_id {
49 request = request.header("X-Correlation-Id", correlation_id);
50 }
51
52 let response = self.run_request(request).await?;
53 if response.status().is_success() {
54 let resource: R = response.json().await?;
55 Ok(Some(resource))
56 } else if [StatusCode::NOT_FOUND, StatusCode::GONE].contains(&response.status()) {
57 Ok(None)
58 } else {
59 Err(Error::from_response::<V>(response).await)
60 }
61 }
62
63 pub async fn read<R: AnyResource<V> + DeserializeOwned>(
65 &self,
66 id: &str,
67 ) -> Result<Option<R>, Error> {
68 let url = self.url(&[R::TYPE_STR, id]);
69 self.read_generic(url, None).await
70 }
71
72 pub async fn read_version<R: AnyResource<V> + DeserializeOwned>(
74 &self,
75 id: &str,
76 version_id: &str,
77 ) -> Result<Option<R>, Error> {
78 let url = self.url(&[R::TYPE_STR, id, "_history", version_id]);
79 self.read_generic(url, None).await
80 }
81
82 pub async fn read_referenced(&self, reference: &V::Reference) -> Result<V::Resource, Error> {
84 let parsed_reference = reference.parse().ok_or(Error::MissingReference)?;
85 let url = match parsed_reference {
86 ParsedReference::Local { .. } => return Err(Error::LocalReference),
87 ParsedReference::Relative { resource_type, id, version_id } => {
88 if let Some(version_id) = version_id {
89 self.url(&[resource_type, id, "_history", version_id])
90 } else {
91 self.url(&[resource_type, id])
92 }
93 }
94 ParsedReference::Absolute { url, .. } => {
95 url.parse().map_err(|_| Error::UrlParse(url.to_owned()))?
96 }
97 };
98
99 let resource: V::Resource = self
100 .read_generic(url.clone(), None)
101 .await?
102 .ok_or_else(|| Error::ResourceNotFound(url.to_string()))?;
103 if let Some(resource_type) = reference.r#type() {
104 if resource.resource_type_str() != resource_type {
105 return Err(Error::WrongResourceType(
106 resource.resource_type_str().to_owned(),
107 resource_type.to_owned(),
108 ));
109 }
110 }
111
112 Ok(resource)
113 }
114
115 pub async fn history<R>(&self, id: Option<&str>) -> Result<Page<V, R>, Error>
117 where
118 R: AnyResource<V> + TryFrom<V::Resource, Error = WrongResourceType> + 'static,
119 for<'a> &'a R: TryFrom<&'a V::Resource>,
120 {
121 let correlation_id = make_uuid_header_value();
122
123 let url = {
124 if let Some(id) = id {
125 self.url(&[R::TYPE_STR, id, "_history"])
126 } else {
127 self.url(&[R::TYPE_STR, "_history"])
128 }
129 };
130 let request = self
131 .0
132 .client
133 .get(url)
134 .header(header::ACCEPT, V::MIME_TYPE)
135 .header("X-Correlation-Id", correlation_id.clone());
136
137 let response = self.run_request(request).await?;
138 if response.status().is_success() {
139 let bundle: V::Bundle = response.json().await?;
140 Ok(Page::new(self.clone(), bundle, correlation_id))
141 } else {
142 Err(Error::from_response::<V>(response).await)
143 }
144 }
145
146 pub(crate) async fn create_generic<R: Serialize + Send + Sync>(
148 &self,
149 resource_type: &str,
150 resource: &R,
151 ) -> Result<(String, Option<String>), Error> {
152 let url = self.url(&[resource_type]);
153 let request = self
154 .0
155 .client
156 .post(url)
157 .header(header::ACCEPT, V::MIME_TYPE)
158 .header(header::CONTENT_TYPE, V::MIME_TYPE)
159 .json(resource);
160
161 let response = self.run_request(request).await?;
162 if response.status().is_success() {
163 let (id, version_id) = misc::parse_location(response.headers())?;
164 let version_id = version_id.or_else(|| misc::parse_etag(response.headers()).ok());
165 Ok((id, version_id))
166 } else {
167 Err(Error::from_response::<V>(response).await)
168 }
169 }
170
171 pub async fn create<R: AnyResource<V> + Serialize + Send + Sync>(
174 &self,
175 resource: &R,
176 ) -> Result<(String, Option<String>), Error> {
177 self.create_generic(R::TYPE_STR, resource).await
178 }
179
180 pub(crate) async fn update_generic<R: Serialize + Send + Sync>(
182 &self,
183 resource_type: &str,
184 id: &str,
185 resource: &R,
186 version_id: Option<&str>,
187 ) -> Result<(bool, String), Error> {
188 let url = self.url(&[resource_type, id]);
189 let mut request = self
190 .0
191 .client
192 .put(url)
193 .header(header::ACCEPT, V::MIME_TYPE)
194 .header(header::CONTENT_TYPE, V::MIME_TYPE)
195 .json(resource);
196 if let Some(version_id) = version_id {
197 let if_match = HeaderValue::from_str(&format!("W/\"{version_id}\""))
198 .map_err(|_| Error::MissingVersionId)?;
199 request = request.header(header::IF_MATCH, if_match);
200 }
201
202 let response = self.run_request(request).await?;
203 if response.status().is_success() {
204 let created = response.status() == StatusCode::CREATED;
205 let version_id = misc::parse_etag(response.headers())?;
206 Ok((created, version_id))
207 } else {
208 Err(Error::from_response::<V>(response).await)
209 }
210 }
211
212 pub async fn update<R: AnyResource<V> + Serialize + Send + Sync>(
216 &self,
217 resource: &R,
218 conditional: bool,
219 ) -> Result<(bool, String), Error> {
220 let id = resource.id().ok_or(Error::MissingId)?;
221 let version_id = conditional
222 .then(|| resource.version_id().ok_or(Error::MissingVersionId))
223 .transpose()?;
224 self.update_generic(R::TYPE_STR, id, resource, version_id).await
225 }
226
227 pub async fn delete(&self, resource_type: V::ResourceType, id: &str) -> Result<(), Error> {
229 let url = self.url(&[resource_type.as_ref(), id]);
230 let request = self.0.client.delete(url).header(header::ACCEPT, V::MIME_TYPE);
231
232 let response = self.run_request(request).await?;
233 if response.status().is_success() {
234 Ok(())
235 } else {
236 Err(Error::from_response::<V>(response).await)
237 }
238 }
239
240 pub async fn search_all(
242 &self,
243 queries: SearchParameters,
244 ) -> Result<Page<V, V::Resource>, Error> {
245 let correlation_id = make_uuid_header_value();
248
249 let url = self.url(&[]);
250 let request = self
251 .0
252 .client
253 .get(url)
254 .query(&queries.into_queries())
255 .header(header::ACCEPT, V::MIME_TYPE)
256 .header("X-Correlation-Id", correlation_id.clone());
257
258 let response = self.run_request(request).await?;
259 if response.status().is_success() {
260 let bundle: V::Bundle = response.json().await?;
261 Ok(Page::new(self.clone(), bundle, correlation_id))
262 } else {
263 Err(Error::from_response::<V>(response).await)
264 }
265 }
266
267 pub async fn search<R>(&self, queries: SearchParameters) -> Result<Page<V, R>, Error>
269 where
270 R: AnyResource<V> + TryFrom<V::Resource, Error = WrongResourceType> + 'static,
271 for<'a> &'a R: TryFrom<&'a V::Resource>,
272 {
273 let correlation_id = make_uuid_header_value();
276
277 let url = self.url(&[R::TYPE_STR]);
278 let request = self
279 .0
280 .client
281 .get(url)
282 .query(&queries.into_queries())
283 .header(header::ACCEPT, V::MIME_TYPE)
284 .header("X-Correlation-Id", correlation_id.clone());
285
286 let response = self.run_request(request).await?;
287 if response.status().is_success() {
288 let bundle: V::Bundle = response.json().await?;
289 Ok(Page::new(self.clone(), bundle, correlation_id))
290 } else {
291 Err(Error::from_response::<V>(response).await)
292 }
293 }
294
295 pub async fn search_custom<R, F>(&self, make_request: F) -> Result<Page<V, R>, Error>
308 where
309 R: TryFrom<V::Resource> + Send + Sync + 'static,
310 for<'a> &'a R: TryFrom<&'a V::Resource>,
311 F: FnOnce(&reqwest::Client) -> reqwest::RequestBuilder + Send,
312 {
313 let mut request_builder = (make_request)(&self.0.client);
314 let (client, request_result) = request_builder.build_split();
315 let mut request = request_result?;
316 let correlation_id = request
317 .headers_mut()
318 .entry("X-Correlation-Id")
319 .or_insert_with(make_uuid_header_value)
320 .clone();
321 request_builder = reqwest::RequestBuilder::from_parts(client, request);
322
323 let response = self.run_request(request_builder).await?;
324 if response.status().is_success() {
325 let bundle: V::Bundle = response.json().await?;
326 Ok(Page::new(self.clone(), bundle, correlation_id))
327 } else {
328 Err(Error::from_response::<V>(response).await)
329 }
330 }
331
332 pub fn patch_via_fhir<'a>(
335 &self,
336 resource_type: V::ResourceType,
337 id: &'a str,
338 ) -> PatchViaFhir<'a, V> {
339 PatchViaFhir::new(self.clone(), resource_type, id)
340 }
341
342 pub fn patch_via_json<'a>(
345 &self,
346 resource_type: V::ResourceType,
347 id: &'a str,
348 ) -> PatchViaJson<'a, V> {
349 PatchViaJson::new(self.clone(), resource_type, id)
350 }
351
352 pub fn batch(&self) -> BatchTransaction<V> {
354 BatchTransaction::new(self.clone(), false)
355 }
356
357 pub fn transaction(&self) -> BatchTransaction<V> {
359 BatchTransaction::new(self.clone(), true)
360 }
361}