fhir_sdk/client/
builder.rs

1//! Builder implementation for the client.
2
3use std::marker::PhantomData;
4
5use reqwest::Url;
6
7use super::{Client, Error, LoginManager, RequestSettings, auth::AuthCallback};
8use crate::version::{DefaultVersion, FhirVersion};
9
10/// Default user agent of this client.
11const DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
12
13/// Builder for the [Client]
14pub struct ClientBuilder<Version = DefaultVersion, ACB = ()> {
15	/// The FHIR server's base URL.
16	base_url: Option<Url>,
17	/// Reqwest Client
18	client: Option<reqwest::Client>,
19	/// User agent to use for requests.
20	user_agent: Option<String>,
21	/// Request settings.
22	request_settings: Option<RequestSettings>,
23	/// Auth callback.
24	auth_callback: Option<ACB>,
25
26	/// Whether to error if the server responds with a different major FHIR
27	/// version.
28	error_on_version_mismatch: bool,
29	/// Whether to error before we try to send a request to a different server
30	/// than is configured in the base URL. Also not applies to custom requests!
31	/// Reasoning is to avoid search results and references to resources on other
32	/// servers when this is not wanted.
33	error_on_origin_mismatch: bool,
34
35	/// FHIR version.
36	version: PhantomData<Version>,
37}
38
39impl<V, ACB> Default for ClientBuilder<V, ACB>
40where
41	V: FhirVersion,
42{
43	fn default() -> Self {
44		Self {
45			base_url: None,
46			client: None,
47			user_agent: None,
48			request_settings: None,
49			auth_callback: None,
50			error_on_version_mismatch: true,
51			error_on_origin_mismatch: true,
52			version: PhantomData,
53		}
54	}
55}
56
57impl<V, ACB> ClientBuilder<V, ACB>
58where
59	V: FhirVersion,
60{
61	/// The FHIR server's base URL.
62	#[must_use]
63	pub fn base_url(mut self, base_url: Url) -> Self {
64		self.base_url = Some(base_url);
65		self
66	}
67
68	/// Reqwest client
69	#[must_use]
70	pub fn client(mut self, client: reqwest::Client) -> Self {
71		self.client = Some(client);
72		self
73	}
74
75	/// User agent to use for requests. This is ignored if a Reqwest client is
76	/// passed to the builder using the client() method.
77	#[must_use]
78	pub fn user_agent(mut self, user_agent: String) -> Self {
79		self.user_agent = Some(user_agent);
80		self
81	}
82
83	/// Request settings.
84	#[must_use]
85	pub fn request_settings(mut self, settings: RequestSettings) -> Self {
86		self.request_settings = Some(settings);
87		self
88	}
89
90	/// Set an authorization callback to be called every time the server returns
91	/// unauthorized. Should return the header value for the `Authorization`
92	/// header.
93	///
94	/// Valid login managers are:
95	/// - Async functions `async fn my_auth_callback(client: reqwest::Client) -> Result<HeaderValue,
96	///   MyError>`
97	/// - Async closures `|client: reqwest::Client| async move { ... }`
98	/// - Types that implement [LoginManager]
99	///
100	/// Calling this with unit type `()` will panic on auth_callback called.
101	/// `()` is allowed at compile time for convenience reasons (generics
102	/// stuff).
103	#[must_use]
104	pub fn auth_callback<LM>(self, login_manager: LM) -> ClientBuilder<V, LM>
105	where
106		LM: LoginManager + 'static,
107	{
108		ClientBuilder {
109			base_url: self.base_url,
110			client: self.client,
111			user_agent: self.user_agent,
112			request_settings: self.request_settings,
113			auth_callback: Some(login_manager),
114			version: self.version,
115			error_on_version_mismatch: self.error_on_version_mismatch,
116			error_on_origin_mismatch: self.error_on_origin_mismatch,
117		}
118	}
119
120	/// Disable errors if the server responds with a different major FHIR
121	/// version.
122	#[must_use]
123	pub const fn allow_version_mismatch(mut self) -> Self {
124		self.error_on_version_mismatch = false;
125		self
126	}
127
128	/// Disable errors blocking to send a request to a different server than is
129	/// configured in the base URL. Also not applies to custom requests!
130	/// Reasoning is to avoid search results and references to resources on other
131	/// servers when this is not wanted.
132	#[must_use]
133	pub const fn allow_origin_mismatch(mut self) -> Self {
134		self.error_on_origin_mismatch = false;
135		self
136	}
137
138	/// Finalize building the client.
139	pub fn build(self) -> Result<Client<V>, Error>
140	where
141		ACB: LoginManager + 'static,
142	{
143		let Some(base_url) = self.base_url else {
144			return Err(Error::BuilderMissingField("base_url"));
145		};
146		if base_url.cannot_be_a_base() {
147			return Err(Error::UrlCannotBeBase);
148		}
149
150		let client = match self.client {
151			Some(client) => client,
152			None => {
153				let user_agent = self.user_agent.as_deref().unwrap_or(DEFAULT_USER_AGENT);
154				reqwest::Client::builder().user_agent(user_agent).build()?
155			}
156		};
157
158		let request_settings = self.request_settings.unwrap_or_default();
159
160		let data = super::ClientData {
161			base_url,
162			client,
163			request_settings: std::sync::Mutex::new(request_settings),
164			auth_callback: tokio::sync::Mutex::new(self.auth_callback.map(AuthCallback::new)),
165			error_on_version_mismatch: self.error_on_version_mismatch,
166			error_on_origin_mismatch: self.error_on_origin_mismatch,
167		};
168		Ok(Client::from(data))
169	}
170}
171
172impl<V, ACB> Clone for ClientBuilder<V, ACB>
173where
174	ACB: Clone,
175{
176	fn clone(&self) -> Self {
177		Self {
178			base_url: self.base_url.clone(),
179			client: self.client.clone(),
180			user_agent: self.user_agent.clone(),
181			request_settings: self.request_settings.clone(),
182			auth_callback: self.auth_callback.clone(),
183			version: self.version,
184			error_on_version_mismatch: self.error_on_version_mismatch,
185			error_on_origin_mismatch: self.error_on_origin_mismatch,
186		}
187	}
188}
189
190impl<V, ACB> std::fmt::Debug for ClientBuilder<V, ACB> {
191	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192		f.debug_struct("ClientBuilder")
193			.field("base_url", &self.base_url)
194			.field("client", &self.client)
195			.field("user_agent", &self.user_agent)
196			.field("request_settings", &self.request_settings)
197			.field("auth_callback", &self.auth_callback.as_ref().map(|_| "<login_manager>"))
198			.field("error_on_version_mismatch", &self.error_on_version_mismatch)
199			.field("error_on_origin_mismatch", &self.error_on_origin_mismatch)
200			.field("version", &std::any::type_name::<V>())
201			.finish()
202	}
203}