/// URI component parsing (scheme, query, fragment)
///| Find first occurrence of character in string
fn find_char_in_str(s : String, c : Char) -> Int? {
let chars = s.to_array()
for i = 0; i < chars.length(); i = i + 1 {
if chars[i] == c {
return Some(i)
}
}
None
}
///| Parse scheme: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
pub fn parse_scheme(s : String) -> URIResult[String] {
if s.is_empty() {
Err(InvalidScheme("Scheme cannot be empty"))
} else {
let chars = s.to_array()
// First character must be ALPHA
if not(is_alpha(chars[0])) {
Err(InvalidScheme("Scheme must start with a letter"))
} else {
// Check remaining characters
let mut valid = true
for i = 1; i < chars.length(); i = i + 1 {
if not(is_scheme_char(chars[i])) {
valid = false
}
}
if valid {
Ok(s.to_lower()) // Schemes are case-insensitive, normalize to lowercase
} else {
Err(InvalidScheme("Invalid characters in scheme"))
}
}
}
}
///| Parse query: *( pchar / "/" / "?" )
pub fn parse_query(s : String) -> URIResult[String] {
if is_valid_encoded_string(s, is_query_char) {
Ok(s)
} else {
Err(InvalidQuery("Invalid characters in query"))
}
}
///| Parse fragment: *( pchar / "/" / "?" )
pub fn parse_fragment(s : String) -> URIResult[String] {
if is_valid_encoded_string(s, is_fragment_char) {
Ok(s)
} else {
Err(InvalidFragment("Invalid characters in fragment"))
}
}
///| Split query string into key-value pairs
pub fn parse_query_params(query : String) -> Array[(String, String)] {
if query.is_empty() {
return []
}
let pairs = query.split("&")
let result : Array[(String, String)] = []
for pair in pairs {
let pair_str = pair.to_string()
if pair_str.contains("=") {
let eq_pos = find_char_in_str(pair_str, '=')
match eq_pos {
Some(pos) => {
let key = pair_str.substring(start=0, end=pos)
let value = pair_str.substring(start=pos + 1)
// Decode percent-encoded values
match (percent_decode(key), percent_decode(value)) {
(Ok(decoded_key), Ok(decoded_value)) =>
result.push((decoded_key, decoded_value))
_ =>
// If decoding fails, keep original values
result.push((key, value))
}
}
None => () // Should not happen since we checked contains
}
} else {
// Key without value
match percent_decode(pair_str) {
Ok(decoded_key) => result.push((decoded_key, ""))
Err(_) => result.push((pair_str, ""))
}
}
}
result
}
///| Build query string from key-value pairs
pub fn build_query_string(params : Array[(String, String)]) -> String {
if params.is_empty() {
return ""
}
let parts : Array[String] = []
for i = 0; i < params.length(); i = i + 1 {
let (key, value) = params[i]
let encoded_key = encode_query(key)
if value.is_empty() {
parts.push(encoded_key)
} else {
let encoded_value = encode_query(value)
parts.push(encoded_key + "=" + encoded_value)
}
}
parts.join("&")
}
///| Validate that all URI components are properly formed
pub fn validate_uri_components(
scheme : String,
authority : Authority?,
path : String,
query : String?,
fragment : String?
) -> URIResult[Unit] {
// Validate scheme
match parse_scheme(scheme) {
Ok(_) => ()
Err(e) => return Err(e)
}
// Validate query if present
match query {
Some(q) =>
match parse_query(q) {
Ok(_) => ()
Err(e) => return Err(e)
}
None => ()
}
// Validate fragment if present
match fragment {
Some(f) =>
match parse_fragment(f) {
Ok(_) => ()
Err(e) => return Err(e)
}
None => ()
}
// Authority validation is done during parsing
// Path validation depends on whether authority is present
match authority {
Some(_) =>
// With authority, path must be path-abempty
match validate_path_abempty(path) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
None =>
// Without authority, validate based on path type
match classify_and_validate_path(path) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
}
///| Case-insensitive scheme comparison
pub fn schemes_equal(scheme1 : String, scheme2 : String) -> Bool {
scheme1.to_lower() == scheme2.to_lower()
}
///| Normalize URI component case (schemes and host names are case-insensitive)
pub fn normalize_uri_case(uri : URI) -> URI {
let normalized_authority = match uri.authority {
Some(auth) => {
let normalized_host = match auth.host {
HostType::RegName(name) => HostType::RegName(name.to_lower())
other => other
}
Some({ ..auth, host: normalized_host })
}
None => None
}
{ ..uri, scheme: uri.scheme.to_lower(), authority: normalized_authority }
}