use url::Url;
use super::{ConfigError, Result};
pub(crate) fn parse_network_url(
field: &'static str,
value: &str,
schemes: &[&str],
allow_credentials: bool,
message: &'static str,
) -> Result<Url> {
let value = value.trim();
let parsed = Url::parse(value).map_err(|_| ConfigError::validation(field, message))?;
let valid_scheme = schemes.contains(&parsed.scheme());
let has_explicit_authority = value.split_once("://").is_some_and(|(scheme, authority)| {
scheme.eq_ignore_ascii_case(parsed.scheme())
&& !authority.is_empty()
&& !authority.starts_with(['/', '?', '#'])
});
let has_host = parsed.host_str().is_some_and(|host| !host.is_empty());
let has_credentials = !parsed.username().is_empty() || parsed.password().is_some();
if !valid_scheme
|| !has_explicit_authority
|| !has_host
|| (!allow_credentials && has_credentials)
{
return Err(ConfigError::validation(field, message));
}
Ok(parsed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn enforces_scheme_host_and_credential_policy() {
let message = "must be an HTTPS URL with a host and no embedded credentials";
for invalid in [
"http://service.example.invalid",
"https:///missing-host",
"https://user:secret@service.example.invalid",
"not a url",
] {
assert!(
parse_network_url("service.url", invalid, &["https"], false, message).is_err(),
"{invalid} should be rejected"
);
}
let parsed = parse_network_url(
"service.url",
"https://service.example.invalid/v1",
&["https"],
false,
message,
)
.expect("valid HTTPS endpoint should parse");
assert_eq!(parsed.host_str(), Some("service.example.invalid"));
}
}