qubit_http/request/http_request.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026.
4 * Haixing Hu, Qubit Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9//! Immutable HTTP request object.
10
11use std::sync::RwLock;
12use std::time::Duration;
13
14use bytes::Bytes;
15use futures_util::stream as futures_stream;
16use http::{HeaderMap, HeaderName, HeaderValue, Method};
17use qubit_function::MutatingFunction;
18use reqwest::Response;
19use tokio_util::sync::CancellationToken;
20use url::Host;
21use url::Url;
22
23use crate::client::error_mapper::{map_reqwest_error, ReqwestErrorPhase};
24use crate::{AsyncHeaderInjector, HeaderInjector, HttpError, HttpErrorKind, HttpResult};
25
26use super::http_request_body::HttpRequestBody;
27use super::http_request_builder::HttpRequestBuilder;
28use super::http_request_retry_override::HttpRequestRetryOverride;
29use super::parse_header;
30
31/// Immutable snapshot of a single HTTP call produced by
32/// [`crate::HttpRequestBuilder`].
33#[derive(Debug)]
34pub struct HttpRequest {
35 /// HTTP method (GET, POST, …).
36 method: Method,
37 /// Absolute URL string, or path joined with client `base_url` when not
38 /// parseable as URL.
39 path: String,
40 /// Query string parameters as `(name, value)` pairs.
41 query: Vec<(String, String)>,
42 /// Headers added on top of client defaults and injector output.
43 headers: HeaderMap,
44 /// Serialized body variant.
45 body: HttpRequestBody,
46 /// Overrides client-wide request timeout when set; otherwise client default
47 /// applies.
48 request_timeout: Option<Duration>,
49 /// Per-request write timeout used during request sending.
50 write_timeout: Duration,
51 /// Per-request read timeout used during response body reads.
52 read_timeout: Duration,
53 /// Base URL copied from client options, used to resolve relative `path`.
54 base_url: Option<Url>,
55 /// Lazily maintained cache for the currently resolved URL.
56 resolved_url: RwLock<Option<Url>>,
57 /// Attempt-scoped cache of merged outbound headers after applying
58 /// defaults/injectors/request-local headers.
59 effective_headers: Option<HeaderMap>,
60 /// Whether resolved URLs must avoid IPv6 literal hosts.
61 ipv4_only: bool,
62 /// Optional cancellation token checked before send and during I/O phases.
63 cancellation_token: Option<CancellationToken>,
64 /// Per-request retry override (enable/disable/method-policy/Retry-After
65 /// behavior).
66 retry_override: HttpRequestRetryOverride,
67 /// Client default headers snapshot captured when this request builder was
68 /// created.
69 default_headers: HeaderMap,
70 /// Client sync header injectors snapshot captured when this request builder
71 /// was created.
72 injectors: Vec<HeaderInjector>,
73 /// Client async header injectors snapshot captured when this request
74 /// builder was created.
75 async_injectors: Vec<AsyncHeaderInjector>,
76}
77
78impl HttpRequest {
79 /// Consumes a finished [`HttpRequestBuilder`] and freezes its fields into
80 /// an [`HttpRequest`].
81 ///
82 /// # Parameters
83 /// - `builder`: Populated builder produced by the HTTP client pipeline.
84 ///
85 /// # Returns
86 /// Snapshot ready for URL resolution, header assembly, and sending.
87 pub(super) fn new(builder: HttpRequestBuilder) -> Self {
88 let mut request = Self {
89 method: builder.method,
90 path: builder.path,
91 query: builder.query,
92 headers: builder.headers,
93 body: builder.body,
94 request_timeout: builder.request_timeout,
95 write_timeout: builder.write_timeout,
96 read_timeout: builder.read_timeout,
97 base_url: builder.base_url,
98 resolved_url: RwLock::new(None),
99 effective_headers: None,
100 ipv4_only: builder.ipv4_only,
101 cancellation_token: builder.cancellation_token,
102 retry_override: builder.retry_override,
103 default_headers: builder.default_headers,
104 injectors: builder.injectors,
105 async_injectors: builder.async_injectors,
106 };
107 request.refresh_resolved_url_cache();
108 request
109 }
110
111 /// Returns the HTTP verb for this snapshot.
112 ///
113 /// # Returns
114 /// Borrowed [`Method`] (for example GET or POST).
115 pub fn method(&self) -> &Method {
116 &self.method
117 }
118
119 /// Replaces the HTTP verb.
120 ///
121 /// # Parameters
122 /// - `method`: New [`Method`].
123 ///
124 /// # Returns
125 /// `self` for method chaining.
126 pub fn set_method(&mut self, method: Method) -> &mut Self {
127 self.method = method;
128 self
129 }
130
131 /// Returns the path segment or absolute URL string stored on this request.
132 ///
133 /// # Returns
134 /// The raw path/URL before query string assembly; may be relative if a base
135 /// URL is set.
136 pub fn path(&self) -> &str {
137 &self.path
138 }
139
140 /// Replaces the path or absolute URL string.
141 ///
142 /// # Parameters
143 /// - `path`: New path or URL string (query string is managed separately via
144 /// [`Self::add_query_param`]).
145 ///
146 /// # Returns
147 /// `self` for method chaining.
148 pub fn set_path(&mut self, path: impl Into<String>) -> &mut Self {
149 self.path = path.into();
150 self.refresh_resolved_url_cache();
151 self
152 }
153
154 /// Returns ordered `(name, value)` query pairs that will be appended to the
155 /// resolved URL.
156 ///
157 /// # Returns
158 /// Slice view of accumulated query parameters.
159 pub fn query(&self) -> &[(String, String)] {
160 &self.query
161 }
162
163 /// Appends a single query pair preserving insertion order.
164 ///
165 /// # Parameters
166 /// - `key`: Parameter name.
167 /// - `value`: Parameter value.
168 ///
169 /// # Returns
170 /// `self` for method chaining.
171 pub fn add_query_param(
172 &mut self,
173 key: impl Into<String>,
174 value: impl Into<String>,
175 ) -> &mut Self {
176 self.query.push((key.into(), value.into()));
177 self
178 }
179
180 /// Removes every query pair from this snapshot.
181 ///
182 /// # Returns
183 /// `self` for method chaining.
184 pub fn clear_query_params(&mut self) -> &mut Self {
185 self.query.clear();
186 self
187 }
188
189 /// Returns request-local headers layered on top of client defaults and
190 /// injector output at send time.
191 ///
192 /// # Returns
193 /// Borrowed [`HeaderMap`] owned by this request only (not merged defaults).
194 pub fn headers(&self) -> &HeaderMap {
195 &self.headers
196 }
197
198 /// Parses and inserts one header from string name/value pairs.
199 ///
200 /// # Parameters
201 /// - `name`: Header field name.
202 /// - `value`: Header field value.
203 ///
204 /// # Returns
205 /// `Ok(self)` on success.
206 ///
207 /// # Errors
208 /// Returns [`HttpError`] when name or value cannot be converted into valid
209 /// HTTP tokens.
210 pub fn set_header(&mut self, name: &str, value: &str) -> Result<&mut Self, HttpError> {
211 let (header_name, header_value) = parse_header(name, value)?;
212 self.headers.insert(header_name, header_value);
213 self.invalidate_effective_headers_cache();
214 Ok(self)
215 }
216
217 /// Inserts one header using pre-validated [`HeaderName`] / [`HeaderValue`]
218 /// types.
219 ///
220 /// # Parameters
221 /// - `name`: Typed header name.
222 /// - `value`: Typed header value.
223 ///
224 /// # Returns
225 /// `self` for method chaining.
226 pub fn set_typed_header(&mut self, name: HeaderName, value: HeaderValue) -> &mut Self {
227 self.headers.insert(name, value);
228 self.invalidate_effective_headers_cache();
229 self
230 }
231
232 /// Removes all values for a header field by typed name.
233 ///
234 /// # Parameters
235 /// - `name`: Header name to strip from the request-local map.
236 ///
237 /// # Returns
238 /// `self` for method chaining.
239 pub fn remove_header(&mut self, name: &HeaderName) -> &mut Self {
240 self.headers.remove(name);
241 self.invalidate_effective_headers_cache();
242 self
243 }
244
245 /// Clears all request-local headers (defaults and injectors are unaffected
246 /// until send).
247 ///
248 /// # Returns
249 /// `self` for method chaining.
250 pub fn clear_headers(&mut self) -> &mut Self {
251 self.headers.clear();
252 self.invalidate_effective_headers_cache();
253 self
254 }
255
256 /// Returns the serialized body variant for this snapshot.
257 ///
258 /// # Returns
259 /// Borrowed [`HttpRequestBody`].
260 pub fn body(&self) -> &HttpRequestBody {
261 &self.body
262 }
263
264 /// Replaces the entire body payload.
265 ///
266 /// # Parameters
267 /// - `body`: New [`HttpRequestBody`] variant.
268 ///
269 /// # Returns
270 /// `self` for method chaining.
271 pub fn set_body(&mut self, body: HttpRequestBody) -> &mut Self {
272 self.body = body;
273 self
274 }
275
276 /// Returns the per-request total timeout, if any.
277 ///
278 /// # Returns
279 /// `Some(duration)` when a request-specific timeout overrides the client
280 /// default; otherwise `None`.
281 pub fn request_timeout(&self) -> Option<Duration> {
282 self.request_timeout
283 }
284
285 /// Sets a per-request total timeout that overrides the client default for
286 /// this send.
287 ///
288 /// # Parameters
289 /// - `timeout`: Upper bound for the entire request lifecycle handled by
290 /// reqwest.
291 ///
292 /// # Returns
293 /// `self` for method chaining.
294 pub fn set_request_timeout(&mut self, timeout: Duration) -> &mut Self {
295 self.request_timeout = Some(timeout);
296 self
297 }
298
299 /// Drops the per-request timeout so the client-wide default applies again.
300 ///
301 /// # Returns
302 /// `self` for method chaining.
303 pub fn clear_request_timeout(&mut self) -> &mut Self {
304 self.request_timeout = None;
305 self
306 }
307
308 /// Returns the write-phase timeout used while sending the request.
309 pub fn write_timeout(&self) -> Duration {
310 self.write_timeout
311 }
312
313 /// Sets the write-phase timeout used while sending the request.
314 pub fn set_write_timeout(&mut self, timeout: Duration) -> &mut Self {
315 self.write_timeout = timeout;
316 self
317 }
318
319 /// Returns the read-phase timeout used while reading response body bytes.
320 pub fn read_timeout(&self) -> Duration {
321 self.read_timeout
322 }
323
324 /// Sets the read-phase timeout used while reading response body bytes.
325 pub fn set_read_timeout(&mut self, timeout: Duration) -> &mut Self {
326 self.read_timeout = timeout;
327 self
328 }
329
330 /// Returns the optional base URL used to resolve relative [`Self::path`]
331 /// values.
332 ///
333 /// # Returns
334 /// `Some` when a base is configured; `None` when only absolute URLs in
335 /// `path` are valid.
336 pub fn base_url(&self) -> Option<&Url> {
337 self.base_url.as_ref()
338 }
339
340 /// Sets the base URL used by [`Self::resolved_url`] when `path` is not
341 /// absolute.
342 ///
343 /// # Parameters
344 /// - `base_url`: Root URL to join against relative paths.
345 ///
346 /// # Returns
347 /// `self` for method chaining.
348 pub fn set_base_url(&mut self, base_url: Url) -> &mut Self {
349 self.base_url = Some(base_url);
350 self.refresh_resolved_url_cache();
351 self
352 }
353
354 /// Removes the configured base URL so relative paths can no longer be
355 /// resolved without resetting it.
356 ///
357 /// # Returns
358 /// `self` for method chaining.
359 pub fn clear_base_url(&mut self) -> &mut Self {
360 self.base_url = None;
361 self.refresh_resolved_url_cache();
362 self
363 }
364
365 /// Returns whether IPv6 literal hosts are rejected after URL resolution.
366 ///
367 /// # Returns
368 /// `true` when a resolved URL whose host is an IPv6 literal must be
369 /// rejected with [`HttpError::invalid_url`].
370 pub fn ipv4_only(&self) -> bool {
371 self.ipv4_only
372 }
373
374 /// Enables or disables IPv6 literal host rejection for resolved URLs.
375 ///
376 /// # Parameters
377 /// - `enabled`: When `true`, resolved URLs whose host is an IPv6 literal
378 /// are errors.
379 ///
380 /// # Returns
381 /// `self` for method chaining.
382 pub fn set_ipv4_only(&mut self, enabled: bool) -> &mut Self {
383 self.ipv4_only = enabled;
384 self.refresh_resolved_url_cache();
385 self
386 }
387
388 /// Returns the cooperative cancellation handle, if configured.
389 ///
390 /// # Returns
391 /// `Some` token checked before send and during I/O; `None` when
392 /// cancellation is not wired.
393 pub fn cancellation_token(&self) -> Option<&CancellationToken> {
394 self.cancellation_token.as_ref()
395 }
396
397 /// Attaches a [`CancellationToken`] that can abort this request
398 /// cooperatively.
399 ///
400 /// # Parameters
401 /// - `token`: Shared cancellation source.
402 ///
403 /// # Returns
404 /// `self` for method chaining.
405 pub fn set_cancellation_token(&mut self, token: CancellationToken) -> &mut Self {
406 self.cancellation_token = Some(token);
407 self
408 }
409
410 /// Removes any cancellation token from this snapshot.
411 ///
412 /// # Returns
413 /// `self` for method chaining.
414 pub fn clear_cancellation_token(&mut self) -> &mut Self {
415 self.cancellation_token = None;
416 self
417 }
418
419 /// Returns the per-request retry override applied by the client pipeline.
420 ///
421 /// # Returns
422 /// Borrowed [`HttpRequestRetryOverride`].
423 pub fn retry_override(&self) -> &HttpRequestRetryOverride {
424 &self.retry_override
425 }
426
427 /// Replaces the retry override for this single request.
428 ///
429 /// # Parameters
430 /// - `retry_override`: New override policy and knobs.
431 ///
432 /// # Returns
433 /// `self` for method chaining.
434 pub fn set_retry_override(&mut self, retry_override: HttpRequestRetryOverride) -> &mut Self {
435 self.retry_override = retry_override;
436 self
437 }
438
439 /// Moves the current body out, leaving [`HttpRequestBody::Empty`] in its
440 /// place.
441 ///
442 /// Used internally before handing the payload to reqwest so the snapshot is
443 /// not cloned twice.
444 ///
445 /// # Returns
446 /// Previous [`HttpRequestBody`] value.
447 pub(crate) fn take_body(&mut self) -> HttpRequestBody {
448 std::mem::replace(&mut self.body, HttpRequestBody::Empty)
449 }
450
451 /// Assembles a reqwest [`RequestBuilder`](reqwest::RequestBuilder), applies
452 /// this snapshot's body, then sends with a bounded write phase.
453 ///
454 /// Centralizes query/timeout/body wiring plus cooperative cancellation and
455 /// write-timeout handling; higher-level retry, logging, and interceptors
456 /// stay in [`crate::HttpClient`].
457 ///
458 /// # Parameters
459 /// - `backend`: Shared reqwest client.
460 ///
461 /// # Returns
462 /// The successful [`Response`] or a mapped [`HttpError`].
463 ///
464 /// # Errors
465 /// - Cooperative cancellation while waiting on the send future.
466 /// - Transport failures mapped from reqwest.
467 /// - Write timeout when the send future does not complete within
468 /// `write_timeout`.
469 pub(crate) async fn send_impl(&mut self, backend: &reqwest::Client) -> HttpResult<Response> {
470 let method = self.method.clone();
471 let url = self.resolved_url()?;
472 let headers = self.effective_headers().await?.clone();
473 let mut builder = backend.request(method.clone(), url.clone());
474 builder = builder.headers(headers);
475 if !self.query.is_empty() {
476 builder = builder.query(self.query.as_slice());
477 }
478 if let Some(timeout) = self.request_timeout {
479 builder = builder.timeout(timeout);
480 }
481 builder = Self::apply_request_body(builder, self.take_body());
482
483 let send_future = tokio::time::timeout(self.write_timeout, builder.send());
484 let next = if let Some(token) = self.cancellation_token.as_ref() {
485 tokio::select! {
486 _ = token.cancelled() => {
487 return Err(HttpError::cancelled("Request cancelled while sending")
488 .with_method(&method)
489 .with_url(&url));
490 }
491 send_result = send_future => send_result,
492 }
493 } else {
494 send_future.await
495 };
496
497 match next {
498 Ok(Ok(response)) => Ok(response),
499 Ok(Err(error)) => Err(map_reqwest_error(
500 error,
501 HttpErrorKind::Transport,
502 Some(ReqwestErrorPhase::Send),
503 Some(method.clone()),
504 Some(url.clone()),
505 )),
506 Err(_) => Err(HttpError::write_timeout(format!(
507 "Write timeout after {:?} while sending request",
508 self.write_timeout
509 ))
510 .with_method(&method)
511 .with_url(&url)),
512 }
513 }
514
515 /// Returns the resolved URL for current request fields, computing and
516 /// caching it on demand.
517 ///
518 /// # Returns
519 /// Resolved [`Url`] value (cloned from cache when already computed).
520 ///
521 /// # Errors
522 /// Returns [`HttpError::invalid_url`] when parsing fails, the base URL is
523 /// missing for a relative path, joining fails, or [`Self::ipv4_only`]
524 /// rejects an IPv6 literal host.
525 pub(crate) fn resolved_url(&self) -> Result<Url, HttpError> {
526 if let Some(url) = self
527 .resolved_url
528 .read()
529 .expect("resolved_url read lock poisoned")
530 .as_ref()
531 {
532 return Ok(url.clone());
533 }
534 let resolved = self.compute_resolved_url()?;
535 *self
536 .resolved_url
537 .write()
538 .expect("resolved_url write lock poisoned") = Some(resolved.clone());
539 Ok(resolved)
540 }
541
542 /// Returns cached resolved URL when available.
543 pub fn resolved_url_cached(&self) -> Option<Url> {
544 self.resolved_url
545 .read()
546 .expect("resolved_url read lock poisoned")
547 .clone()
548 }
549
550 /// Recomputes and stores the current resolved URL.
551 fn refresh_resolved_url_cache(&mut self) {
552 *self
553 .resolved_url
554 .write()
555 .expect("resolved_url write lock poisoned") = self.compute_resolved_url().ok();
556 }
557
558 /// Computes the resolved URL from current path/base/ipv4 settings.
559 fn compute_resolved_url(&self) -> Result<Url, HttpError> {
560 if let Ok(url) = Url::parse(&self.path) {
561 self.validate_resolved_url_host(&url)?;
562 return Ok(url);
563 }
564
565 let base = self.base_url.as_ref().ok_or_else(|| {
566 HttpError::invalid_url(format!(
567 "Cannot resolve relative path '{}' without base_url",
568 self.path
569 ))
570 })?;
571
572 let url = base.join(&self.path).map_err(|error| {
573 HttpError::invalid_url(format!(
574 "Failed to resolve path '{}' against base URL '{}': {}",
575 self.path, base, error
576 ))
577 })?;
578 self.validate_resolved_url_host(&url)?;
579 Ok(url)
580 }
581
582 /// Enforces [`Self::ipv4_only`] by rejecting IPv6 literal hosts in `url`.
583 ///
584 /// # Parameters
585 /// - `url`: Candidate URL after parsing or joining.
586 ///
587 /// # Returns
588 /// `Ok(())` when the host is acceptable.
589 ///
590 /// # Errors
591 /// [`HttpError::invalid_url`] when `ipv4_only` is `true` and the host is an
592 /// IPv6 literal.
593 fn validate_resolved_url_host(&self, url: &Url) -> Result<(), HttpError> {
594 if self.ipv4_only && matches!(url.host(), Some(Host::Ipv6(_))) {
595 return Err(HttpError::invalid_url(format!(
596 "IPv6 literal host is not allowed when ipv4_only=true: {}",
597 url
598 )));
599 }
600 Ok(())
601 }
602
603 /// Returns the attempt-scoped merged outbound headers.
604 ///
605 /// On first call after invalidation, this computes merged headers by
606 /// replaying defaults/injectors/request-local headers and stores them in
607 /// [`Self::effective_headers`]. Later calls in the same attempt return the
608 /// cached map.
609 ///
610 /// Merge order (later wins on duplicates):
611 /// 1. Client default headers snapshot captured when the builder was
612 /// created.
613 /// 2. Synchronous injector output in registration order.
614 /// 3. Asynchronous injector output in registration order.
615 /// 4. Request-local headers from this snapshot.
616 ///
617 /// # Returns
618 /// Borrowed merged [`HeaderMap`] from the cache.
619 ///
620 /// # Errors
621 /// Propagates failures returned by any injector's `apply` implementation.
622 pub(crate) async fn effective_headers(&mut self) -> HttpResult<&HeaderMap> {
623 if self.effective_headers.is_none() {
624 self.effective_headers = Some(self.compute_effective_headers().await?);
625 }
626 Ok(self
627 .effective_headers
628 .as_ref()
629 .expect("effective_headers must exist after successful effective_headers"))
630 }
631
632 /// Returns cached merged outbound headers when available.
633 pub(crate) fn effective_headers_cached(&self) -> Option<&HeaderMap> {
634 self.effective_headers.as_ref()
635 }
636
637 /// Clears the effective-header cache.
638 ///
639 /// This method invalidates [`Self::effective_headers`] so the next call to
640 /// [`Self::effective_headers`] recomputes merged headers by re-running
641 /// defaults and header injectors.
642 ///
643 /// Why this is needed:
644 /// - request-local headers may have been mutated (`set_header`, `clear_headers`, etc.);
645 /// - injector output may be time-sensitive (for example rotating auth token
646 /// or timestamp-based signatures), so each send attempt should recompute
647 /// merged headers instead of reusing stale values from prior attempts.
648 ///
649 /// When to call:
650 /// - immediately before starting a new send attempt;
651 /// - after any mutation that can change final outbound headers.
652 pub(crate) fn invalidate_effective_headers_cache(&mut self) {
653 self.effective_headers = None;
654 }
655
656 /// Computes merged outbound headers without touching the cache.
657 async fn compute_effective_headers(&self) -> HttpResult<HeaderMap> {
658 let mut headers = self.default_headers.clone();
659
660 for injector in &self.injectors {
661 injector.apply(&mut headers)?;
662 }
663 for injector in &self.async_injectors {
664 injector.apply(&mut headers).await?;
665 }
666
667 headers.extend(self.headers.clone());
668 Ok(headers)
669 }
670
671 /// Returns a pre-cancelled [`HttpError`] when a token is present and
672 /// already cancelled.
673 ///
674 /// # Parameters
675 /// - `message`: Human-readable cancellation reason.
676 ///
677 /// # Returns
678 /// `Some` [`HttpError`] (including method context and cached URL when
679 /// available) when a token exists and is already cancelled; otherwise
680 /// `None`.
681 pub(crate) fn cancelled_error_if_needed(&self, message: &str) -> Option<HttpError> {
682 if self
683 .cancellation_token
684 .as_ref()
685 .is_some_and(CancellationToken::is_cancelled)
686 {
687 let mut error = HttpError::cancelled(message.to_string()).with_method(&self.method);
688 if let Ok(url) = self.resolved_url() {
689 error = error.with_url(&url);
690 }
691 Some(error)
692 } else {
693 None
694 }
695 }
696
697 /// Attaches the correct reqwest body encoding for each [`HttpRequestBody`]
698 /// variant.
699 ///
700 /// # Parameters
701 /// - `builder`: Partially configured [`reqwest::RequestBuilder`]
702 /// (method/URL/headers already set).
703 /// - `body`: Payload variant to attach; moved into the builder.
704 ///
705 /// # Returns
706 /// The same builder with an appropriate `.body(...)` applied (or unchanged
707 /// for [`HttpRequestBody::Empty`]).
708 fn apply_request_body(
709 builder: reqwest::RequestBuilder,
710 body: HttpRequestBody,
711 ) -> reqwest::RequestBuilder {
712 match body {
713 HttpRequestBody::Empty => builder,
714 HttpRequestBody::Bytes(bytes)
715 | HttpRequestBody::Json(bytes)
716 | HttpRequestBody::Form(bytes)
717 | HttpRequestBody::Multipart(bytes)
718 | HttpRequestBody::Ndjson(bytes) => builder.body(bytes),
719 HttpRequestBody::Stream(chunks) => {
720 let body_stream = futures_stream::iter(
721 chunks.into_iter().map(Result::<Bytes, std::io::Error>::Ok),
722 );
723 builder.body(reqwest::Body::wrap_stream(body_stream))
724 }
725 HttpRequestBody::Text(text) => builder.body(text),
726 }
727 }
728}
729
730impl Clone for HttpRequest {
731 fn clone(&self) -> Self {
732 Self {
733 method: self.method.clone(),
734 path: self.path.clone(),
735 query: self.query.clone(),
736 headers: self.headers.clone(),
737 body: self.body.clone(),
738 request_timeout: self.request_timeout,
739 write_timeout: self.write_timeout,
740 read_timeout: self.read_timeout,
741 base_url: self.base_url.clone(),
742 resolved_url: RwLock::new(self.resolved_url_cached()),
743 effective_headers: self.effective_headers.clone(),
744 ipv4_only: self.ipv4_only,
745 cancellation_token: self.cancellation_token.clone(),
746 retry_override: self.retry_override.clone(),
747 default_headers: self.default_headers.clone(),
748 injectors: self.injectors.clone(),
749 async_injectors: self.async_injectors.clone(),
750 }
751 }
752}