Skip to content

Commit

Permalink
http_interop: Implement Request conversion for http::request::Parts
Browse files Browse the repository at this point in the history
I found the current conversion for `http::request::Builder` to be
rather useless when the `http` crate and various crates providing `http`
interfaces like `oauth2` are designed to provide an `http::Request`
directly, and there being no way to convert from a `http::Request`
back into its `http::request::Builder`.  That, together with strange
infallible defaults instead of providing  `TryFrom` make the current
implementation cumbersome to use.

Fortunately `http` provides `http::Request::into_parts()` to get
back a `Parts` structure (which is wrapped in `Result<>` inside
`http::request::Builder` as sole member!) together with the request body
(a generic type) which the user can manually pass to `send_string()`,
`send_bytes()` or `call()` if there's no data.

Implement a `From<http::request::Parts> for ureq::Request` to support
this case, making `ureq` finally capable of sending `http::Request`s.
(Note that, despite exclusively consisting of `Result<Parts>`,
 `http::request::Builder` has no constructor from `Ok(Parts {..})` which
 would have also facilitated this use-case somewhat)
  • Loading branch information
MarijnS95 authored and algesten committed Oct 17, 2023
1 parent 5082bcc commit 3343981
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 5 deletions.
2 changes: 2 additions & 0 deletions FUTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Replace `impl From<http::request::Builder> for Request` with `TryFrom` because the conversion is fallible
(implement in terms of `From<http::request::Parts>`: `builder.body(())?.into_parts().0.into()`);
78 changes: 73 additions & 5 deletions src/http_interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ impl From<Response> for http::Response<Vec<u8>> {

/// Converts an [`http::request::Builder`] into a [`Request`].
///
/// This will safely handle cases where a builder is not fully "complete" to prevent the conversion
/// from failing. Should the requests' method or URI not be correctly set, the request will default
/// to being a `GET` request to `"https://example.com"`. Additionally, any non-UTF8 headers will
/// be skipped.
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
Expand All @@ -158,6 +153,44 @@ impl From<Response> for http::Response<Vec<u8>> {
/// # Ok(())
/// # }
/// ```
///
/// # Fallibility
///
/// [`http::request::Builder`] contains a [`Result`] that is normally checked when the builder
/// is "built" into a [`http::Request`]. This [`From`] implementation does _not_ check it
/// however, and returns a `GET` [`Request`] that defaults to `"https://example.com"` in case
/// it contains [`Err`]. In order to test for errors, utilize the provided conversion from
/// [`http::request::Parts`]:
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// ureq::is_test(true);
/// let http_builder = http::Request::builder().method("GET").uri("http://example.com");
/// let request = http_builder.body(()).expect("Builder error"); // Check the error
/// let (parts, ()) = request.into_parts();
/// let request: ureq::Request = parts.into();
//// request.call()?;
/// # Ok(())
/// # }
/// ```
///
/// # Converting from [`http::Request`]
///
/// Notably `ureq` does _not_ implement the conversion from [`http::Request`] because it contains
/// the body of a request together with the actual request data. However, [`http`] provides
/// [`http::Request::into_parts()`] to split out a request into [`http::request::Parts`] and a
/// `body`, for which the conversion _is_ implemented and can be used as follows:
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let http_request = http::Request::builder().method("GET").uri("http://example.com").body(vec![0u8]).unwrap();
/// let (http_parts, body) = http_request.into_parts();
/// let request: ureq::Request = http_parts.into();
/// request.send_bytes(&body)?;
/// # Ok(())
/// # }
/// ```
impl From<http::request::Builder> for Request {
fn from(value: http::request::Builder) -> Self {
let mut new_request = crate::agent().request(
Expand Down Expand Up @@ -186,6 +219,41 @@ impl From<http::request::Builder> for Request {
}
}

/// Converts [`http::request::Parts`] into a [`Request`].
///
/// An [`http::Request`] can be split out into its [`http::request::Parts`] and body as follows:
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let http_request = http::Request::builder().method("GET").uri("http://example.com").body(vec![0u8]).unwrap();
/// let (http_parts, body) = http_request.into_parts();
/// let request: ureq::Request = http_parts.into();
/// request.send_bytes(&body)?;
/// # Ok(())
/// # }
/// ```
impl From<http::request::Parts> for Request {
fn from(value: http::request::Parts) -> Self {
let mut new_request = crate::agent().request(value.method.as_str(), &value.uri.to_string());

for (name, value) in &value.headers {
// TODO: Aren't complete header values available as raw byte slices?
let mut raw_header: Vec<u8> = name.to_string().into_bytes();
raw_header.extend(b": ");
raw_header.extend(value.as_bytes());

let header = HeaderLine::from(raw_header)
.into_header()
.expect("Unreachable");

crate::header::add_header(&mut new_request.headers, header)
}

new_request
}
}

/// Converts a [`Request`] into an [`http::request::Builder`].
///
/// This will only convert valid UTF-8 header values into headers on the resulting builder. The
Expand Down

0 comments on commit 3343981

Please sign in to comment.