RFC3986 compliant URI parsing library for MoonBit
{
"deps": {
"username/uri": "^0.1.0"
}
}///|
test "quick_start_example" {
// Parse a URI
let uri = @uri.parse("https://example.com:8080/path?query=value#fragment")
debug_inspect(uri.scheme(), content="Some(\"https\")")
debug_inspect(uri.host(), content="Some(\"example.com\")")
debug_inspect(uri.port(), content="Some(8080)")
inspect(uri.path(), content="/path")
debug_inspect(uri.query(), content="Some(\"query=value\")")
debug_inspect(uri.fragment(), content="Some(\"fragment\")")
// Build a URI using the builder methods
let built_uri = @uri.empty()
.with_scheme(Some("https"))
.with_host(Some("api.example.com"))
.with_path("/v1/users")
.with_query(Some("limit=10&offset=0"))
inspect(
built_uri.to_string(),
content="https://api.example.com/v1/users?limit=10&offset=0",
)
}///|
test "of_string_example" {
let uri = @uri.parse("https://example.com/path")
debug_inspect(uri.host(), content="Some(\"example.com\")")
}///|
test "to_string_example" {
let uri = @uri.parse("https://example.com/path")
let uri_string = uri.to_string()
inspect(uri_string, content="https://example.com/path")
}///|
test "normalize_example" {
let uri = @uri.parse("https://example.com:443/path")
let normalized = uri.normalize()
// Default HTTPS port (443) should be removed
debug_inspect(normalized.port(), content="None")
debug_inspect(normalized.host(), content="Some(\"example.com\")")
}///|
test "resolve_example" {
let base = @uri.parse("https://example.com/dir/")
let relative = @uri.parse("../other/file.html")
let resolved = @uri.resolve(base, relative)
inspect(resolved.to_string(), content="https://example.com/other/file.html")
}///|
test "parse_http_uri" {
let uri = @uri.parse(
"https://user:pass@example.com:8080/path?query=value#section",
)
debug_inspect(uri.scheme(), content="Some(\"https\")")
debug_inspect(uri.host(), content="Some(\"example.com\")")
debug_inspect(uri.port(), content="Some(8080)")
inspect(uri.path(), content="/path")
debug_inspect(uri.query(), content="Some(\"query=value\")")
debug_inspect(uri.fragment(), content="Some(\"section\")")
}///|
test "build_api_uri" {
let api_uri = @uri.empty()
.with_scheme(Some("https"))
.with_host(Some("api.github.com"))
.with_path("/repos/owner/repo/issues")
.with_query(Some("state=open&per_page=50"))
inspect(
api_uri.to_string(),
content="https://api.github.com/repos/owner/repo/issues?state=open&per_page=50",
)
}///|
test "resolve_relative_uri" {
let base = @uri.parse("https://example.com/docs/guide/")
let relative = @uri.parse("../api/reference.html")
let resolved = @uri.resolve(base, relative)
inspect(
resolved.to_string(),
content="https://example.com/docs/api/reference.html",
)
}///|
test "query_parameters" {
let uri = @uri.parse("https://search.example.com/?q=moonbit&lang=en&safe=on")
match uri.query() {
Some(query_str) => {
inspect(query_str, content="q=moonbit&lang=en&safe=on")
// Use built-in query parameter methods
let q_param = uri.get_query_param("q")
debug_inspect(q_param, content="Some(\"moonbit\")")
let lang_param = uri.get_query_param("lang")
debug_inspect(lang_param, content="Some(\"en\")")
}
None => inspect("Should have query", content="\"Should have query\"")
}
}///|
test "ipv6_uri" {
let uri = @uri.parse("http://[2001:db8::1]:8080/path")
debug_inspect(uri.scheme(), content="Some(\"http\")")
debug_inspect(uri.host(), content="Some(\"[2001:db8::1]\")")
debug_inspect(uri.port(), content="Some(8080)")
inspect(uri.path(), content="/path")
}///|
test "uri_normalization" {
let uri = @uri.parse("https://example.com:443/./path/../other/./file.html")
let normalized = uri.normalize()
// Default HTTPS port (443) should be removed
debug_inspect(normalized.port(), content="None")
// Path should be normalized
inspect(normalized.path(), content="/other/file.html")
inspect(normalized.to_string(), content="https://example.com/other/file.html")
}///|
test "error_handling_example" {
// Test with a valid but unusual URI
let uri = @uri.parse("custom://example.com")
debug_inspect(uri.scheme(), content="Some(\"custom\")")
debug_inspect(uri.host(), content="Some(\"example.com\")")
}moon testpub suberror UriError {
InvalidScheme(String)
InvalidAuthority(String)
InvalidPath(String)
InvalidQuery(String)
InvalidFragment(String)
InvalidPort(String)
EmptyUri
} derive(ToJson)// Parse a URI
let uri = @uri.parse("https://user:pass@example.com:8080/path?query=value#fragment")
// Access components
inspect(uri.scheme(), content="Some(\"https\")")
inspect(uri.host(), content="Some(\"example.com\")")
inspect(uri.port(), content="Some(8080)")
inspect(uri.path(), content="/path")
// Modify URI (immutable)
let new_uri = uri.with_host(Some("newhost.com")).with_port(Some(9000))
inspect(new_uri.to_string(), content="https://user:pass@newhost.com:9000/path?query=value#fragment")
// Query parameter helpers
let search_uri = @uri.parse("https://example.com/search")
let with_params = search_uri.with_query_param("q", "moonbit").with_query_param("lang", "en")
inspect(with_params.to_string(), content="https://example.com/search?q=moonbit&lang=en")let query = @uri.Uri::build_query([("name", "John Doe"), ("age", "30")])
inspect(query, content="name=John%20Doe&age=30")// Basic decoding
let decoded = @uri.Uri::decode("hello%20world%21")
inspect(decoded, content="hello world!")
// Decoding special characters
let email = @uri.Uri::decode("user%40domain.com")
inspect(email, content="user@domain.com")
// Mixed encoded and unencoded content
let mixed = @uri.Uri::decode("path%2Fto%2Fresource")
inspect(mixed, content="path/to/resource")
// Invalid sequences are preserved
let invalid = @uri.Uri::decode("hello%ZZ%20world")
inspect(invalid, content="hello%ZZ world")
// Already decoded strings remain unchanged
let plain = @uri.Uri::decode("hello world")
inspect(plain, content="hello world")// Basic encoding with spaces and punctuation
let encoded = @uri.Uri::encode("hello world!")
inspect(encoded, content="hello%20world%21")
// Encoding special characters
let special = @uri.Uri::encode("user@domain.com")
inspect(special, content="user%40domain.com")
// Encoding query parameter values
let query_value = @uri.Uri::encode("search term with spaces")
inspect(query_value, content="search%20term%20with%20spaces")
// Safe characters remain unchanged
let safe = @uri.Uri::encode("ABCabc123-_.~")
inspect(safe, content="ABCabc123-_.~")// Fragment in HTML document
let uri = @uri.parse("https://example.com/page.html#section1")
inspect(uri.fragment(), content="Some(\"section1\")")
// Fragment with encoded characters
let encoded_fragment = @uri.parse("https://example.com/doc#user%20guide")
inspect(encoded_fragment.fragment(), content="Some(\"user%20guide\")")
// No fragment
let no_fragment = @uri.parse("https://example.com/page.html")
inspect(no_fragment.fragment(), content="None")
// Empty fragment
let empty_fragment = @uri.parse("https://example.com/page.html#")
inspect(empty_fragment.fragment(), content="Some(\"\")")// Domain name
let uri = @uri.parse("https://example.com:8080/path")
inspect(uri.host(), content="Some(\"example.com\")")
// IPv4 address
let ipv4_uri = @uri.parse("http://192.168.1.1/api")
inspect(ipv4_uri.host(), content="Some(\"192.168.1.1\")")
// IPv6 address (with brackets)
let ipv6_uri = @uri.parse("http://[2001:db8::1]:8080/path")
inspect(ipv6_uri.host(), content="Some(\"[2001:db8::1]\")")
// Relative URI without host
let relative_uri = @uri.parse("/path/to/resource")
inspect(relative_uri.host(), content="None")let params = @uri.Uri::parse_query("name=John&age=30&city=New%20York")
inspect(params.length(), content="3")// Absolute path
let uri = @uri.parse("https://example.com/path/to/resource")
inspect(uri.path(), content="/path/to/resource")
// Root path
let root_uri = @uri.parse("https://example.com/")
inspect(root_uri.path(), content="/")
// Empty path (defaults to empty string)
let no_path_uri = @uri.parse("https://example.com")
inspect(no_path_uri.path(), content="")
// Relative path
let relative_uri = @uri.parse("path/to/resource")
inspect(relative_uri.path(), content="path/to/resource")
// Path with encoded characters
let encoded_uri = @uri.parse("https://example.com/path%20with%20spaces")
inspect(encoded_uri.path(), content="/path%20with%20spaces")let uri = @uri.parse("/path/to/resource")
let _segments = uri.path_segments()
// _segments will contain ["path", "to", "resource"]// Explicit port
let uri = @uri.parse("https://example.com:8080/path")
inspect(uri.port(), content="Some(8080)")
// No explicit port (uses scheme default)
let default_uri = @uri.parse("https://example.com/path")
inspect(default_uri.port(), content="None")
// Different port numbers
let custom_port = @uri.parse("http://localhost:3000/api")
inspect(custom_port.port(), content="Some(3000)")
// IPv6 with port
let ipv6_uri = @uri.parse("http://[::1]:8080/path")
inspect(ipv6_uri.port(), content="Some(8080)")// Query with multiple parameters
let uri = @uri.parse("https://example.com/path?key=value&foo=bar")
inspect(uri.query(), content="Some(\"key=value&foo=bar\")")
// Single parameter
let single_param = @uri.parse("https://example.com/search?q=moonbit")
inspect(single_param.query(), content="Some(\"q=moonbit\")")
// Empty query
let empty_query = @uri.parse("https://example.com/path?")
inspect(empty_query.query(), content="Some(\"\")")
// No query
let no_query = @uri.parse("https://example.com/path")
inspect(no_query.query(), content="None")
// Query with encoded characters
let encoded_query = @uri.parse("https://example.com/search?q=hello%20world")
inspect(encoded_query.query(), content="Some(\"q=hello%20world\")")// Absolute URI with scheme
let uri = @uri.parse("https://example.com")
inspect(uri.scheme(), content="Some(\"https\")")
// Different schemes
let ftp_uri = @uri.parse("ftp://files.example.com/file.txt")
inspect(ftp_uri.scheme(), content="Some(\"ftp\")")
// Relative URI without scheme
let relative_uri = @uri.parse("/path/to/resource")
inspect(relative_uri.scheme(), content="None")let uri = @uri.parse("https://user:pass@example.com")
match uri.userinfo_components() {
Some((user, Some(pass))) => {
inspect(user, content="user")
inspect(pass, content="pass")
}
_ => ()
}// Change host
let uri = @uri.parse("https://old-host.com/path")
let new_uri = uri.with_host(Some("new-host.com"))
inspect(new_uri.to_string(), content="https://new-host.com/path")
// Add host to relative URI
let relative = @uri.parse("/path/to/resource")
let with_host = relative.with_host(Some("example.com"))
inspect(with_host.to_string(), content="example.com/path/to/resource")
// Remove host (makes URI relative)
let absolute = @uri.parse("https://example.com/path")
let relative_uri = absolute.with_host(None)
inspect(relative_uri.to_string(), content="https:/path")let uri = @uri.empty().with_path_segments(["api", "v1", "users"])
inspect(uri.path(), content="/api/v1/users")// Change HTTP to HTTPS
let http_uri = @uri.parse("http://example.com/path")
let https_uri = http_uri.with_scheme(Some("https"))
inspect(https_uri.to_string(), content="https://example.com/path")
// Remove scheme (make relative)
let absolute_uri = @uri.parse("https://example.com/path")
let relative_uri = absolute_uri.with_scheme(None)
inspect(relative_uri.to_string(), content="example.com/path")
// Add scheme to relative URI
let relative = @uri.parse("/path/to/resource")
let absolute = relative.with_scheme(Some("https"))
inspect(absolute.to_string(), content="https:/path/to/resource")let _uri = @uri.empty()
.with_host(Some("example.com"))
.with_userinfo(Some("user"), Some("pass"))
// Creates URI with encoded userinfolet uri = @uri.empty()
inspect(uri.path(), content="")
inspect(uri.scheme(), content="None")let uri = @uri.parse("https://user:pass@example.com:8080/path?query=value#fragment")
inspect(uri.scheme(), content="Some(\"https\")")
inspect(uri.host(), content="Some(\"example.com\")")
inspect(uri.port(), content="Some(8080)")// Resolve relative path
let base = @uri.parse("https://example.com/docs/guide/")
let relative = @uri.parse("../api/reference.html")
let resolved = @uri.resolve(base, relative)
inspect(resolved.to_string(), content="https://example.com/docs/api/reference.html")
// Resolve absolute path (replaces base path)
let base2 = @uri.parse("https://example.com/old/path")
let absolute_path = @uri.parse("/new/path")
let resolved2 = @uri.resolve(base2, absolute_path)
inspect(resolved2.to_string(), content="https://example.com/new/path")
// Absolute URI returns unchanged
let base3 = @uri.parse("https://example.com/")
let absolute_uri = @uri.parse("https://other.com/path")
let resolved3 = @uri.resolve(base3, absolute_uri)
inspect(resolved3.to_string(), content="https://other.com/path")Install
Download zipRFC3986 compliant URI parsing library for MoonBit