1use 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#[derive(Clone, Debug)]
29struct HeaderOp {
30 name: String,
31 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#[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 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 pub fn url(&self) -> &Url {
71 &self.url
72 }
73
74 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
93struct Layer {
95 target: Result<Target, FaithError>,
98 options: RequestOptions,
99 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 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 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 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
193macro_rules! layer_setters {
195 ($builder:ident) => {
196 impl $builder {
197 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 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 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 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 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
265 self.layer.body = Some(RequestBody::Bytes(body.into()));
266 self
267 }
268
269 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 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 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 #[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 #[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 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 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
333pub enum Priority {
334 High,
336 Low,
338 #[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#[must_use = "a request builder does nothing until built"]
355pub struct RequestBuilder {
356 layer: Layer,
357}
358
359layer_setters!(RequestBuilder);
360
361impl RequestBuilder {
362 pub fn build(self) -> Result<Request, FaithError> {
366 self.layer.settle()
367 }
368}
369
370#[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 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 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 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;