GitHub
01A URL library for MoonBit v0.2.1

moonurl.

URLs read the way a browser reads them and the way RFC 3986 says to read them: one type, two grammars. Host names are internationalised, URI templates fill in and read back, and form bodies ride on the same percent coding. No sockets.

2
grammars · RFC 3986 & WHATWG
6
specifications implemented
4
URI Template levels
UTF‑8
/ NFC normalisation
≡

Two grammars, one type

Switch the reader for a single string and keep working with the result.

@

Internationalised names

UTS 46 mapping and RFC 3492 punycode turn any script into ASCII.

{ }

URI templates

RFC 6570, all four levels: expand a URI and read one back.

&

Form encoding

application/x-www-form-urlencoded as ordered, repeatable name/value pairs.

◧

Data URLs

RFC 2397 — a whole representation written inside the reference to it.

%

Percent coding

Escape and unescape by component, with the safe set each one allows.

Add the package
moon add moonbitstack/moonurl
Apache-2.0 · MoonBit Scroll down ↓ · or pick a section on the right
02Guide

From first line to the corners

Each snippet runs in a project that has only this package added.

1

Parse and read a reference

A reference is five components. Read it, then ask for any of them.

Decode & read
let url = @moonurl.Url::decode("https://leo:pw@example.test:8443/a/b?x=1#top")
url.scheme      // Some("https")
url.host        // Some("example.test")
url.port        // Some(8443)
url.query       // Some("x=1")
url.origin()    // "https://example.test:8443"
2

Resolve a relative reference

resolve applies RFC 3986 §5.4 against a base.

Resolve
let base = @moonurl.Url::decode("http://example.test/a/b/c")
base.resolve(@moonurl.Url::decode("../d"))
// http://example.test/a/d
3

Two readings of the same string

syntax defaults to rfc3986, so existing code is unchanged. The WHATWG reading normalises on the way in.

Two grammars
@moonurl.Url::decode("HTTP://EXAMPLE.test:80/a/../b").encode()
// "HTTP://EXAMPLE.test:80/a/../b"

@moonurl.Url::decode("HTTP://EXAMPLE.test:80/a/../b", syntax=@moonurl.Whatwg).encode()
// "http://example.test/b"
4

Names in any script

A host name goes through UTS 46; an ASCII name is taken as it stands.

Internationalised host
@moonurl.domain("MÜNCHEN.example")                        // "xn--mnchen-3ya.example"
@moonurl.Url::decode("http://192.0x00A80001/", syntax=@moonurl.Whatwg).host
// Some("192.168.0.1")
5

Fill in a template, read it back

All four levels; capture reverses expand.

Templates
let values : Map[String, @template.Value] = Map([])
values.set("id", @template.Value::Text("42"))
values.set("page", @template.Value::Text("2"))

@template.Template::decode("/users/{id}{?page}").expand(values)
// "/users/42?page=2"
@template.Template::decode("/users/{id}{?page}").capture("/users/42?page=2")
// [("id", "42"), ("page", "2")]
6

Form bodies and data URLs

A URL's query and a form body are the same ordered pairs.

Forms & data URLs
let q = @moonurl.Query::decode("a=1&b=2&a=3")
q.get("a")        // Some("1")
q.values("a")     // ["1", "3"]
q.encode()        // "a=1&a=3&b=2"

@moonurl.dataurl.Url::decode("data:text/plain,Hi%20there").data
// b"Hi there"
Run the examples: moon run src/examples/tour Source on GitHub ↗
03Reference

API

Public items, grouped by package. Signatures match the compiled interface.

moonurl Url · Query · percent coding

Parsing, recomposing, resolving, normalising, origins, setters; percent coding by component; form bodies.
+Url::decode(text, syntax?, base?) -> Url raise Refused
Read a reference in either grammar. syntax defaults to rfc3986; base resolves a relative reference.
+Url::encode() -> String
Write the reference back out.
+Url::resolve(reference) -> Url
Resolve against this base (RFC 3986 §5.4).
+Url::normalize() -> Url
Case, escapes and dot segments (§6.2.2).
+Url::origin() -> String
scheme://host:port, or "null".
+Url::authority() -> String?
The authority, brackets restored.
+Url::is_absolute() -> Bool
A scheme and no fragment.
+Url::params() -> Query raise Refused
The query as ordered pairs.
+Url::set_scheme / set_username / set_password / set_host / set_hostname / set_port / set_path / set_query / set_fragment
Nine setters: one component changed, the rest kept.
+Url::new(..) -> Url
A reference built piece by piece.
+domain(text, strict?) -> String raise Refused
A host name in ASCII (UTS 46).
+escape / escape_text / unescape / unescape_text
Percent-encode octets or text, and undo it.
+escaped(text, kind?) / normalize_escapes(text) / remove_dots(path)
Check completeness; upper-case hex; drop . and .. segments.
+Query::decode(bytes, limits?) / encode() / get / values / has / add / set / remove / entries / new
Ordered pairs; + is a space; serialiser keeps only letters, digits and *-._.

moonurl/idna punycode · UTS 46

A name mapped to ASCII and back: the mapping table, NFC, hyphen and joiner rules, the bidi rule, and DNS lengths.
+to_ascii(text, options?) -> Outcome
A name in ASCII, plus any faults the standard recorded.
+to_unicode(text, options?) -> Outcome
A name back in Unicode, plus any faults.
+encode(label) -> String / decode(label) -> String raise Refused
One label of RFC 3492 punycode.
+options : Options / strict : Options
The lenient default and the strict (DNS-length) preset.
+Outcome { name, errors } / Fault / Options
is_ok() tells you; errors lists what the standard objected to.

moonurl/template RFC 6570

Holes named and filled: simple and reserved expansions, label, path and parameter operators, prefixes and exploded lists and pairs.
+Template::decode(text) -> Template raise Refused
Read a template.
+Template::expand(values) -> String
Fill it in. Any Variables works; a Map[String, Value] already does.
+Template::capture(uri) -> Array[(String, String)]?
Read a URI back into the names that made it.
+Value :: Text / List / Pairs
A scalar, a list, or a list of pairs fed to the holes.

moonurl/dataurl RFC 2397

A whole representation written inside the reference to it.
+Url::decode(text, strict?) -> Url raise Refused
Read a data URL.
+Url::encode(base64?) -> String / new(Bytes, kind?)
Write it back; choose base64 or the URL-safe form.
+Url::charset() -> String
The media type's charset, defaulting to US-ASCII.
Errors are one enum per package — Refused — every offset named at. Source on GitHub ↗
04Showcase Live & hands-on

See moonurl think

The same input, read several ways — then the tools to try your own.

⇄

Two grammars

One decoder, two standards. WHATWG normalises on the way in; RFC 3986 leaves the dots and the case alone.

RFC 3986http://EXAMPLE.com/A/../b
WHATWGhttp://example.com/b
@

Any script

UTS 46 maps and normalises; RFC 3492 punycode shrinks it to ASCII that DNS understands.

UnicodeMÜNCHEN.example
ASCIIxn--mnchen-3ya.example
{ }

Templates

Fill a hole, read it back. expand and capture are exact inverses.

Template/users/{id}{?page}
Filled/users/42?page=2

Try it yourself

Internationalised name

Type a host name. Non-ASCII labels become xn-- under UTS 46.

URI Template

Give a template and variables as JSON. Lists and objects become lists and pairs.

Take a URL apart

The same string by either grammar: RFC 3986, or the WHATWG URL Standard a browser follows.

The demos run moonurl itself, built to JavaScript; nothing leaves your browser. Source on GitHub ↗