Skip to content

URI Parsing and Resolution (RFC 3986)

This package parses URI-references into structured components and resolves relative references against base URIs per RFC 3986.

Entry Points

Use ParseURI for standard URI-references (absolute URIs and relative references — covers origin-form, absolute-form, and asterisk-form HTTP request-targets):

match ParseURI("/index.html?page=1")
| let u: URI val => // u.path, u.query, etc.
| let e: URIParseError val => // handle error
end

Use ResolveURI to resolve a relative reference against a base URI (RFC 3986 section 5):

match (ParseURI("http://example.com/a/b"), ParseURI("../c"))
| (let base: URI val, let ref': URI val) =>
  match ResolveURI(base, ref')
  | let target: URI val => // target.string() == "http://example.com/a/c"
  | let e: ResolveURIError val => // base was not absolute
  end
end

Use ParseURIAuthority for HTTP CONNECT authority-form targets (host:port without scheme or //):

match ParseURIAuthority("example.com:443")
| let a: URIAuthority val => // a.host, a.port
| let e: URIParseError val => // handle error
end

Use NormalizeURI to apply RFC 3986 section 6 normalization (case, percent-encoding, dot segments, default port removal, empty path):

match ParseURI("HTTP://Example.COM:80/%7Euser/a/../b")
| let u: URI val =>
  match NormalizeURI(u)
  | let n: URI val => // n.string() == "http://example.com/~user/b"
  | let e: InvalidPercentEncoding val => // malformed percent-encoding
  end
end

Use URIEquivalent to test whether two URIs are equivalent under normalization:

match (ParseURI("HTTP://Example.COM:80/path"), ParseURI("http://example.com/path"))
| (let a: URI val, let b: URI val) =>
  match URIEquivalent(a, b)
  | let eq: Bool => // eq == true
  end
end

Use URIBuilder to construct a URI from raw (unencoded) components with automatic percent-encoding, or to modify an existing URI:

match URIBuilder
  .set_scheme("https")
  .set_host("example.com")
  .set_path("/api/users")
  .add_query_param("name", "Jane Doe")
  .build()
| let u: URI val =>
  // u.string() == "https://example.com/api/users?name=Jane%20Doe"
| let e: URIBuildError val =>
  // handle error
end

To modify an existing URI, start from URIBuilder.from(uri) and change only the components you need:

match ParseURI("https://example.com/old?x=1")
| let u: URI val =>
  match URIBuilder.from(u)
    .set_path("/new")
    .add_query_param("y", "2")
    .build()
  | let modified: URI val => // "https://example.com/new?x=1&y=2"
  end
end

For query parameter access, URI.query_params() is the simplest path — it returns a QueryParams collection with get(), get_all(), and contains() methods for key-based lookup. Use ParseQueryParameters directly on the query field when you need to distinguish "no query" from "invalid percent-encoding." Use PercentDecode/PercentEncode for encoding operations, PathSegments for decoded path segment access, and RemoveDotSegments for standalone path normalization.

For URI template expansion (RFC 6570), use the uri/template subpackage.

IRI Support (RFC 3987)

ParseURI, ResolveURI, PercentDecode, and PercentEncode handle IRIs natively — ParseURI only looks for ASCII structural delimiters, so non-ASCII bytes pass through correctly, and URI stores components as String (UTF-8).

The IRI-specific primitives handle conversion between IRI and URI forms:

Use IRIToURI to convert an IRI to a URI by percent-encoding all non-ASCII bytes. Use URIToIRI for the reverse — selectively decoding percent-encoded UTF-8 sequences that are valid ucschar (or iprivate in query).

Use IRIPercentEncode instead of PercentEncode when constructing IRIs from unencoded text — it preserves ucschar codepoints as literal UTF-8 while applying the same ASCII encoding rules.

Use NormalizeIRI for IRI-aware normalization (applies NormalizeURI then decodes ucschar sequences back to literal UTF-8). Use IRIEquivalent to test equivalence across IRI and URI forms.

Planned Features

  • URI Manipulation - Higher-level URI manipulation beyond URIBuilder

Public Types