Skip to content

ada-url/kotlin

Repository files navigation

WHATWG URL parser for Kotlin/Native

Fast WHATWG URL Specification compliant URL parser for Kotlin/Native. Built on Ada — the same C++ URL parser used by Node.js since Node 18.

The Ada library passes the full range of tests from the specification across a wide range of platforms (Linux, macOS). It fully supports the relevant Unicode Technical Standard.

Usage

Parsing a URL

Url.parse("https://example.com/path?query#hash")?.use { url ->
    println(url.href)      // https://example.com/path?query#hash
    println(url.protocol)  // https:
    println(url.host)      // example.com
    println(url.pathname)  // /path
    println(url.search)    // ?query
    println(url.hash)      // #hash
}

Url implements AutoCloseable — always call close() or use the use {} block to free the underlying native memory.

Relative URL resolution

Url.parse("/new-path", base = "https://example.com/old")?.use { url ->
    println(url.href) // https://example.com/new-path
}

Validation

Url.canParse("https://example.com") // true
Url.canParse("not a url")           // false

Mutating a URL

All setters return true on success. Pass null to setPort, setHash, or setSearch to clear the component.

Url.parse("https://example.com")?.use { url ->
    url.setPathname("/new-path")
    url.setSearch("key=value")
    url.setPort("8080")
    println(url.href) // https://example.com:8080/new-path?key=value
}

Unicode and IDNA

Internationalized domain names are handled automatically during parsing and also exposed via Idna:

Idna.toAscii("meßagefactory.ca")          // xn--meagefactory-m9a.ca
Idna.toUnicode("xn--meagefactory-m9a.ca") // meßagefactory.ca

URL search params

UrlSearchParams.parse("key=value&foo=bar").use { params ->
    println(params.get("key"))  // value
    println(params.size)        // 2

    params.append("key", "second")
    params.sort()
    println(params.toString())  // foo=bar&key=value&key=second

    params.keys().use { keys ->
        while (keys.hasNext()) println(keys.next())
    }
}

API reference

Url

Member Description
Url.parse(input, base?) Parses a URL string, optionally relative to a base. Returns null on failure.
Url.canParse(input, base?) Returns true if the input is a valid URL.
href The serialized URL.
protocol The scheme including the trailing colon (e.g. "https:").
host Host and port (e.g. "example.com:8080").
hostname Host without port (e.g. "example.com").
port Port as a string, or "" when absent.
pathname The path (e.g. "/foo/bar").
search The query string including ?, or "" when absent.
hash The fragment including #, or "" when absent.
username The username component.
password The password component.
origin The serialized origin (e.g. "https://example.com").
hostType HostType.Domain, HostType.IPv4, or HostType.IPv6.
schemeType SchemeType.Https, SchemeType.Http, SchemeType.File, etc.
components Raw byte-offset positions of each URL component as UrlComponents.
setHref(input) Replaces the entire URL.
setProtocol(input) Sets the scheme.
setHost(input) Sets the host and optional port.
setHostname(input) Sets the host without changing the port.
setPort(input?) Sets the port, or clears it when null.
setPathname(input) Sets the path.
setSearch(input?) Sets the query string, or clears it when null.
setHash(input?) Sets the fragment, or clears it when null.
setUsername(input) Sets the username.
setPassword(input) Sets the password.
hasCredentials() true when username or password is non-empty.
hasPort() true when an explicit port is present.
hasSearch() true when a query string is present.
hasHash() true when a fragment is present.
hasHostname() true when a hostname is present.
hasEmptyHostname() true when the hostname is explicitly empty.
hasNonEmptyUsername() true when the username is non-empty.
hasNonEmptyPassword() true when the password is non-empty.
hasPassword() true when a password component is present.
copy() Returns an independent deep copy.
toString() Equivalent to href.

UrlSearchParams

Member Description
UrlSearchParams.parse(input) Parses a query string (with or without a leading ?).
size Number of key/value pairs.
isEmpty true when there are no entries.
append(key, value) Adds a pair without removing existing pairs with the same key.
set(key, value) Sets the value for a key, removing any prior pairs with that key.
get(key) Returns the first value for the key, or null if not found.
getAll(key) Returns a UrlSearchParamsList of all values for the key.
has(key) true if any pair with the given key exists.
has(key, value) true if a pair with both the given key and value exists.
remove(key) Removes all pairs with the given key.
remove(key, value) Removes all pairs with both the given key and value.
reset(input) Replaces all entries by re-parsing the input string.
sort() Sorts pairs by key in Unicode code-point order (stable).
keys() Returns a UrlSearchParamsKeyIterator.
values() Returns a UrlSearchParamsValueIterator.
entries() Returns a UrlSearchParamsEntryIterator of Pair<String, String>.
toString() Serializes to application/x-www-form-urlencoded form.

UrlSearchParamsList and all iterator types implement AutoCloseable — close them when done.

Idna

Member Description
Idna.toAscii(input) Converts a Unicode domain to its Punycode ACE form.
Idna.toUnicode(input) Converts a Punycode ACE domain to its Unicode form.

Version

println(adaVersion())            // e.g. "3.4.0"
println(adaVersionComponents())  // AdaVersion(major=3, minor=4, revision=0)

Memory management

Url, UrlSearchParams, UrlSearchParamsList, and all iterator types wrap native memory and must be freed explicitly. The idiomatic pattern is use {}:

Url.parse("https://example.com")?.use { url ->
    // url is freed automatically at the end of this block
}

For long-lived objects, call close() manually:

val url = Url.parse("https://example.com") ?: error("invalid URL")
try {
    println(url.href)
} finally {
    url.close()
}

Development

Installing dependencies

macOS

brew install gradle just llvm

llvm provides clang++. Gradle 8.13+ is required; verify with gradle --version.

Ubuntu / Debian

sudo apt-get update
sudo apt-get install -y gradle clang just

If the packaged Gradle is older than 8.13, install a current version via sdkman:

curl -s "https://get.sdkman.io" | bash
sdk install gradle 8.13

Note (snap Gradle): If you installed Gradle via snap, Kotlin/Native's bundled libclang may fail to load due to a glibc version mismatch with the snap sandbox. Use the sdkman or apt version instead, or run with JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 gradle ....

Other Linux

Install Gradle via sdkman and a C++20-capable compiler via your package manager (g++ 10+ or clang++ 10+):

curl -s "https://get.sdkman.io" | bash
sdk install gradle 8.13

All platforms

A JDK 21+ is required on all platforms:

  • macOS: brew install --cask temurin
  • Ubuntu: sudo apt-get install -y openjdk-21-jdk
  • Any OS: sdkmansdk install java 21-tem

Kotlin/Native downloads its own toolchain (clang, linker, sysroots) into ~/.konan on first build automatically — no manual setup is needed beyond the C++ compiler used to compile ada.cpp.

Build

just build   # or: gradle buildAdaLib

Test

just test             # current platform
just test-linux       # Linux (linuxX64)
just test-macos-x64   # macOS Intel
just test-macos-arm64 # macOS Apple Silicon

Lint & format

just lint    # check (ktlint)
just format  # auto-fix (ktlint)

All checks (matches CI)

just check

License

This code is made available under the Apache License 2.0 as well as the MIT license.

About

Kotlin bindings for Ada URL parser

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors