diff --git a/Cargo.lock b/Cargo.lock index 7508fdc..03afe1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "base64" -version = "0.13.1" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bitflags" @@ -59,9 +59,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" dependencies = [ "errno-dragonfly", "libc", @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "foreign-types" @@ -101,10 +101,11 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "http_req" -version = "0.9.3" +version = "0.10.0" dependencies = [ "native-tls", "rustls", + "rustls-pemfile", "unicase", "webpki", "webpki-roots", @@ -133,9 +134,9 @@ checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" [[package]] name = "log" @@ -261,9 +262,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" dependencies = [ "bitflags 2.4.0", "errno", @@ -274,15 +275,33 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ - "base64", "log", "ring", + "rustls-webpki", "sct", - "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -296,9 +315,9 @@ dependencies = [ [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", @@ -335,9 +354,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "syn" -version = "2.0.36" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -456,9 +475,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" dependencies = [ "ring", "untrusted", @@ -466,12 +485,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.21.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "winapi" diff --git a/Cargo.toml b/Cargo.toml index f2ad32a..adb0af1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "http_req" -version = "0.9.3" +version = "0.10.0" license = "MIT" description = "simple and lightweight HTTP client with built-in HTTPS support" repository = "https://github.com/jayjamesjay/http_req" @@ -11,24 +11,28 @@ keywords = ["http", "client", "request"] edition = "2021" [dependencies] -unicase = "^2.6" +unicase = "^2.7" [features] default = ["native-tls"] -rust-tls = ["rustls", "webpki", "webpki-roots"] +rust-tls = ["rustls", "webpki", "webpki-roots", "rustls-pemfile"] [dependencies.native-tls] version = "^0.2" optional = true [dependencies.rustls] -version = "^0.19" +version = "^0.21" +optional = true + +[dependencies.rustls-pemfile] +version = "^1.0" optional = true [dependencies.webpki] -version = "^0.21" +version = "^0.22" optional = true [dependencies.webpki-roots] -version = "^0.21" +version = "^0.25" optional = true diff --git a/README.md b/README.md index 52f01b7..00a6163 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,35 @@ # http_req [![Rust](https://github.com/jayjamesjay/http_req/actions/workflows/rust.yml/badge.svg)](https://github.com/jayjamesjay/http_req/actions/workflows/rust.yml) -[![Crates.io](https://img.shields.io/badge/crates.io-v0.9.3-orange.svg?longCache=true)](https://crates.io/crates/http_req) -[![Docs.rs](https://docs.rs/http_req/badge.svg)](https://docs.rs/http_req/0.9.3/http_req/) +[![Crates.io](https://img.shields.io/badge/crates.io-v0.10.0-orange.svg?longCache=true)](https://crates.io/crates/http_req) +[![Docs.rs](https://docs.rs/http_req/badge.svg)](https://docs.rs/http_req/0.10.0/http_req/) Simple and lightweight HTTP client with built-in HTTPS support. ## Requirements http_req by default uses [rust-native-tls](https://github.com/sfackler/rust-native-tls), -which uses TLS framework provided by OS on Windows and macOS, and OpenSSL +which relies on TLS framework provided by OS on Windows and macOS, and OpenSSL on all other platforms. But it also supports [rus-tls](https://crates.io/crates/rustls). ## Example -Basic GET request +Basic HTTP GET request ```rust use http_req::request; fn main() { - let mut writer = Vec::new(); //container for body of a response - let res = request::get("https://doc.rust-lang.org/", &mut writer).unwrap(); + let mut body = Vec::new(); //Container for body of a response. + let res = request::get("https://doc.rust-lang.org/", &mut body).unwrap(); println!("Status: {} {}", res.status_code(), res.reason()); } ``` +Take a look at [more examples](https://github.com/jayjamesjay/http_req/tree/master/examples) + ## How to use with `rustls`: In order to use `http_req` with `rustls` in your project, add following lines to `Cargo.toml`: ```toml [dependencies] -http_req = {version="^0.9", default-features = false, features = ["rust-tls"]} +http_req = {version="^0.10", default-features = false, features = ["rust-tls"]} ``` ## License diff --git a/examples/get.rs b/examples/get.rs index 1fbf882..a14c8a7 100644 --- a/examples/get.rs +++ b/examples/get.rs @@ -1,10 +1,14 @@ use http_req::request; fn main() { - let mut writer = Vec::new(); //container for body of a response - let res = request::get("https://www.rust-lang.org/learn", &mut writer).unwrap(); + //Container for body of a response. + let mut body = Vec::new(); + //Sends a HTTP GET request and processes the response. Saves body of the response to `body` variable. + let res = request::get("https://www.rust-lang.org/learn", &mut body).unwrap(); + + //Prints details about the response. println!("Status: {} {}", res.status_code(), res.reason()); - println!("Headers {}", res.headers()); - //println!("{}", String::from_utf8_lossy(&writer)); + println!("Headers: {}", res.headers()); + //println!("{}", String::from_utf8_lossy(&body)); } diff --git a/examples/head.rs b/examples/head.rs index eb2f816..922e278 100644 --- a/examples/head.rs +++ b/examples/head.rs @@ -1,8 +1,10 @@ use http_req::request; fn main() { + //Sends a HTTP HEAD request and processes the response. let res = request::head("https://www.rust-lang.org/learn").unwrap(); + //Prints details about the response. println!("Status: {} {}", res.status_code(), res.reason()); - println!("{:?}", res.headers()); + println!("Headers: {}", res.headers()); } diff --git a/examples/post.rs b/examples/post.rs index 982fd32..fadfb9d 100644 --- a/examples/post.rs +++ b/examples/post.rs @@ -1,11 +1,17 @@ use http_req::request; fn main() { - let mut writer = Vec::new(); //container for body of a response - const BODY: &[u8; 27] = b"field1=value1&field2=value2"; - let res = request::post("https://httpbin.org/post", BODY, &mut writer).unwrap(); + //Container for body of a response. + let mut res_body = Vec::new(); + //Body of a request. + const REQ_BODY: &[u8; 27] = b"field1=value1&field2=value2"; + + //Sends a HTTP POST request and processes the response. + let res = request::post("https://httpbin.org/post", REQ_BODY, &mut res_body).unwrap(); + + //Prints details about the response. println!("Status: {} {}", res.status_code(), res.reason()); - println!("Headers {}", res.headers()); - //println!("{}", String::from_utf8_lossy(&writer)); + println!("Headers: {}", res.headers()); + println!("{}", String::from_utf8_lossy(&res_body)); } diff --git a/examples/request_builder_get.rs b/examples/request_builder_get.rs index 52457d7..7c237d5 100644 --- a/examples/request_builder_get.rs +++ b/examples/request_builder_get.rs @@ -2,26 +2,27 @@ use http_req::{request::RequestBuilder, tls, uri::Uri}; use std::{convert::TryFrom, net::TcpStream}; fn main() { - //Parse uri and assign it to variable `addr` - let addr: Uri = Uri::try_from("https://doc.rust-lang.org/").unwrap(); + //Parses a URI and assigns it to a variable `addr`. + let addr: Uri = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); - //Connect to remote host + //Connects to a remote host. Uses information from `addr`. let stream = TcpStream::connect((addr.host().unwrap(), addr.corr_port())).unwrap(); - //Open secure connection over TlsStream, because of `addr` (https) + //Opens a secure connection over TlsStream. This is required due to use of `https` protocol. let mut stream = tls::Config::default() .connect(addr.host().unwrap_or(""), stream) .unwrap(); - //Container for response's body + //Container for a response's body. let mut writer = Vec::new(); - //Add header `Connection: Close` + //Adds a header `Connection: Close`. let response = RequestBuilder::new(&addr) .header("Connection", "Close") .send(&mut stream, &mut writer) .unwrap(); println!("Status: {} {}", response.status_code(), response.reason()); + println!("Headers: {}", response.headers()); //println!("{}", String::from_utf8_lossy(&writer)); } diff --git a/src/error.rs b/src/error.rs index 55bf752..5c16e68 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,8 @@ pub enum ParseErr { UriErr, Invalid, Empty, + #[cfg(feature = "rust-tls")] + Rustls(rustls::Error), } impl error::Error for ParseErr { @@ -20,6 +22,8 @@ impl error::Error for ParseErr { Utf8(e) => Some(e), Int(e) => Some(e), StatusErr | HeadersErr | UriErr | Invalid | Empty => None, + #[cfg(feature = "rust-tls")] + Rustls(e) => Some(e), } } } @@ -36,11 +40,20 @@ impl fmt::Display for ParseErr { StatusErr => "status line contains invalid values", HeadersErr => "headers contain invalid values", UriErr => "uri contains invalid characters", + #[cfg(feature = "rust-tls")] + Rustls(_) => "rustls error", }; write!(f, "ParseErr: {}", err) } } +#[cfg(feature = "rust-tls")] +impl From for ParseErr { + fn from(e: rustls::Error) -> Self { + ParseErr::Rustls(e) + } +} + impl From for ParseErr { fn from(e: num::ParseIntError) -> Self { ParseErr::Int(e) diff --git a/src/lib.rs b/src/lib.rs index 27bd641..d07d71f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,9 @@ //!use http_req::request; //! //!fn main() { -//! let mut writer = Vec::new(); //container for body of a response -//! let res = request::get("https://doc.rust-lang.org/", &mut writer).unwrap(); +//! //Container for body of a response +//! let mut body = Vec::new(); +//! let res = request::get("https://doc.rust-lang.org/", &mut body).unwrap(); //! //! println!("Status: {} {}", res.status_code(), res.reason()); //!} diff --git a/src/tls.rs b/src/tls.rs index ebb5b91..5d6a713 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -23,7 +23,7 @@ pub struct Conn { stream: native_tls::TlsStream, #[cfg(feature = "rust-tls")] - stream: rustls::StreamOwned, + stream: rustls::StreamOwned, } impl io::Read for Conn { @@ -63,7 +63,7 @@ pub struct Config { #[cfg(feature = "native-tls")] extra_root_certs: Vec, #[cfg(feature = "rust-tls")] - client_config: std::sync::Arc, + root_certs: std::sync::Arc, } impl Default for Config { @@ -76,13 +76,16 @@ impl Default for Config { #[cfg(feature = "rust-tls")] fn default() -> Self { - let mut config = rustls::ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - + let mut root_store = rustls::RootCertStore::empty(); + root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { + rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); Config { - client_config: std::sync::Arc::new(config), + root_certs: std::sync::Arc::new(root_store), } } } @@ -127,11 +130,8 @@ impl Config { pub fn add_root_cert_file_pem(&mut self, file_path: &Path) -> Result<&mut Self, HttpError> { let f = File::open(file_path)?; let mut f = BufReader::new(f); - let config = std::sync::Arc::make_mut(&mut self.client_config); - let _ = config - .root_store - .add_pem_file(&mut f) - .map_err(|_| HttpError::from(ParseErr::Invalid))?; + let root_certs = std::sync::Arc::make_mut(&mut self.root_certs); + root_certs.add_parsable_certificates(&rustls_pemfile::certs(&mut f)?); Ok(self) } @@ -141,13 +141,17 @@ impl Config { H: AsRef, S: io::Read + io::Write, { - use rustls::{ClientSession, StreamOwned}; - - let session = ClientSession::new( - &self.client_config, - webpki::DNSNameRef::try_from_ascii_str(hostname.as_ref()) - .map_err(|_| HttpError::Tls)?, - ); + use rustls::{ClientConnection, StreamOwned}; + + let client_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(self.root_certs.clone()) + .with_no_client_auth(); + let session = ClientConnection::new( + std::sync::Arc::new(client_config), + hostname.as_ref().try_into().map_err(|_| HttpError::Tls)?, + ) + .map_err(|e| ParseErr::Rustls(e))?; let stream = StreamOwned::new(session, stream); Ok(Conn { stream }) diff --git a/src/uri.rs b/src/uri.rs index 378a602..a3e9301 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -229,31 +229,23 @@ impl<'a> Uri<'a> { ///assert_eq!(uri.resource(), "/bar/baz?query#fragment"); ///``` pub fn resource(&self) -> &str { - let mut result = "/"; - - for v in &[self.path, self.query, self.fragment] { - if let Some(r) = v { - result = &self.inner[r.start..]; - break; - } + match self.path { + Some(p) => &self.inner[p.start..], + None => "/", } - - result } } impl<'a> fmt::Display for Uri<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let uri = if let Some(auth) = &self.authority { - let mut uri = self.inner.to_string(); + let mut uri = self.inner.to_string(); + + if let Some(auth) = &self.authority { let auth = auth.to_string(); let start = self.scheme.end + 3; uri.replace_range(start..(start + auth.len()), &auth); - uri - } else { - self.inner.to_string() - }; + } write!(f, "{}", uri) } @@ -265,30 +257,38 @@ impl<'a> TryFrom<&'a str> for Uri<'a> { fn try_from(s: &'a str) -> Result { let (scheme, mut uri_part) = get_chunks(&s, Some(RangeC::new(0, s.len())), ":"); let scheme = scheme.ok_or(ParseErr::UriErr)?; + let (mut authority, mut query, mut fragment) = (None, None, None); - let mut authority = None; - - if let Some(u) = &uri_part { - if s[*u].contains("//") { + if let Some(u) = uri_part { + if s[u].contains("//") { let (auth, part) = get_chunks(&s, Some(RangeC::new(u.start + 2, u.end)), "/"); - authority = if let Some(a) = auth { - Some(Authority::try_from(&s[a])?) - } else { - None + if let Some(a) = auth { + authority = Some(Authority::try_from(&s[a])?) }; uri_part = part; } } - let (mut path, uri_part) = get_chunks(&s, uri_part, "?"); - - if authority.is_some() || &s[scheme] == "file" { - path = path.map(|p| RangeC::new(p.start - 1, p.end)); + if let Some(u) = uri_part { + if &s[u.start - 1..u.start] == "/" { + uri_part = Some(RangeC::new(u.start - 1, u.end)); + } } - let (query, fragment) = get_chunks(&s, uri_part, "#"); + let mut path = uri_part; + + if let Some(u) = uri_part { + if s[u].contains("?") && s[u].contains("#") { + (path, uri_part) = get_chunks(&s, uri_part, "?"); + (query, fragment) = get_chunks(&s, uri_part, "#"); + } else if s[u].contains("?") { + (path, query) = get_chunks(&s, uri_part, "?"); + } else if s[u].contains("#") { + (path, fragment) = get_chunks(&s, uri_part, "#"); + } + } Ok(Uri { inner: s, @@ -400,22 +400,18 @@ impl<'a> TryFrom<&'a str> for Authority<'a> { type Error = ParseErr; fn try_from(s: &'a str) -> Result { - let mut username = None; - let mut password = None; + let (mut username, mut password) = (None, None); let uri_part = if s.contains('@') { let (info, part) = get_chunks(&s, Some(RangeC::new(0, s.len())), "@"); - let (name, pass) = get_chunks(&s, info, ":"); - - username = name; - password = pass; + (username, password) = get_chunks(&s, info, ":"); part } else { Some(RangeC::new(0, s.len())) }; - let split_by = if s.contains(']') && s.contains('[') { + let split_by = if s.contains('[') && s.contains(']') { "]:" } else { ":" @@ -441,17 +437,14 @@ impl<'a> TryFrom<&'a str> for Authority<'a> { impl<'a> fmt::Display for Authority<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let auth = if let Some(pass) = self.password { - let range = Range::from(pass); + let mut auth = self.inner.to_string(); + if let Some(pass) = self.password { + let range = Range::from(pass); let hidden_pass = "*".repeat(range.len()); - let mut auth = self.inner.to_string(); - auth.replace_range(range, &hidden_pass); - auth - } else { - self.inner.to_string() - }; + auth.replace_range(range, &hidden_pass); + } write!(f, "{}", auth) } @@ -470,40 +463,38 @@ fn get_chunks<'a>( range: Option, separator: &'a str, ) -> (Option, Option) { - if let Some(r) = range { - let range = Range::from(r); + let (mut before, mut after) = (None, None); - match s[range.clone()].find(separator) { + if let Some(range) = range { + match s[range].find(separator) { Some(i) => { - let mid = r.start + i + separator.len(); - let before = Some(RangeC::new(r.start, mid - 1)).filter(|r| r.start != r.end); - let after = Some(RangeC::new(mid, r.end)).filter(|r| r.start != r.end); - - (before, after) + let mid = range.start + i + separator.len(); + before = Some(RangeC::new(range.start, mid - 1)).filter(|r| r.start != r.end); + after = Some(RangeC::new(mid, range.end)).filter(|r| r.start != r.end); } None => { if !s[range].is_empty() { - (Some(r), None) - } else { - (None, None) + before = Some(range); } } } - } else { - (None, None) } + + (before, after) } #[cfg(test)] mod tests { use super::*; - const TEST_URIS: [&str; 5] = [ + const TEST_URIS: [&str; 7] = [ "https://user:info@foo.com:12/bar/baz?query#fragment", "file:///C:/Users/User/Pictures/screenshot.png", "https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol", "mailto:John.Doe@example.com", "https://[4b10:bbb0:0:d0::ba7:8001]:443/", + "http://example.com/?query=val", + "https://example.com/#fragment", ]; const TEST_AUTH: [&str; 4] = [ @@ -552,12 +543,11 @@ mod tests { .iter() .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); + const RESULT: [&str; 7] = ["https", "file", "https", "mailto", "https", "http", "https"]; - assert_eq!(uris[0].scheme(), "https"); - assert_eq!(uris[1].scheme(), "file"); - assert_eq!(uris[2].scheme(), "https"); - assert_eq!(uris[3].scheme(), "mailto"); - assert_eq!(uris[4].scheme(), "https"); + for i in 0..RESULT.len() { + assert_eq!(uris[i].scheme(), RESULT[i]); + } } #[test] @@ -566,12 +556,11 @@ mod tests { .iter() .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); + const RESULT: [Option<&str>; 7] = [Some("user:info"), None, None, None, None, None, None]; - assert_eq!(uris[0].user_info(), Some("user:info")); - assert_eq!(uris[1].user_info(), None); - assert_eq!(uris[2].user_info(), None); - assert_eq!(uris[3].user_info(), None); - assert_eq!(uris[4].user_info(), None); + for i in 0..RESULT.len() { + assert_eq!(uris[i].user_info(), RESULT[i]); + } } #[test] @@ -581,11 +570,19 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].host(), Some("foo.com")); - assert_eq!(uris[1].host(), None); - assert_eq!(uris[2].host(), Some("en.wikipedia.org")); - assert_eq!(uris[3].host(), None); - assert_eq!(uris[4].host(), Some("[4b10:bbb0:0:d0::ba7:8001]")); + const RESULT: [Option<&str>; 7] = [ + Some("foo.com"), + None, + Some("en.wikipedia.org"), + None, + Some("[4b10:bbb0:0:d0::ba7:8001]"), + Some("example.com"), + Some("example.com"), + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].host(), RESULT[i]); + } } #[test] @@ -612,7 +609,11 @@ mod tests { assert_eq!(uris[0].port(), Some(12)); assert_eq!(uris[4].port(), Some(443)); - for i in 1..3 { + for i in 1..4 { + assert_eq!(uris[i].port(), None); + } + + for i in 5..7 { assert_eq!(uris[i].port(), None); } } @@ -624,11 +625,13 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].corr_port(), 12); - assert_eq!(uris[1].corr_port(), HTTP_PORT); - assert_eq!(uris[2].corr_port(), HTTPS_PORT); - assert_eq!(uris[3].corr_port(), HTTP_PORT); - assert_eq!(uris[4].corr_port(), HTTPS_PORT); + const RESULT: [u16; 7] = [ + 12, HTTP_PORT, HTTPS_PORT, HTTP_PORT, HTTPS_PORT, HTTP_PORT, HTTPS_PORT, + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].corr_port(), RESULT[i]); + } } #[test] @@ -638,14 +641,19 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].path(), Some("/bar/baz")); - assert_eq!( - uris[1].path(), - Some("/C:/Users/User/Pictures/screenshot.png") - ); - assert_eq!(uris[2].path(), Some("/wiki/Hypertext_Transfer_Protocol")); - assert_eq!(uris[3].path(), Some("John.Doe@example.com")); - assert_eq!(uris[4].path(), None); + const RESULT: [Option<&str>; 7] = [ + Some("/bar/baz"), + Some("/C:/Users/User/Pictures/screenshot.png"), + Some("/wiki/Hypertext_Transfer_Protocol"), + Some("John.Doe@example.com"), + None, + Some("/"), + Some("/"), + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].path(), RESULT[i]); + } } #[test] @@ -655,10 +663,18 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].query(), Some("query")); - - for i in 1..4 { - assert_eq!(uris[i].query(), None); + const RESULT: [Option<&str>; 7] = [ + Some("query"), + None, + None, + None, + None, + Some("query=val"), + None, + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].query(), RESULT[i]); } } @@ -669,10 +685,18 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].fragment(), Some("fragment")); - - for i in 1..4 { - assert_eq!(uris[i].fragment(), None); + const RESULT: [Option<&str>; 7] = [ + Some("fragment"), + None, + None, + None, + None, + None, + Some("fragment"), + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].fragment(), RESULT[i]); } } @@ -683,11 +707,19 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].resource(), "/bar/baz?query#fragment"); - assert_eq!(uris[1].resource(), "/C:/Users/User/Pictures/screenshot.png"); - assert_eq!(uris[2].resource(), "/wiki/Hypertext_Transfer_Protocol"); - assert_eq!(uris[3].resource(), "John.Doe@example.com"); - assert_eq!(uris[4].resource(), "/"); + const RESULT: [&str; 7] = [ + "/bar/baz?query#fragment", + "/C:/Users/User/Pictures/screenshot.png", + "/wiki/Hypertext_Transfer_Protocol", + "John.Doe@example.com", + "/", + "/?query=val", + "/#fragment", + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].resource(), RESULT[i]); + } } #[test]