Skip to main content

web_faith/request/
builder.rs

1//! The request and fetch builders.
2
3use std::{
4	future::{Future, IntoFuture},
5	pin::Pin,
6	time::Duration,
7};
8
9use bytes::Bytes;
10use futures::Stream;
11
12#[cfg(feature = "cache")]
13use http_cache_reqwest::CacheMode;
14use reqwest::{
15	Method,
16	header::{HeaderName, HeaderValue},
17};
18use url::Url;
19
20use crate::{
21	agent::Agent,
22	error::{FaithError, FaithErrorKind},
23	request::{Credentials, RequestBody, RequestOptions, Target, send::send},
24	response::Response,
25};
26
27/// A header the caller set or removed on one layer.
28#[derive(Clone, Debug)]
29struct HeaderOp {
30	name: String,
31	/// `None` removes the name, including whatever the layers beneath contributed for it.
32	value: Option<String>,
33}
34
35impl std::fmt::Debug for RequestBody {
36	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37		match self {
38			Self::None => f.write_str("None"),
39			Self::Bytes(bytes) => f.debug_tuple("Bytes").field(&bytes.len()).finish(),
40			Self::Stream(_) => f.write_str("Stream"),
41		}
42	}
43}
44
45/// A request prepared but not sent.
46///
47/// Carries no agent, so it can be sent on more than one. Passing it to [`Agent::fetch`] returns a
48/// builder that layers over it.
49// spec:REQ
50#[derive(Debug)]
51pub struct Request {
52	pub(super) url: Url,
53	pub(super) options: RequestOptions,
54	pub(super) body: RequestBody,
55}
56
57impl Request {
58	/// Prepare a request aimed at `target`.
59	pub fn new<T>(target: T) -> RequestBuilder
60	where
61		T: TryInto<Target>,
62		T::Error: Into<FaithError>,
63	{
64		RequestBuilder {
65			layer: Layer::over(target),
66		}
67	}
68
69	/// The URL this request is aimed at.
70	pub fn url(&self) -> &Url {
71		&self.url
72	}
73
74	/// Copy the request, when its body allows it.
75	///
76	/// `None` when the body is a stream, which can only be consumed once.
77	// spec:REQ
78	pub fn try_clone(&self) -> Option<Self> {
79		let body = match &self.body {
80			RequestBody::None => RequestBody::None,
81			RequestBody::Bytes(bytes) => RequestBody::Bytes(bytes.clone()),
82			RequestBody::Stream(_) => return None,
83		};
84
85		Some(Self {
86			url: self.url.clone(),
87			options: self.options.clone(),
88			body,
89		})
90	}
91}
92
93/// One layer of settings over a target, which both builders are made of.
94struct Layer {
95	/// The first failure met while setting up, held until the builder resolves.
96	// spec:REQ
97	target: Result<Target, FaithError>,
98	options: RequestOptions,
99	/// Which single-valued settings this layer set explicitly, so an unset one is inherited rather
100	/// than overwritten with a default.
101	set: SetFlags,
102	headers: Vec<HeaderOp>,
103	body: Option<RequestBody>,
104}
105
106#[derive(Default)]
107struct SetFlags {
108	#[cfg(feature = "cache")]
109	cache: bool,
110	#[cfg(feature = "encoding")]
111	compress: bool,
112	credentials: bool,
113	integrity: bool,
114	method: bool,
115	priority: bool,
116	timeout: bool,
117}
118
119impl Layer {
120	/// Hold a failure until the builder resolves, keeping the first one met.
121	// spec:REQ
122	fn fail(&mut self, err: FaithError) {
123		if self.target.is_ok() {
124			self.target = Err(err);
125		}
126	}
127
128	fn over<T>(target: T) -> Self
129	where
130		T: TryInto<Target>,
131		T::Error: Into<FaithError>,
132	{
133		Self {
134			target: target.try_into().map_err(Into::into),
135			options: RequestOptions::default(),
136			set: SetFlags::default(),
137			headers: Vec::new(),
138			body: None,
139		}
140	}
141
142	/// Settle this layer over whatever it wraps: the outermost explicit value wins, a setting this
143	/// layer does not touch is inherited unchanged, and headers merge by name.
144	// spec:REQ
145	fn settle(self) -> Result<Request, FaithError> {
146		let (url, mut options, body) = match self.target? {
147			Target::Url(url) => (url, RequestOptions::default(), RequestBody::None),
148			// The URL comes from the target at the bottom of the stack.
149			Target::Request(inner) => (inner.url, inner.options, inner.body),
150		};
151
152		#[cfg(feature = "cache")]
153		if self.set.cache {
154			options.cache = self.options.cache;
155		}
156		#[cfg(feature = "encoding")]
157		if self.set.compress {
158			options.compress = self.options.compress;
159		}
160		if self.set.credentials {
161			options.credentials = self.options.credentials;
162		}
163		if self.set.integrity {
164			options.integrity = self.options.integrity;
165		}
166		if self.set.method {
167			options.method = self.options.method;
168		}
169		if self.set.priority {
170			options.priority = self.options.priority;
171		}
172		if self.set.timeout {
173			options.timeout = self.options.timeout;
174		}
175
176		let mut headers = options.headers.take().unwrap_or_default();
177		for op in self.headers {
178			headers.retain(|(name, _)| !name.eq_ignore_ascii_case(&op.name));
179			if let Some(value) = op.value {
180				headers.push((op.name, value));
181			}
182		}
183		options.headers = (!headers.is_empty()).then_some(headers);
184
185		Ok(Request {
186			url,
187			options,
188			body: self.body.unwrap_or(body),
189		})
190	}
191}
192
193/// The setters both builders carry, so a call reads the same way whichever it is written against.
194macro_rules! layer_setters {
195	($builder:ident) => {
196		impl $builder {
197			/// The request method. Defaults to `GET`.
198			///
199			/// Takes a `Method` or anything that converts into one; a value that does not is
200			/// reported where the builder resolves.
201			pub fn method<M>(mut self, method: M) -> Self
202			where
203				M: TryInto<Method>,
204			{
205				match method.try_into() {
206					Ok(method) => {
207						self.layer.options.method = Some(method.to_string());
208						self.layer.set.method = true;
209					}
210					Err(_) => self.layer.fail(FaithErrorKind::InvalidMethod.into()),
211				}
212				self
213			}
214
215			/// Set a header, replacing whatever any layer beneath contributed for that name.
216			///
217			/// Takes `HeaderName` and `HeaderValue` or anything that converts into them.
218			pub fn header<N, V>(mut self, name: N, value: V) -> Self
219			where
220				N: TryInto<HeaderName>,
221				V: TryInto<HeaderValue>,
222			{
223				match (name.try_into(), value.try_into()) {
224					(Ok(name), Ok(value)) => match value.to_str() {
225						Ok(value) => self.layer.headers.push(HeaderOp {
226							name: name.to_string(),
227							value: Some(value.to_owned()),
228						}),
229						Err(_) => self.layer.fail(FaithErrorKind::InvalidHeader.into()),
230					},
231					_ => self.layer.fail(FaithErrorKind::InvalidHeader.into()),
232				}
233				self
234			}
235
236			/// Set several headers, each applied as a single header would be.
237			pub fn headers<N, V>(mut self, headers: impl IntoIterator<Item = (N, V)>) -> Self
238			where
239				N: TryInto<HeaderName>,
240				V: TryInto<HeaderValue>,
241			{
242				for (name, value) in headers {
243					self = self.header(name, value);
244				}
245				self
246			}
247
248			/// Remove a header, including whatever the layers beneath contributed for it.
249			pub fn remove_header<N>(mut self, name: N) -> Self
250			where
251				N: TryInto<HeaderName>,
252			{
253				match name.try_into() {
254					Ok(name) => self.layer.headers.push(HeaderOp {
255						name: name.to_string(),
256						value: None,
257					}),
258					Err(_) => self.layer.fail(FaithErrorKind::InvalidHeader.into()),
259				}
260				self
261			}
262
263			/// A body already in hand.
264			pub fn body(mut self, body: impl Into<Bytes>) -> Self {
265				self.layer.body = Some(RequestBody::Bytes(body.into()));
266				self
267			}
268
269			/// A body arriving in chunks, which goes out chunked having no length to declare.
270			pub fn body_stream(
271				mut self,
272				body: impl Stream<Item = std::io::Result<Bytes>> + Send + 'static,
273			) -> Self {
274				self.layer.body = Some(RequestBody::Stream(Box::pin(body)));
275				self
276			}
277
278			/// Bound the whole request and response.
279			// spec:CANCEL
280			pub fn timeout(mut self, timeout: Duration) -> Self {
281				self.layer.options.timeout = Some(timeout);
282				self.layer.set.timeout = true;
283				self
284			}
285
286			/// The digests the response body is expected to match.
287			// spec:SRI
288			pub fn integrity(mut self, integrity: impl Into<String>) -> Self {
289				self.layer.options.integrity = Some(integrity.into());
290				self.layer.set.integrity = true;
291				self
292			}
293
294			/// Compress the request body in this coding, named by its wire token.
295			// spec:ENC
296			#[cfg(feature = "encoding")]
297			pub fn compress(mut self, coding: impl Into<String>) -> Self {
298				self.layer.options.compress = Some(coding.into());
299				self.layer.set.compress = true;
300				self
301			}
302
303			/// How the HTTP cache is consulted for this request.
304			// spec:CACHE
305			#[cfg(feature = "cache")]
306			pub fn cache(mut self, mode: CacheMode) -> Self {
307				self.layer.options.cache = mode;
308				self.layer.set.cache = true;
309				self
310			}
311
312			/// Whether the request carries its credentials.
313			pub fn credentials(mut self, credentials: Credentials) -> Self {
314				self.layer.options.credentials = credentials;
315				self.layer.set.credentials = true;
316				self
317			}
318
319			/// How this request ranks against others, as an RFC 9218 urgency.
320			// spec:REQ#request-priority
321			pub fn priority(mut self, priority: Priority) -> Self {
322				self.layer.options.priority = priority.urgency();
323				self.layer.set.priority = true;
324				self
325			}
326		}
327	};
328}
329
330/// How a request ranks against others.
331// spec:REQ#request-priority
332#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
333pub enum Priority {
334	/// More urgent than an unmarked request.
335	High,
336	/// Less urgent than an unmarked request.
337	Low,
338	/// Send no `Priority` header, leaving the request at the scheme's default urgency.
339	#[default]
340	Auto,
341}
342
343impl Priority {
344	fn urgency(self) -> Option<&'static str> {
345		match self {
346			Self::High => Some("u=1"),
347			Self::Low => Some("u=5"),
348			Self::Auto => None,
349		}
350	}
351}
352
353/// Prepares a [`Request`] without sending it.
354#[must_use = "a request builder does nothing until built"]
355pub struct RequestBuilder {
356	layer: Layer,
357}
358
359layer_setters!(RequestBuilder);
360
361impl RequestBuilder {
362	/// Settle the layers into a request.
363	///
364	/// A conversion that failed on the way in — an unparseable target, say — is reported here.
365	pub fn build(self) -> Result<Request, FaithError> {
366		self.layer.settle()
367	}
368}
369
370/// A request bound to an agent, which sends when awaited.
371///
372/// Awaiting is the send; there is no separate step. Dropped without being awaited it sends nothing,
373/// and dropping the future mid-flight cancels the request.
374// spec:REQ spec:CANCEL
375#[must_use = "a fetch builder sends nothing until awaited"]
376pub struct FetchBuilder {
377	agent: Agent,
378	layer: Layer,
379}
380
381layer_setters!(FetchBuilder);
382
383impl FetchBuilder {
384	/// Settle the layers into the request that would be sent, without sending it.
385	pub fn build(self) -> Result<Request, FaithError> {
386		self.layer.settle()
387	}
388}
389
390impl IntoFuture for FetchBuilder {
391	type Output = Result<Response, FaithError>;
392	type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
393
394	fn into_future(self) -> Self::IntoFuture {
395		Box::pin(async move {
396			// Taken before anything is awaited, so the request counts as in flight from here.
397			// spec:AGENT
398			let client = self.agent.client().ok_or(FaithErrorKind::Closed)?;
399			let request = self.layer.settle()?;
400
401			send(
402				&self.agent,
403				client,
404				request.url.as_str(),
405				request.options,
406				request.body,
407				None::<std::future::Pending<()>>,
408			)
409			.await
410		})
411	}
412}
413
414impl Agent {
415	/// Aim a request at `target`, to send when awaited.
416	///
417	/// The target is a URL, or a [`Request`] to layer over. See [`FetchBuilder`].
418	// spec:REQ
419	pub fn fetch<T>(&self, target: T) -> FetchBuilder
420	where
421		T: TryInto<Target>,
422		T::Error: Into<FaithError>,
423	{
424		FetchBuilder {
425			agent: self.clone(),
426			layer: Layer::over(target),
427		}
428	}
429}
430
431#[cfg(test)]
432#[cfg(test)]
433mod tests;