#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UriParts {
pub scheme: Option<String>,
pub authority: Option<String>,
pub path: String,
pub query: Option<String>,
pub fragment: Option<String>,
}
#[must_use]
pub fn looks_like_uri(input: &str) -> bool {
let trimmed = input.trim();
!trimmed.is_empty() && (has_scheme(trimmed) || trimmed.starts_with("//"))
}
#[must_use]
pub fn parse_uri_basic(input: &str) -> UriParts {
let trimmed = input.trim();
let scheme = extract_scheme(trimmed);
let mut remainder = if let Some(scheme) = scheme.as_deref() {
&trimmed[scheme.len() + 1..]
} else {
trimmed
};
let authority = if let Some(after_slashes) = remainder.strip_prefix("//") {
let end = after_slashes
.find(['/', '?', '#'])
.unwrap_or(after_slashes.len());
remainder = &after_slashes[end..];
let value = &after_slashes[..end];
(!value.is_empty()).then(|| value.to_string())
} else {
None
};
let fragment_index = remainder.find('#');
let without_fragment = &remainder[..fragment_index.unwrap_or(remainder.len())];
let query_index = without_fragment.find('?');
let path = without_fragment[..query_index.unwrap_or(without_fragment.len())].to_string();
let query = query_index.map(|index| without_fragment[index + 1..].to_string());
let fragment = fragment_index.map(|index| remainder[index + 1..].to_string());
UriParts {
scheme,
authority,
path,
query,
fragment,
}
}
#[must_use]
pub fn has_scheme(input: &str) -> bool {
extract_scheme(input).is_some()
}
#[must_use]
pub fn extract_scheme(input: &str) -> Option<String> {
let trimmed = input.trim();
let colon_index = trimmed.find(':')?;
let candidate = &trimmed[..colon_index];
if candidate.is_empty() {
return None;
}
let mut chars = candidate.chars();
let first = chars.next()?;
if !first.is_ascii_alphabetic() {
return None;
}
if chars
.all(|character| character.is_ascii_alphanumeric() || matches!(character, '+' | '-' | '.'))
{
Some(candidate.to_ascii_lowercase())
} else {
None
}
}
#[must_use]
pub fn extract_fragment(input: &str) -> Option<String> {
let fragment_index = input.find('#')?;
Some(input[fragment_index + 1..].to_string())
}
#[must_use]
pub fn strip_fragment(input: &str) -> &str {
match input.find('#') {
Some(index) => &input[..index],
None => input,
}
}
#[must_use]
pub fn extract_query(input: &str) -> Option<String> {
let without_fragment = strip_fragment(input);
let query_index = without_fragment.find('?')?;
Some(without_fragment[query_index + 1..].to_string())
}
#[must_use]
pub fn strip_query(input: &str) -> &str {
match input.find('?') {
Some(index) => &input[..index],
None => input,
}
}