Use http::Url in http::Response.

This commit is contained in:
Jean-Christophe Amiel 2024-05-17 17:59:59 +02:00
parent 2c72b8d6a0
commit 050b803a97
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
9 changed files with 135 additions and 94 deletions

View File

@ -283,7 +283,7 @@ impl Client {
let stop_dt = start_dt + duration;
let timings = Timings::new(&mut self.handle, start_dt, stop_dt);
let url = Url::try_from(url.as_str())?;
let url = Url::parse(&url)?;
let request = Request::new(
&method.to_string(),
url.clone(),
@ -296,7 +296,7 @@ impl Client {
headers,
response_body,
duration,
&url.to_string(),
url,
certificate,
);

View File

@ -40,8 +40,9 @@ pub struct Request {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum RequestedHttpVersion {
/// The effective HTTP version will be chosen by libcurl
#[default]
Default, // The effective HTTP version will be chosen by libcurl
Default,
Http10,
Http11,
Http2,
@ -63,8 +64,9 @@ impl fmt::Display for RequestedHttpVersion {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum IpResolve {
/// Default, can use addresses of all IP versions that your system allows.
#[default]
Default, // Default, can use addresses of all IP versions that your system allows.
Default,
IpV4,
IpV6,
}
@ -120,13 +122,13 @@ mod tests {
headers.push(Header::new("Accept", "*/*"));
headers.push(Header::new("User-Agent", "hurl/1.0"));
headers.push(Header::new("content-type", "application/json"));
let url = Url::try_from("http://localhost:8000/hello").unwrap();
let url = Url::parse("http://localhost:8000/hello").unwrap();
Request::new("GET", url, headers, vec![])
}
fn query_string_request() -> Request {
let url = Url::try_from("http://localhost:8000/querystring-params?param1=value1&param2=&param3=a%3Db&param4=1%2C2%2C3").unwrap();
let url = Url::parse("http://localhost:8000/querystring-params?param1=value1&param2=&param3=a%3Db&param4=1%2C2%2C3").unwrap();
Request::new("GET", url, HeaderVec::new(), vec![])
}
@ -134,7 +136,7 @@ mod tests {
fn cookies_request() -> Request {
let mut headers = HeaderVec::new();
headers.push(Header::new("Cookie", "cookie1=value1; cookie2=value2"));
let url = Url::try_from("http://localhost:8000/cookies").unwrap();
let url = Url::parse("http://localhost:8000/cookies").unwrap();
Request::new("GET", url, headers, vec![])
}

View File

@ -19,7 +19,7 @@ use std::fmt;
use std::time::Duration;
use crate::http::certificate::Certificate;
use crate::http::HeaderVec;
use crate::http::{HeaderVec, Url};
/// Represents a runtime HTTP response.
/// This is a real response, that has been executed by our HTTP client.
@ -30,25 +30,11 @@ pub struct Response {
pub headers: HeaderVec,
pub body: Vec<u8>,
pub duration: Duration,
pub url: String,
pub url: Url,
/// The end-user certificate, in the response certificate chain
pub certificate: Option<Certificate>,
}
impl Default for Response {
fn default() -> Self {
Response {
version: HttpVersion::Http10,
status: 200,
headers: HeaderVec::new(),
body: vec![],
duration: Default::default(),
url: String::new(),
certificate: None,
}
}
}
impl Response {
/// Creates a new HTTP response
pub fn new(
@ -57,7 +43,7 @@ impl Response {
headers: HeaderVec,
body: Vec<u8>,
duration: Duration,
url: &str,
url: Url,
certificate: Option<Certificate>,
) -> Self {
Response {
@ -66,7 +52,7 @@ impl Response {
headers,
body,
duration,
url: url.to_string(),
url,
certificate,
}
}
@ -103,10 +89,14 @@ mod tests {
fn get_header_values() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Length", "12"));
let response = Response {
version: HttpVersion::Http10,
status: 200,
headers,
..Default::default()
body: vec![],
duration: Default::default(),
url: Url::parse("http://localhost").unwrap(),
certificate: None,
};
assert_eq!(response.headers.values("Content-Length"), vec!["12"]);
assert!(response.headers.values("Unknown").is_empty());

View File

@ -157,7 +157,19 @@ fn uncompress_zlib(data: &[u8]) -> Result<Vec<u8>, HttpError> {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::http::{Header, HeaderVec, Response};
use crate::http::{Header, HeaderVec, HttpVersion, Response, Url};
fn default_response() -> Response {
Response {
version: HttpVersion::Http10,
status: 200,
headers: HeaderVec::new(),
body: vec![],
duration: Default::default(),
url: Url::parse("http://localhost").unwrap(),
certificate: None,
}
}
#[test]
fn test_parse_content_encoding() {
@ -175,7 +187,7 @@ pub mod tests {
#[test]
fn test_content_encoding() {
let response = Response::default();
let response = default_response();
assert_eq!(response.headers.content_encoding().unwrap(), vec![]);
let mut headers = HeaderVec::new();
@ -183,7 +195,7 @@ pub mod tests {
let response = Response {
headers,
..Default::default()
..default_response()
};
assert_eq!(
response.headers.content_encoding().err().unwrap(),
@ -197,7 +209,7 @@ pub mod tests {
let response = Response {
headers,
..Default::default()
..default_response()
};
assert_eq!(
response.headers.content_encoding().unwrap(),
@ -211,7 +223,7 @@ pub mod tests {
headers.push(Header::new("Content-Encoding", "br, identity"));
let response = Response {
headers,
..Default::default()
..default_response()
};
assert_eq!(
response.headers.content_encoding().unwrap(),
@ -230,7 +242,7 @@ pub mod tests {
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21, 0x03,
],
..Default::default()
..default_response()
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
@ -242,13 +254,13 @@ pub mod tests {
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21, 0x03,
],
..Default::default()
..default_response()
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
let response = Response {
body: b"Hello World!".to_vec(),
..Default::default()
..default_response()
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
}
@ -301,7 +313,7 @@ pub mod tests {
fn hello_response() -> Response {
Response {
body: b"Hello World!".to_vec(),
..Default::default()
..default_response()
}
}
@ -312,7 +324,7 @@ pub mod tests {
Response {
headers,
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
..Default::default()
..default_response()
}
}
@ -326,7 +338,7 @@ pub mod tests {
Response {
headers,
body: vec![0x63, 0x61, 0x66, 0xe9],
..Default::default()
..default_response()
}
}
@ -390,7 +402,7 @@ pub mod tests {
Response {
headers,
body: b"Hello World!".to_vec(),
..Default::default()
..default_response()
}
.headers
.character_encoding()
@ -407,7 +419,7 @@ pub mod tests {
assert_eq!(
Response {
body: vec![0x63, 0x61, 0x66, 0xe9],
..Default::default()
..default_response()
}
.text()
.err()
@ -427,7 +439,7 @@ pub mod tests {
Response {
headers,
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
..Default::default()
..default_response()
}
.text()
.unwrap(),

View File

@ -15,10 +15,24 @@
* limitations under the License.
*
*/
use crate::http::{Header, HeaderVec, Method, Param, RequestCookie, RequestSpec, Response};
use crate::http::{
Header, HeaderVec, HttpVersion, Method, Param, RequestCookie, RequestSpec, Response, Url,
};
/// Some Request Response to be used by tests
fn default_response() -> Response {
Response {
version: HttpVersion::Http10,
status: 200,
headers: HeaderVec::new(),
body: vec![],
duration: Default::default(),
url: Url::parse("http://localhost").unwrap(),
certificate: None,
}
}
pub fn hello_http_request() -> RequestSpec {
RequestSpec {
method: Method("GET".to_string()),
@ -42,7 +56,7 @@ pub fn json_http_response() -> Response {
"#
.to_string(),
),
..Default::default()
..default_response()
}
}
@ -63,7 +77,7 @@ pub fn xml_two_users_http_response() -> Response {
"#
.to_string(),
),
..Default::default()
..default_response()
}
}
@ -85,7 +99,7 @@ pub fn xml_three_users_http_response() -> Response {
"#
.to_string(),
),
..Default::default()
..default_response()
}
}
@ -97,7 +111,7 @@ pub fn hello_http_response() -> Response {
Response {
headers,
body: String::into_bytes(String::from("Hello World!")),
..Default::default()
..default_response()
}
}
@ -109,7 +123,7 @@ pub fn bytes_http_response() -> Response {
Response {
headers,
body: vec![255],
..Default::default()
..default_response()
}
}
@ -122,7 +136,7 @@ pub fn html_http_response() -> Response {
body: String::into_bytes(String::from(
"<html><head><meta charset=\"UTF-8\"></head><body><br></body></html>",
)),
..Default::default()
..default_response()
}
}

View File

@ -26,6 +26,22 @@ pub struct Url {
}
impl Url {
/// Parses an absolute URL from a string.
pub fn parse(value: &str) -> Result<Url, HttpError> {
let inner = match url::Url::parse(value) {
Ok(url) => url,
Err(e) => return Err(HttpError::InvalidUrl(value.to_string(), e.to_string())),
};
let scheme = inner.scheme();
if scheme != "http" && scheme != "https" {
return Err(HttpError::InvalidUrl(
value.to_string(),
"Missing protocol http or https".to_string(),
));
}
Ok(Url { inner })
}
/// Returns a list of query parameters (values are URL decoded).
pub fn query_params(&self) -> Vec<Param> {
self.inner
@ -46,27 +62,7 @@ impl Url {
))
}
};
Url::try_from(new_inner.to_string().as_str())
}
}
impl TryFrom<&str> for Url {
type Error = HttpError;
/// Parses an absolute URL from a string.
fn try_from(value: &str) -> Result<Self, Self::Error> {
let inner = match url::Url::parse(value) {
Ok(url) => url,
Err(e) => return Err(HttpError::InvalidUrl(value.to_string(), e.to_string())),
};
let scheme = inner.scheme();
if scheme != "http" && scheme != "https" {
return Err(HttpError::InvalidUrl(
value.to_string(),
"Missing protocol http or https".to_string(),
));
}
Ok(Url { inner })
Url::parse(new_inner.as_str())
}
}
@ -91,16 +87,16 @@ mod tests {
"https://localhost:8000",
];
for url in urls {
assert!(Url::try_from(url).is_ok());
assert!(Url::parse(url).is_ok());
}
}
#[test]
fn query_params() {
let url = Url::try_from("http://localhost:8000/hello").unwrap();
let url = Url::parse("http://localhost:8000/hello").unwrap();
assert_eq!(url.query_params(), vec![]);
let url = Url::try_from("http://localhost:8000/querystring-params?param1=value1&param2=&param3=a%3Db&param4=1%2C2%2C3").unwrap();
let url = Url::parse("http://localhost:8000/querystring-params?param1=value1&param2=&param3=a%3Db&param4=1%2C2%2C3").unwrap();
assert_eq!(
url.query_params(),
vec![
@ -114,29 +110,29 @@ mod tests {
#[test]
fn test_join() {
let base = Url::try_from("http://example.net/foo/index.html").unwrap();
let base = Url::parse("http://example.net/foo/index.html").unwrap();
// Test join with absolute
assert_eq!(
base.join("http://bar.com/redirected").unwrap(),
Url::try_from("http://bar.com/redirected").unwrap()
Url::parse("http://bar.com/redirected").unwrap()
);
// Test join with relative
assert_eq!(
base.join("/redirected").unwrap(),
Url::try_from("http://example.net/redirected").unwrap()
Url::parse("http://example.net/redirected").unwrap()
);
assert_eq!(
base.join("../bar/index.html").unwrap(),
Url::try_from("http://example.net/bar/index.html").unwrap()
Url::parse("http://example.net/bar/index.html").unwrap()
);
// Scheme relative URL
assert_eq!(
base.join("//example.org/baz/index.html").unwrap(),
Url::try_from("http://example.org/baz/index.html").unwrap()
Url::parse("http://example.org/baz/index.html").unwrap()
)
}
}

View File

@ -123,6 +123,18 @@ mod tests {
use crate::util::term::{Stderr, Stdout, WriteMode};
use hurl_core::ast::{Pos, SourceInfo};
fn default_response() -> Response {
Response {
version: HttpVersion::Http10,
status: 200,
headers: HeaderVec::new(),
body: vec![],
duration: Default::default(),
url: Url::parse("http://localhost").unwrap(),
certificate: None,
}
}
fn hurl_result_json() -> HurlResult {
let mut headers = HeaderVec::new();
headers.push(Header::new("x-foo", "xxx"));
@ -138,12 +150,12 @@ mod tests {
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
calls: vec![Call {
request: Request {
url: Url::try_from("https://foo.com").unwrap(),
url: Url::parse("https://foo.com").unwrap(),
method: "GET".to_string(),
headers: HeaderVec::new(),
body: vec![],
},
response: Default::default(),
response: default_response(),
timings: Default::default(),
}],
captures: vec![],
@ -157,12 +169,12 @@ mod tests {
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
calls: vec![Call {
request: Request {
url: Url::try_from("https://bar.com").unwrap(),
url: Url::parse("https://bar.com").unwrap(),
method: "GET".to_string(),
headers: HeaderVec::new(),
body: vec![],
},
response: Default::default(),
response: default_response(),
timings: Default::default(),
}],
captures: vec![],
@ -176,7 +188,7 @@ mod tests {
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
calls: vec![Call {
request: Request {
url: Url::try_from("https://baz.com").unwrap(),
url: Url::parse("https://baz.com").unwrap(),
method: "GET".to_string(),
headers: HeaderVec::new(),
body: vec![],
@ -187,7 +199,7 @@ mod tests {
headers,
body: b"{\"say\": \"Hello World!\"}".into(),
duration: Default::default(),
url: String::new(),
url: Url::parse("https://baz.com").unwrap(),
certificate: None,
},
timings: Default::default(),

View File

@ -71,7 +71,7 @@ fn eval_query_status(response: &http::Response) -> QueryResult {
}
fn eval_query_url(response: &http::Response) -> QueryResult {
Ok(Some(Value::String(response.url.clone())))
Ok(Some(Value::String(response.url.to_string())))
}
fn eval_query_header(
@ -349,12 +349,24 @@ impl Value {
#[cfg(test)]
pub mod tests {
use crate::http::{HeaderVec, HttpError};
use crate::http::{HeaderVec, HttpError, HttpVersion, Url};
use hex_literal::hex;
use hurl_core::ast::{Pos, SourceInfo};
use super::*;
fn default_response() -> http::Response {
http::Response {
version: HttpVersion::Http10,
status: 200,
headers: HeaderVec::new(),
body: vec![],
duration: Default::default(),
url: Url::parse("http://localhost").unwrap(),
certificate: None,
}
}
pub fn xpath_invalid_query() -> Query {
// xpath ???
let whitespace = Whitespace {
@ -642,7 +654,7 @@ pub mod tests {
let response = http::Response {
headers,
..Default::default()
..default_response()
};
// cookie "LSID"
@ -872,7 +884,7 @@ pub mod tests {
let variables = HashMap::new();
let http_response = http::Response {
body: vec![200],
..Default::default()
..default_response()
};
let error = eval_query(&xpath_users(), &variables, &http_response)
.err()
@ -1024,7 +1036,7 @@ pub mod tests {
let variables = HashMap::new();
let http_response = http::Response {
body: String::into_bytes(String::from("xxx")),
..Default::default()
..default_response()
};
let error = eval_query(&jsonpath_success(), &variables, &http_response)
.err()
@ -1038,7 +1050,7 @@ pub mod tests {
let variables = HashMap::new();
let http_response = http::Response {
body: String::into_bytes(String::from("{}")),
..Default::default()
..default_response()
};
//assert_eq!(jsonpath_success().eval(http_response).unwrap(), Value::List(vec![]));
assert_eq!(
@ -1123,7 +1135,7 @@ pub mod tests {
&variables,
&http::Response {
body: vec![0xff],
..Default::default()
..default_response()
}
)
.unwrap()
@ -1138,7 +1150,7 @@ pub mod tests {
fn test_query_certificate() {
assert!(eval_query_certificate(
&http::Response {
..Default::default()
..default_response()
},
CertificateAttributeName::Subject
)
@ -1154,7 +1166,7 @@ pub mod tests {
expire_date: Default::default(),
serial_number: String::new()
}),
..Default::default()
..default_response()
},
CertificateAttributeName::Subject
)

View File

@ -52,7 +52,7 @@ fn simple_sample() {
fn check_request(request: &Request) {
assert_eq!(
request.url,
Url::try_from("http://localhost:8000/hello").unwrap()
Url::parse("http://localhost:8000/hello").unwrap()
);
assert_eq!(request.method, "GET");
let header_names = request
@ -82,7 +82,10 @@ fn simple_sample() {
assert!(header_names.contains(&"Server".to_string())); // There are two 'Server' HTTP headers
assert_eq!(response.body.len(), 12);
assert!(response.duration < Duration::from_secs(1));
assert_eq!(response.url, "http://localhost:8000/hello");
assert_eq!(
response.url,
Url::parse("http://localhost:8000/hello").unwrap()
);
assert!(response.certificate.is_none());
}