///|
/// Joins a base URL and a path with exactly one separating slash, so
/// `join_url("http://localhost:8000/", "/api/v2")` is
/// `http://localhost:8000/api/v2`.
pub fn join_url(base : String, path : String) -> String {
let trimmed = base.trim_end(chars="/").to_owned()
let suffix = path.trim_start(chars="/").to_owned()
if suffix.length() == 0 {
trimmed
} else {
trimmed + "/" + suffix
}
}
///|
/// Percent-encodes a single path segment.
///
/// Collection names may contain spaces and other characters that are not
/// legal in a URL path, so every dynamic segment goes through here.
pub fn encode_path_segment(segment : String) -> String {
percent_encode(segment, space_as_plus=false)
}
///|
/// Percent-encodes a query component. Unlike a path segment a space may be
/// written as `+` here.
pub fn encode_query_component(value : String) -> String {
percent_encode(value, space_as_plus=true)
}
///|
/// Appends `key=value` pairs to a URL, percent-encoding both sides.
pub fn append_query(url : String, params : Array[(String, String)]) -> String {
if params.length() == 0 {
return url
}
let out = StringBuilder()
out.write_string(url)
out.write_string(if url.contains("?") { "&" } else { "?" })
let mut first = true
for param in params {
let (key, value) = param
if !first {
out.write_string("&")
}
first = false
out.write_string(encode_query_component(key))
out.write_string("=")
out.write_string(encode_query_component(value))
}
out.to_string()
}
///|
fn percent_encode(value : String, space_as_plus~ : Bool) -> String {
let out = StringBuilder()
for c in value {
match c {
'a'..<='z' | 'A'..<='Z' | '0'..<='9' | '-' | '_' | '.' | '~' =>
out.write_char(c)
' ' if space_as_plus => out.write_char('+')
_ => write_percent_encoded(out, c)
}
}
out.to_string()
}
///|
/// Writes the UTF-8 bytes of a character as `%XX` escapes.
fn write_percent_encoded(out : StringBuilder, c : Char) -> Unit {
let code = c.to_int()
if code < 0x80 {
write_hex_byte(out, code)
} else if code < 0x800 {
write_hex_byte(out, 0xC0 | (code >> 6))
write_hex_byte(out, 0x80 | (code & 0x3F))
} else if code < 0x10000 {
write_hex_byte(out, 0xE0 | (code >> 12))
write_hex_byte(out, 0x80 | ((code >> 6) & 0x3F))
write_hex_byte(out, 0x80 | (code & 0x3F))
} else {
write_hex_byte(out, 0xF0 | (code >> 18))
write_hex_byte(out, 0x80 | ((code >> 12) & 0x3F))
write_hex_byte(out, 0x80 | ((code >> 6) & 0x3F))
write_hex_byte(out, 0x80 | (code & 0x3F))
}
}
///|
/// Writes one byte as `%XX`.
fn write_hex_byte(out : StringBuilder, value : Int32) -> Unit {
let digits : Array[String] = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
]
out.write_string("%")
out.write_string(digits[(value >> 4) & 0xF])
out.write_string(digits[value & 0xF])
}