From e92bf0b4bbe6339e9b5dcdd921847d946c27b5dc Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Sat, 21 Nov 2020 22:11:15 -0800 Subject: [PATCH] Add ureq::request_url and Agent::request_url. (#226) These let a user pass an already-parsed Url. --- src/agent.rs | 38 +++++++++++++++++++++++++++++++++++--- src/lib.rs | 33 +++++++++++++++++++++++++++++++-- src/request.rs | 43 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 102 insertions(+), 12 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index a55c9ce..5bb9a73 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use url::Url; + use crate::pool::ConnectionPool; use crate::proxy::Proxy; use crate::request::Request; @@ -94,15 +96,21 @@ impl Agent { AgentBuilder::new().build() } - /// Request by providing the HTTP verb such as `GET`, `POST`... + /// Make a request with the HTTP verb as a parameter. + /// + /// This allows making requests with verbs that don't have a dedicated + /// method. + /// + /// If you've got an already-parsed [Url], try [request_url][Agent::request_url]. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); + /// use ureq::Response; /// let agent = ureq::agent(); /// - /// let resp = agent - /// .request("GET", "http://httpbin.org/status/200") + /// let resp: Response = agent + /// .request("OPTIONS", "http://example.com/") /// .call()?; /// # Ok(()) /// # } @@ -111,6 +119,30 @@ impl Agent { Request::new(self.clone(), method.into(), path.into()) } + /// Make a request using an already-parsed [Url]. + /// + /// This is useful if you've got a parsed Url from some other source, or if + /// you want to parse the URL and then modify it before making the request. + /// If you'd just like to pass a String or a `&str`, try [request][Agent::request]. + /// + /// ``` + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// use {url::Url, ureq::Response}; + /// let agent = ureq::agent(); + /// + /// let mut url: Url = "http://example.com/some-page".parse().unwrap(); + /// url.set_path("/robots.txt"); + /// let resp: Response = agent + /// .request_url("GET", &url) + /// .call()?; + /// # Ok(()) + /// # } + /// ``` + pub fn request_url(&self, method: &str, url: &Url) -> Request { + Request::with_url(self.clone(), method.into(), url.clone()) + } + /// Make a GET request from this agent. pub fn get(&self, path: &str) -> Request { self.request("GET", path) diff --git a/src/lib.rs b/src/lib.rs index 9e5c60f..56ce261 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,6 +207,7 @@ mod cookies; #[cfg(feature = "json")] pub use serde_json::json; +use url::Url; #[cfg(test)] mod test; @@ -265,18 +266,46 @@ pub fn agent() -> Agent { return testserver::test_agent(); } -/// Make a request setting the HTTP method via a string. +/// Make a request with the HTTP verb as a parameter. +/// +/// This allows making requests with verbs that don't have a dedicated +/// method. +/// +/// If you've got an already-parsed [Url], try [request_url][request_url]. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); -/// ureq::request("GET", "http://example.com").call()?; +/// let resp: ureq::Response = ureq::request("OPTIONS", "http://example.com/") +/// .call()?; /// # Ok(()) /// # } /// ``` pub fn request(method: &str, path: &str) -> Request { agent().request(method, path) } +/// Make a request using an already-parsed [Url]. +/// +/// This is useful if you've got a parsed Url from some other source, or if +/// you want to parse the URL and then modify it before making the request. +/// If you'd just like to pass a String or a `&str`, try [request][request()]. +/// +/// ``` +/// # fn main() -> Result<(), ureq::Error> { +/// # ureq::is_test(true); +/// use url::Url; +/// let agent = ureq::agent(); +/// +/// let mut url: Url = "http://example.com/some-page".parse().unwrap(); +/// url.set_path("/robots.txt"); +/// let resp: ureq::Response = ureq::request_url("GET", &url) +/// .call()?; +/// # Ok(()) +/// # } +/// ``` +pub fn request_url(method: &str, url: &Url) -> Request { + agent().request_url(method, url) +} /// Make a GET request. pub fn get(path: &str) -> Request { diff --git a/src/request.rs b/src/request.rs index 6e58c01..02e6ea1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -15,6 +15,12 @@ use super::SerdeValue; pub type Result = std::result::Result; +#[derive(Debug, Clone)] +enum Urlish { + Url(Url), + Str(String), +} + /// Request instances are builders that creates a request. /// /// ``` @@ -30,12 +36,21 @@ pub type Result = std::result::Result; pub struct Request { agent: Agent, method: String, - url: String, + url: Urlish, error_on_non_2xx: bool, headers: Vec
, query_params: Vec<(String, String)>, } +impl fmt::Display for Urlish { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Urlish::Url(u) => write!(f, "{}", u), + Urlish::Str(s) => write!(f, "{}", s), + } + } +} + impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( @@ -51,7 +66,18 @@ impl Request { Request { agent, method, - url, + url: Urlish::Str(url), + headers: vec![], + error_on_non_2xx: true, + query_params: vec![], + } + } + + pub(crate) fn with_url(agent: Agent, method: String, url: Url) -> Request { + Request { + agent, + method, + url: Urlish::Url(url), headers: vec![], error_on_non_2xx: true, query_params: vec![], @@ -79,11 +105,14 @@ impl Request { for h in &self.headers { h.validate()?; } - let mut url: Url = self.url.parse().map_err(|e: url::ParseError| { - ErrorKind::BadUrl - .msg(&format!("failed to parse URL '{}'", self.url)) - .src(e) - })?; + let mut url: Url = match self.url.clone() { + Urlish::Url(u) => u, + Urlish::Str(s) => s.parse().map_err(|e: url::ParseError| { + ErrorKind::BadUrl + .msg(&format!("failed to parse URL '{}'", self.url)) + .src(e) + })?, + }; for (name, value) in self.query_params.clone() { url.query_pairs_mut().append_pair(&name, &value); }