fhir_sdk/client/
error.rs

1//! Client errors.
2
3#[cfg(feature = "r4b")]
4use fhir_model::r4b;
5#[cfg(feature = "r5")]
6use fhir_model::r5;
7#[cfg(feature = "stu3")]
8use fhir_model::stu3;
9use reqwest::StatusCode;
10use thiserror::Error;
11
12use crate::version::FhirVersion;
13
14/// FHIR REST Client Error.
15#[derive(Debug, Error)]
16pub enum Error {
17	/// Builder is missing a field to construct the client.
18	#[error("Builder is missing field `{0}` to construct the client")]
19	BuilderMissingField(&'static str),
20
21	/// URL cannot be a base URL.
22	#[error("Given base URL cannot be a base URL")]
23	UrlCannotBeBase,
24
25	/// Failed parsing an URL.
26	#[error("Failed parsing the URL: {0}")]
27	UrlParse(String),
28
29	/// Missing resource ID.
30	#[error("Resource is missing ID")]
31	MissingId,
32
33	/// Missing resource version ID.
34	#[error("Resource is missing version ID")]
35	MissingVersionId,
36
37	/// Missing reference field in FHIR reference.
38	#[error("Missing reference URL in reference")]
39	MissingReference,
40
41	/// Reference was to local resource.
42	#[error("Tried to fetch local reference")]
43	LocalReference,
44
45	/// Request was not clonable.
46	#[error("Was not able to clone HTTP Request")]
47	RequestNotClone,
48
49	/// Found URL with unexpected origin.
50	#[error("URLs with mismatching origins are disabled: {0}")]
51	DifferentOrigin(String),
52
53	/// Auth callback error.
54	#[error("Authorization callback error: {0}")]
55	AuthCallback(String),
56
57	/// Serialization/Deserialization error.
58	#[error("JSON error: {0}")]
59	Json(#[from] serde_json::Error),
60
61	/// HTTP Request error.
62	#[error("Request error: {0}")]
63	Request(#[from] reqwest::Error),
64
65	/// HTTP error response.
66	#[error("Got error response ({0}): {1}")]
67	Response(StatusCode, String),
68
69	/// Mismatching FHIR version in response.
70	#[error("Server responded with mismatching major FHIR version: {0}")]
71	DifferentFhirVersion(String),
72
73	#[cfg(feature = "stu3")]
74	/// OperationOutcome.
75	#[error("OperationOutcome({0}): {1:?}")]
76	OperationOutcomeStu3(StatusCode, stu3::resources::OperationOutcome),
77
78	#[cfg(feature = "r4b")]
79	/// OperationOutcome.
80	#[error("OperationOutcome({0}): {1:?}")]
81	OperationOutcomeR4B(StatusCode, r4b::resources::OperationOutcome),
82
83	#[cfg(feature = "r5")]
84	/// OperationOutcome.
85	#[error("OperationOutcome({0}): {1:?}")]
86	OperationOutcomeR5(StatusCode, r5::resources::OperationOutcome),
87
88	/// Resource was not found.
89	#[error("Resource `{0}` was not found")]
90	ResourceNotFound(String),
91
92	/// Failed to parse header value due to non-ASCII value content.
93	#[error("Failed to parse header value: {0}")]
94	HeaderParsing(#[from] reqwest::header::ToStrError),
95
96	/// Error parsing ETag to version ID, i.e. missing ETag or wrong format.
97	#[error("Missing or wrong ETag in response: {0}")]
98	EtagFailure(String),
99
100	/// Response did not provide `Location` header or it failed to parse.
101	#[error("Missing or wrong Location header in response: {0}")]
102	LocationFailure(String),
103
104	/// Wrong resource was delivered.
105	#[error("Resource type {0} is not the requested type {1}")]
106	WrongResourceType(String, String),
107}
108
109impl Error {
110	/// Whether the error should likely be retried.
111	#[must_use]
112	pub fn should_retry(&self) -> bool {
113		tracing::debug!("Checking if error `{self}` should be retried");
114		match self {
115			Self::Request(err) => err.is_connect() || err.is_request() || err.is_timeout(),
116			_ => false,
117		}
118	}
119
120	/// Extract the error from a response.
121	pub(crate) async fn from_response<V>(response: reqwest::Response) -> Self
122	where
123		V: FhirVersion,
124		(StatusCode, V::OperationOutcome): Into<Self>,
125	{
126		let status = response.status();
127		let body = response.text().await.unwrap_or_default();
128		if let Ok(outcome) = serde_json::from_str::<V::OperationOutcome>(&body) {
129			(status, outcome).into()
130		} else {
131			Self::Response(status, body)
132		}
133	}
134}
135
136#[cfg(feature = "stu3")]
137impl From<(StatusCode, stu3::resources::OperationOutcome)> for Error {
138	fn from((status, outcome): (StatusCode, stu3::resources::OperationOutcome)) -> Self {
139		Self::OperationOutcomeStu3(status, outcome)
140	}
141}
142
143#[cfg(feature = "r4b")]
144impl From<(StatusCode, r4b::resources::OperationOutcome)> for Error {
145	fn from((status, outcome): (StatusCode, r4b::resources::OperationOutcome)) -> Self {
146		Self::OperationOutcomeR4B(status, outcome)
147	}
148}
149
150#[cfg(feature = "r5")]
151impl From<(StatusCode, r5::resources::OperationOutcome)> for Error {
152	fn from((status, outcome): (StatusCode, r5::resources::OperationOutcome)) -> Self {
153		Self::OperationOutcomeR5(status, outcome)
154	}
155}