Skip to main content

web_faith/request/
target.rs

1//! A request's target.
2
3use bytes::Bytes;
4use url::Url;
5
6use crate::{
7	error::{FaithError, FaithErrorKind},
8	request::{Request, RequestBody, RequestOptions},
9};
10
11/// A request's target.
12///
13/// Anything that converts into a `url::Url`, or a [`Request`] to layer over, which lets a prepared
14/// request be adjusted at each call site.
15// spec:REQ
16pub enum Target {
17	/// A URL.
18	Url(Url),
19	/// A prepared request, to layer over.
20	Request(Box<Request>),
21}
22
23impl From<Url> for Target {
24	fn from(url: Url) -> Self {
25		Self::Url(url)
26	}
27}
28
29impl From<Request> for Target {
30	fn from(request: Request) -> Self {
31		Self::Request(Box::new(request))
32	}
33}
34
35/// A string target is parsed when the builder resolves, so an unparseable one is reported there
36/// rather than by a call that cannot fail.
37// spec:REQ
38impl TryFrom<&str> for Target {
39	type Error = FaithError;
40
41	fn try_from(url: &str) -> Result<Self, Self::Error> {
42		Url::parse(url)
43			.map(Self::Url)
44			.map_err(|_| FaithErrorKind::InvalidUrl.into())
45	}
46}
47
48impl TryFrom<String> for Target {
49	type Error = FaithError;
50
51	fn try_from(url: String) -> Result<Self, Self::Error> {
52		Self::try_from(url.as_str())
53	}
54}
55
56/// An `http::Request` is a target too, so a request built against the wider ecosystem can be sent
57/// on an agent: its method, URL, headers, and body come across.
58// spec:REQ
59impl<B> TryFrom<http::Request<B>> for Target
60where
61	B: Into<Bytes>,
62{
63	type Error = FaithError;
64
65	fn try_from(request: http::Request<B>) -> Result<Self, Self::Error> {
66		let (parts, body) = request.into_parts();
67
68		let url = Url::parse(&parts.uri.to_string())
69			.map_err(|_| FaithError::from(FaithErrorKind::InvalidUrl))?;
70
71		let mut headers = Vec::new();
72		for (name, value) in parts.headers.iter() {
73			let Ok(value) = value.to_str() else {
74				return Err(FaithErrorKind::InvalidHeader.into());
75			};
76			headers.push((name.to_string(), value.to_owned()));
77		}
78
79		let body = body.into();
80		Ok(Self::Request(Box::new(Request {
81			url,
82			options: RequestOptions {
83				method: Some(parts.method.to_string()),
84				headers: (!headers.is_empty()).then_some(headers),
85				..RequestOptions::default()
86			},
87			body: if body.is_empty() {
88				RequestBody::None
89			} else {
90				RequestBody::Bytes(body)
91			},
92		})))
93	}
94}