March Docs

Uri

URI module: URL/URI parsing, encoding, and query-string manipulation.

Types: URI(scheme, host, port, path, query, fragment) scheme -- e.g. "https", "http", "" if absent host -- e.g. "example.com" port -- Some(443) or None path -- e.g. "/api/users" (includes leading /) query -- raw query string e.g. "q=hello&page=2" fragment -- fragment identifier e.g. "section-1"

Percent-encoding follows RFC 3986: unreserved chars (A-Z a-z 0-9 - _ . ~) are left as-is; all other bytes are encoded as %XX (uppercase hex).

encode_query / decode_query work with List((String, String)) association lists (not Map) so this module has no stdlib dependencies.

Types

typeURIURI = URI(String, String, Option(Int), String, String, String)#

Functions

fndecodedecode(s)#

Percent-decode a URI component.

%XX sequences are decoded to their byte values; + is left unchanged
(use decode_query for form-encoded values where + means space).

    Uri.decode("hello%20world")  -- "hello world"
    Uri.decode("caf%C3%A9")      -- "café"
    Uri.decode("no-encoding")    -- "no-encoding"
fndecode_querydecode_query(s)#

Decode a URL query string into a list of key-value pairs.

Percent-encoded sequences are decoded; "+" is treated as a space
(application/x-www-form-urlencoded convention).

    Uri.decode_query("q=hello+world&page=2")
    -- [("q", "hello world"), ("page", "2")]

    Uri.decode_query("")  -- []
fnencodeencode(s)#

Percent-encode a string for safe use in a URI component.

Unreserved characters (A–Z, a–z, 0–9, -, _, ., ~) are left unchanged;
all other bytes are encoded as %XX (uppercase hex).

    Uri.encode("hello world")  -- "hello%20world"
    Uri.encode("foo=bar&baz")  -- "foo%3Dbar%26baz"
    Uri.encode("café")         -- "caf%C3%A9"
fnencode_queryencode_query(pairs)#

Encode a list of key-value pairs as a URL query string.

Keys and values are percent-encoded; pairs are joined with "&".

    Uri.encode_query([("q", "hello world"), ("page", "2")])
    -- "q=hello%20world&page=2"

    Uri.encode_query([])  -- ""
fnfragmentfragment(uri)#

Extract the fragment identifier (e.g. \

fnhosthost(uri)#

Extract the host component (e.g. \

fnmergemerge(base, relative)#

Merge a relative URI reference onto a base URI per RFC 3986 §5.2.

  • If the relative URI has a scheme, it is used as-is (absolute reference).
  • If it has an authority (host), the base scheme is kept and the
  rest comes from the relative.
- Empty relative path → keep the base path; query/fragment from
  relative if present, else from base.
- Absolute relative path (starts with `/`) → replace base path.
- Otherwise → merge with base path's directory and remove dot segments
  per RFC 3986 §5.2.4 ("Remove Dot Segments").

Dot segments that would resolve "above" the root are silently
absorbed, matching the RFC's recommendation: `Uri.merge(base,
Uri.parse("../../../x"))` yields a path of `x` (or `/x` for
authoritative base) regardless of how many `..` segments are present.
Use `merge_strict` if you need to *detect* such over-traversal.

    let base = Uri.parse("https://example.com/a/b/c")
    Uri.to_string(Uri.merge(base, Uri.parse("../d")))
    -- "https://example.com/a/d"

    Uri.to_string(Uri.merge(base, Uri.parse("//other.com/x")))
    -- "https://other.com/x"
fnmerge_strictmerge_strict(base, relative)#

Like merge, but returns Err("...") if the resolved path would escape "above" the base — that is, if the dot-segment resolver would pop an empty stack. Useful when the relative reference comes from untrusted input and you want to reject directory-traversal attempts rather than silently absorb them.

    Uri.merge_strict(base, Uri.parse("../../../etc/passwd"))
    -- Err("Uri.merge_strict: relative reference traverses above base")
fnparseparse(s)#

Parse a URI string into its components.

    Uri.parse("https://example.com:8080/api/users?q=hello#top")
    -- URI("https", "example.com", Some(8080), "/api/users", "q=hello", "top")

    Uri.parse("http://localhost/path")
    -- URI("http", "localhost", None, "/path", "", "")

    Uri.parse("/relative/path?foo=bar")
    -- URI("", "", None, "/relative/path", "foo=bar", "")

Relative URIs (no scheme) are supported; host and port will be empty/None.
fnpathpath(uri)#

Extract the path component (e.g. \

fnportport(uri)#

Extract the port as Some(n) or None.

fnqueryquery(uri)#

Extract the raw query string (e.g. \

fnschemescheme(uri)#

Extract the scheme component (e.g. \

fnto_stringto_string(uri)#

Convert a URI back to its string representation.

    Uri.to_string(URI("https", "example.com", None, "/path", "q=1", "top"))
    -- "https://example.com/path?q=1#top"

    Uri.to_string(URI("http", "localhost", Some(4000), "/", "", ""))
    -- "http://localhost:4000/"