use soup::glib::{self};
#[derive(Debug)]
pub(crate) struct UriBuilder {
flags: glib::UriFlags,
scheme: glib::GString,
userinfo: Option<glib::GString>,
host: Option<glib::GString>,
port: i32,
path: glib::GString,
query: Option<glib::GStringBuilder>,
fragment: Option<glib::GString>,
}
impl Default for UriBuilder {
fn default() -> Self {
Self {
flags: glib::UriFlags::ENCODED,
scheme: Default::default(),
userinfo: Default::default(),
host: Default::default(),
port: Default::default(),
path: Default::default(),
query: Default::default(),
fragment: Default::default(),
}
}
}
impl From<&glib::Uri> for UriBuilder {
fn from(value: &glib::Uri) -> Self {
if value.flags().contains(glib::UriFlags::ENCODED) {
Self {
flags: value.flags(),
scheme: value.scheme(),
userinfo: value.userinfo(),
host: value.host(),
port: value.port(),
path: value.path(),
query: value.query().map(glib::GStringBuilder::new),
fragment: value.fragment(),
}
} else {
let flags = value.flags() | glib::UriFlags::ENCODED;
let (scheme, userinfo, host, port, path, query, fragment) =
glib::Uri::split(&value.to_str(), flags).expect("valid URI to be splittable");
Self {
flags,
scheme: scheme.expect("valid URI to have a scheme"),
userinfo,
host,
port,
path,
query: query.map(glib::GStringBuilder::new),
fragment,
}
}
}
}
impl UriBuilder {
#[cfg(feature = "query")]
pub(crate) fn append_query(mut self, params: &str) -> Self {
if params.is_empty() {
return self;
}
let query = self.query.get_or_insert_default();
if !query.is_empty() {
query.append("&");
}
query.append(params);
self
}
pub(crate) fn build(self) -> glib::Uri {
glib::Uri::build(
self.flags | glib::UriFlags::ENCODED,
&self.scheme,
self.userinfo.as_deref(),
self.host.as_deref(),
self.port,
&self.path,
self.query.as_deref(),
self.fragment.as_deref(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use soup::glib::{Uri, UriFlags};
#[test]
fn returns_original_url_when_encoded_set() {
const URI: &str = "https://user:pw@example.com/hello%20world?foo=bar%20baz#frag%20ment";
let uri = Uri::parse(URI, UriFlags::ENCODED).unwrap();
assert_eq!(URI, uri.to_str()); let actual = UriBuilder::from(&uri).build();
assert_uri_equal(&uri, &actual);
}
#[test]
fn returns_original_url_when_encoded_not_set() {
const URI: &str = "https://user:pw@example.com:80/hello%20world?foo=bar%20baz#frag%20ment";
let uri = Uri::parse(URI, UriFlags::NONE).unwrap();
assert_eq!(URI, uri.to_str()); let actual = UriBuilder::from(&uri).build();
assert_uri_equal(&uri, &actual);
}
#[cfg(feature = "query")]
#[test]
fn does_nothing_if_params_are_empty() {
const URIS: &[&str] = &[
"https://example.com",
"https://example.com?",
"https://example.com?foo=bar",
"https://example.com#foo=bar",
];
for uri_str in URIS {
let uri = Uri::parse(uri_str, UriFlags::ENCODED).unwrap();
assert_eq!(uri_str, &uri.to_str()); let actual = UriBuilder::from(&uri).append_query("").build();
assert_uri_equal(&uri, &actual);
}
}
#[cfg(feature = "query")]
#[test]
fn appends_if_no_query_present() {
const URI: &str = "https://example.com";
const EXPECTED_URI: &str = "https://example.com?foo=bar";
let uri = Uri::parse(URI, UriFlags::NONE).unwrap();
assert_eq!(URI, uri.to_str()); let actual = UriBuilder::from(&uri).append_query("foo=bar").build();
assert_eq!(EXPECTED_URI, actual.to_str());
}
#[cfg(feature = "query")]
#[test]
fn appends_if_empty_query_present() {
const URI: &str = "https://example.com?";
const EXPECTED_URI: &str = "https://example.com?foo=bar";
let uri = Uri::parse(URI, UriFlags::NONE).unwrap();
assert_eq!(URI, uri.to_str()); let actual = UriBuilder::from(&uri).append_query("foo=bar").build();
assert_eq!(EXPECTED_URI, actual.to_str());
}
#[cfg(feature = "query")]
#[test]
fn appends_if_query_present() {
const URI: &str = "https://example.com?foo";
const EXPECTED_URI: &str = "https://example.com?foo&foo=bar";
let uri = Uri::parse(URI, UriFlags::NONE).unwrap();
assert_eq!(URI, uri.to_str()); let actual = UriBuilder::from(&uri).append_query("foo=bar").build();
assert_eq!(EXPECTED_URI, actual.to_str());
}
fn assert_uri_equal(left: &Uri, right: &Uri) {
assert_eq!(left.to_str(), right.to_str());
}
}