///|
pub struct URL {
protocol : String?
host : String?
port : Int?
path : String
query : Map[String, String]?
fragment : String?
} derive(Show)
///|
pub fn URL::parse(rawUrl : String) -> URL {
let url = rawUrl.trim_space()
let scheme_end = url.find("://").unwrap_or(0)
let fragment_start = url.find("#").unwrap_or(-1)
let query_start = url.find("?").unwrap_or(-1)
let query_end = if fragment_start != -1 {
fragment_start
} else {
url.length()
}
let port_start = if scheme_end + 3 < url.length() {
url.view(start_offset=scheme_end + 3).find(":").unwrap_or(-1)
} else {
-1
}
let port_end = if port_start != -1 {
url.view(start_offset=port_start).find("/").unwrap_or(-1)
} else {
0
}
let protocol = if scheme_end != -1 {
Some(url.view(start_offset=0, end_offset=scheme_end).to_string())
} else {
None
}
let host_start = if scheme_end != -1 { scheme_end + 3 } else { 0 }
let host_end = if port_start != -1 {
port_start
} else if query_start != -1 {
query_start
} else if fragment_start != -1 {
fragment_start
} else {
url.length()
}
let host = if url.contains(".") {
Some(url.view(start_offset=host_start, end_offset=host_end).to_string())
} else {
None
}
let port_str = url.view(start_offset=port_start + 1, end_offset=port_end)
let port = if port_str.length() > 0 {
Some(@strconv.parse_int(port_str.to_string())) catch {
_ => None
}
} else {
None
}
let path_start = if port_end != -1 {
port_end
} else if query_start != -1 {
query_start
} else if fragment_start != -1 {
fragment_start
} else {
url.length()
}
let path_end = if query_start != -1 {
query_start
} else if fragment_start != -1 {
fragment_start
} else {
url.length()
}
// println("path_Start: \(path_start), path_End: \(path_end)")
let path = url.view(start_offset=path_start, end_offset=path_end).to_string()
let query = if query_start != -1 {
let query_str = url.view(start_offset=query_start + 1, end_offset=query_end)
Some(query_map(query_str.to_string()))
} else {
None
}
let fragment = if fragment_start != -1 {
Some(
url
.view(start_offset=fragment_start + 1, end_offset=url.length())
.to_string(),
)
} else {
None
}
return { protocol, host, port, path, query, fragment }
}
///|
fn query_map(query_str : String) -> Map[String, String] {
let query_array = query_str.split("&")
query_array.map(item => {
if item.split("=").to_array() is [key, value] {
let key = key.to_string()
let value = value.to_string()
return (key, value)
}
return (item.to_string(), "")
})
|> Map::from_iter
}
///|
pub fn URL::output(self : URL, logger : Logger) -> Unit {
logger.debug(fn() { self.to_string() })
}
///|
pub fn URL::to_string(self : URL) -> String {
let protocol = if self.protocol is Some(p) { p + "://" } else { "" }
let host = if self.host is Some(h) { h } else { "" }
let port = self.port
let path = self.path
let query = self.query
let fragment = self.fragment
let port_str = if port is Some(p) { ":" + p.to_string() } else { "" }
let query_str = if query is Some(q) {
"?" +
q
.to_array()
.map(item => {
let (k, v) = item
k + "=" + v
})
.join("&")
} else {
""
}
let fragment_str = if fragment is Some(f) { "#" + f } else { "" }
protocol + host + port_str + path + query_str + fragment_str
}
///|
test "parse_url" {
let url = "/path/to/resource?name=ferret&color=purple#section1"
let result = URL::parse(url)
// println(url)
// println(result)
inspect(result.path, content="/path/to/resource")
}
///|
test "empty_url" {
let url = ""
let result = URL::parse(url)
// println(url)
// println(result)
inspect(result.path, content="")
}