1#[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#[derive(Debug, Error)]
16pub enum Error {
17 #[error("Builder is missing field `{0}` to construct the client")]
19 BuilderMissingField(&'static str),
20
21 #[error("Given base URL cannot be a base URL")]
23 UrlCannotBeBase,
24
25 #[error("Failed parsing the URL: {0}")]
27 UrlParse(String),
28
29 #[error("Resource is missing ID")]
31 MissingId,
32
33 #[error("Resource is missing version ID")]
35 MissingVersionId,
36
37 #[error("Missing reference URL in reference")]
39 MissingReference,
40
41 #[error("Tried to fetch local reference")]
43 LocalReference,
44
45 #[error("Was not able to clone HTTP Request")]
47 RequestNotClone,
48
49 #[error("URLs with mismatching origins are disabled: {0}")]
51 DifferentOrigin(String),
52
53 #[error("Authorization callback error: {0}")]
55 AuthCallback(String),
56
57 #[error("JSON error: {0}")]
59 Json(#[from] serde_json::Error),
60
61 #[error("Request error: {0}")]
63 Request(#[from] reqwest::Error),
64
65 #[error("Got error response ({0}): {1}")]
67 Response(StatusCode, String),
68
69 #[error("Server responded with mismatching major FHIR version: {0}")]
71 DifferentFhirVersion(String),
72
73 #[cfg(feature = "stu3")]
74 #[error("OperationOutcome({0}): {1:?}")]
76 OperationOutcomeStu3(StatusCode, stu3::resources::OperationOutcome),
77
78 #[cfg(feature = "r4b")]
79 #[error("OperationOutcome({0}): {1:?}")]
81 OperationOutcomeR4B(StatusCode, r4b::resources::OperationOutcome),
82
83 #[cfg(feature = "r5")]
84 #[error("OperationOutcome({0}): {1:?}")]
86 OperationOutcomeR5(StatusCode, r5::resources::OperationOutcome),
87
88 #[error("Resource `{0}` was not found")]
90 ResourceNotFound(String),
91
92 #[error("Failed to parse header value: {0}")]
94 HeaderParsing(#[from] reqwest::header::ToStrError),
95
96 #[error("Missing or wrong ETag in response: {0}")]
98 EtagFailure(String),
99
100 #[error("Missing or wrong Location header in response: {0}")]
102 LocationFailure(String),
103
104 #[error("Resource type {0} is not the requested type {1}")]
106 WrongResourceType(String, String),
107}
108
109impl Error {
110 #[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 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}