From 925da0faa7c0c746ad814323eaeb4471a3036851 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Thu, 14 Aug 2025 16:35:41 +0800 Subject: [PATCH 1/5] feat(response): preserve URL when converting `Response` to `http::Response` --- src/async_impl/response.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/async_impl/response.rs b/src/async_impl/response.rs index 4c0d52727..d891ccd94 100644 --- a/src/async_impl/response.rs +++ b/src/async_impl/response.rs @@ -484,16 +484,20 @@ impl> From> for Response { // It's supposed to be the inverse of the conversion above. impl From for http::Response { fn from(r: Response) -> http::Response { + use crate::response::ResponseUrl; + let (parts, body) = r.res.into_parts(); let body = Body::wrap(body); - http::Response::from_parts(parts, body) + let mut response = http::Response::from_parts(parts, body); + response.extensions_mut().insert(ResponseUrl(*r.url)); + response } } #[cfg(test)] mod tests { use super::Response; - use crate::ResponseBuilderExt; + use crate::{response::ResponseUrl, ResponseBuilderExt}; use http::response::Builder; use url::Url; @@ -510,4 +514,26 @@ mod tests { assert_eq!(response.status(), 200); assert_eq!(*response.url(), url); } + + #[test] + fn test_from_http_response_with_url() { + let url = Url::parse("http://example.com").unwrap(); + let response = Builder::new() + .status(200) + .url(url.clone()) + .body("foo") + .unwrap(); + let response = Response::from(response); + + assert_eq!(response.status(), 200); + assert_eq!(*response.url(), url); + + let mut http_response = http::Response::from(response); + let resp_url = http_response + .extensions_mut() + .remove::() + .expect("ResponseUrl should be present"); + assert_eq!(http_response.status(), 200); + assert_eq!(resp_url.0, url); + } } From ee685891bd4eb88a661e091863a8cedda54cb204 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Thu, 14 Aug 2025 16:55:21 +0800 Subject: [PATCH 2/5] update --- src/async_impl/response.rs | 9 +++------ src/lib.rs | 2 +- src/response.rs | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/async_impl/response.rs b/src/async_impl/response.rs index d891ccd94..09fc7942f 100644 --- a/src/async_impl/response.rs +++ b/src/async_impl/response.rs @@ -497,7 +497,7 @@ impl From for http::Response { #[cfg(test)] mod tests { use super::Response; - use crate::{response::ResponseUrl, ResponseBuilderExt}; + use crate::{ResponseBuilderExt, ResponseExt}; use http::response::Builder; use url::Url; @@ -529,11 +529,8 @@ mod tests { assert_eq!(*response.url(), url); let mut http_response = http::Response::from(response); - let resp_url = http_response - .extensions_mut() - .remove::() - .expect("ResponseUrl should be present"); + let resp_url = http_response.url(); assert_eq!(http_response.status(), 200); - assert_eq!(resp_url.0, url); + assert_eq!(resp_url, Some(url)); } } diff --git a/src/lib.rs b/src/lib.rs index 99a7efc54..6c9f74bb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -291,7 +291,7 @@ mod response; pub use self::error::{Error, Result}; pub use self::into_url::IntoUrl; -pub use self::response::ResponseBuilderExt; +pub use self::response::{ResponseBuilderExt, ResponseExt}; /// Shortcut method to quickly make a `GET` request. /// diff --git a/src/response.rs b/src/response.rs index 9c92cba53..5b4d2a289 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,5 +1,7 @@ use url::Url; +use crate::Body; + #[derive(Debug, Clone, PartialEq)] pub(crate) struct ResponseUrl(pub Url); @@ -12,12 +14,26 @@ pub trait ResponseBuilderExt { fn url(self, url: Url) -> Self; } +/// Extension trait for http::Response objects +/// +/// Provides methods to extract URL information from HTTP responses +pub trait ResponseExt { + /// Extracts and removes the URL associated with this response + fn url(&mut self) -> Option; +} + impl ResponseBuilderExt for http::response::Builder { fn url(self, url: Url) -> Self { self.extension(ResponseUrl(url)) } } +impl ResponseExt for http::Response { + fn url(&mut self) -> Option { + self.extensions_mut().remove::().map(|r| r.0) + } +} + #[cfg(test)] mod tests { use super::{ResponseBuilderExt, ResponseUrl}; From 2a4facb70f15af47ab6f3322def846101ad9bd9c Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Thu, 14 Aug 2025 17:59:27 +0800 Subject: [PATCH 3/5] add test --- src/async_impl/response.rs | 4 ++-- src/response.rs | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/async_impl/response.rs b/src/async_impl/response.rs index 09fc7942f..73b37cfc2 100644 --- a/src/async_impl/response.rs +++ b/src/async_impl/response.rs @@ -528,9 +528,9 @@ mod tests { assert_eq!(response.status(), 200); assert_eq!(*response.url(), url); - let mut http_response = http::Response::from(response); + let http_response = http::Response::from(response); let resp_url = http_response.url(); assert_eq!(http_response.status(), 200); - assert_eq!(resp_url, Some(url)); + assert_eq!(resp_url, Some(&url)); } } diff --git a/src/response.rs b/src/response.rs index 5b4d2a289..eb7c7acd1 100644 --- a/src/response.rs +++ b/src/response.rs @@ -18,8 +18,8 @@ pub trait ResponseBuilderExt { /// /// Provides methods to extract URL information from HTTP responses pub trait ResponseExt { - /// Extracts and removes the URL associated with this response - fn url(&mut self) -> Option; + /// Returns a reference to the `Url` associated with this response, if available. + fn url(&self) -> Option<&Url>; } impl ResponseBuilderExt for http::response::Builder { @@ -29,14 +29,14 @@ impl ResponseBuilderExt for http::response::Builder { } impl ResponseExt for http::Response { - fn url(&mut self) -> Option { - self.extensions_mut().remove::().map(|r| r.0) + fn url(&self) -> Option<&Url> { + self.extensions().get::().map(|r| &r.0) } } #[cfg(test)] mod tests { - use super::{ResponseBuilderExt, ResponseUrl}; + use super::{ResponseBuilderExt, ResponseExt, ResponseUrl}; use http::response::Builder; use url::Url; @@ -54,4 +54,16 @@ mod tests { Some(&ResponseUrl(url)) ); } + + #[test] + fn test_response_ext() { + let url = Url::parse("http://example.com").unwrap(); + let response = http::Response::builder() + .status(200) + .extension(ResponseUrl(url.clone())) + .body(crate::Body::empty()) + .unwrap(); + + assert_eq!(response.url(), Some(&url)); + } } From 194ebbfadf9495021af9e51a25a4924de929067f Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Thu, 14 Aug 2025 18:19:12 +0800 Subject: [PATCH 4/5] fix build --- src/response.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response.rs b/src/response.rs index eb7c7acd1..544f33103 100644 --- a/src/response.rs +++ b/src/response.rs @@ -55,6 +55,7 @@ mod tests { ); } + #[cfg(not(target_arch = "wasm32"))] #[test] fn test_response_ext() { let url = Url::parse("http://example.com").unwrap(); From 4c6fc05449a946c02f883de3f13e01737af93b72 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Thu, 14 Aug 2025 18:34:58 +0800 Subject: [PATCH 5/5] fix build warning --- src/response.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/response.rs b/src/response.rs index 544f33103..ec1b6e3f1 100644 --- a/src/response.rs +++ b/src/response.rs @@ -36,7 +36,7 @@ impl ResponseExt for http::Response { #[cfg(test)] mod tests { - use super::{ResponseBuilderExt, ResponseExt, ResponseUrl}; + use super::{ResponseBuilderExt, ResponseUrl}; use http::response::Builder; use url::Url; @@ -58,6 +58,7 @@ mod tests { #[cfg(not(target_arch = "wasm32"))] #[test] fn test_response_ext() { + use super::ResponseExt; let url = Url::parse("http://example.com").unwrap(); let response = http::Response::builder() .status(200)