Packages

url

Parse, build, join and escape URLs and query strings without string surgery.

import "std:url"

std:url takes URLs apart and puts them back together. Reach for it whenever a URL is more than a string you pass along: reading a query parameter, adding one, resolving a link against a base, or escaping user input before it goes into an address. Building URLs with ++ works until a value contains a space or an ampersand.

Parse a URL#

url.parse(text) returns a result holding the parts. A URL that can't be parsed is an error, not a half-filled record:

main.osl
import "std:url"

auto u = url.parse("https://ada:[email protected]:8443/a/b%20c?x=1&y=two+words&x=2#top").unwrap()
log u.scheme u.hostname u.port
log u.host
log u.user u.password
log u.path
log u.rawPath
log u.queryString
log u.fragment

log url.parse("http://[bad").isErr()
Click the code to edit it

Try changing the port, or removing the #top, and run it again: the parts that are missing come back as empty strings.

host includes the port and hostname doesn't. path is decoded and rawPath is as it was written. The parts that are absent come back as empty strings.

Query parameters#

u.query is the query string already decoded into an object. A parameter that appears once is a string, and one that repeats is an array of strings. To read one parameter from a URL string directly, use url.param, which returns null for a parameter that isn't there:

main.osl
import "std:url"

auto u = url.parse("https://example.com/s?x=1&y=two+words&x=2").unwrap()
log u.query

string link = "https://example.com/s?q=hello&n=2"
log url.param(link, "q").unwrap()
log url.param(link, "missing").unwrap()
Click the code to edit it

url.parseQuery("a=1&b=2") does the same for a bare query string, with or without the leading ?. url.stringifyQuery goes the other way, with keys sorted so the result is stable, and an array becomes a repeated key:

main.osl
import "std:url"

log url.parseQuery("?a=1&b=two+words&a=3").unwrap()
log url.stringifyQuery({b: 2, a: "x y", list: [1, 2]})
Click the code to edit it

Set parameters on a URL#

url.withParams(url, params) returns the URL with the parameters set. Existing ones with the same name are replaced and the rest are kept:

main.osl
import "std:url"

string next = url.withParams("https://example.com/s?q=1&keep=yes", {q: "new", page: 2}).unwrap()
log next
Click the code to edit it

This is the safe way to add a user's search text to a link: every value is escaped for you.

Escaping#

url.escape makes text safe to use as one query key or value, and url.unescape reverses it (a + becomes a space). Unescaping can fail on a broken % sequence, so it returns a result:

main.osl
import "std:url"

log url.escape("a b&c=d/é")
log url.unescape("a%20b+c").unwrap()
log url.unescape("100%").isErr()
Click the code to edit it

Join and build#

url.join(base, reference) resolves a link the way a browser does, so a page can turn ../api into a full address. url.build assembles a URL from the same parts parse returns:

main.osl
import "std:url"

log url.join("https://example.com/docs/guide/", "../api?v=2").unwrap()
log url.join("https://example.com/docs/guide", "/root").unwrap()
log url.build({scheme: "https", host: "example.com", path: "/a b", query: {k: "v"}, fragment: "f"})
Click the code to edit it

Checking input#

url.isValid(text) is true only for an absolute URL with a scheme and a host, so a relative path or a bare domain is rejected:

main.osl
import "std:url"

for text in ["https://example.com", "example.com", "/relative", "ftp://files.example.com/a"] (
  log text url.isValid(text)
)
Click the code to edit it

A valid URL can still use a scheme you don't want. If a form accepts links, also check scheme is http or https before following one.

Quick check

Why use url.withParams instead of joining "?q=" ++ text onto a URL?

Your turn

Next page link

Given a search URL, print the same URL with page set to 2. Keep the existing q.

main.osl
Ctrl Enter runs
Expected output
https://example.com/search?page=2&q=osl

API reference#

12 members, generated from the std:url source of OSL 9.3.0.

Functions

#url.build(object parts) string

build assembles a URL from the parts url.parse returns. Accepted keys: scheme, host, or hostname with port, path, rawPath, query (an object whose array values repeat the key) or queryString, fragment, and user with password. Every key is optional; an unknown key throws ValueError. The valid key of url.parse's result is ignored, so a parsed URL builds back.

#url.decode(any query) object

Deprecated. Deprecated: use url.parseQuery(text) instead, which fails with ParseError for malformed input.

decode reads a query string into an object, or {} when it is malformed.

#url.encode(object m) string

Deprecated. Deprecated: use url.stringifyQuery(params) instead.

encode encodes an object as a query string.

#url.escape(string text) string

escape percent-encodes text for use as a query key or value, writing spaces as "+".

#url.isValid(string text) boolean

isValid reports whether text is an absolute URL with a scheme and a host, such as "https://example.com".

#url.join(string base, string ref) result<string, error>

join resolves a relative reference against a base URL, as a browser resolves a link: join("https://x.com/a/", "b") is "https://x.com/a/b". The result always stays on base's origin, so a ref that names its own scheme or host (such as "https://other.example" or "//other.example") fails with ValueError. Fails with ParseError when either URL is malformed.

#url.param(string url, string key) result<string?, error>

param returns the first value of a query parameter in url, or null when the parameter is absent. Fails with ParseError when url is malformed.

#url.parse(string text) result<{fragment: string, host: string, hostname: string, password: string, path: string, port: string, query: object, queryString: string, rawPath: string, scheme: string, user: string, valid: boolean (deprecated)}, error>

parse splits a URL into its components. It returns {scheme, host, hostname, port, path, rawPath, query, queryString, fragment, user, password}: every key is a string ("" when the URL has no such part) except query, an object mapping each query key to its value, or to an array of values when the key repeats. path is decoded and rawPath keeps its escapes. The deprecated key valid is always true. Relative references such as "../a" parse too. Fails with ParseError for malformed input such as a bad percent-escape or port.

#url.parseQuery(string text) result<object, error>

parseQuery reads a query string such as "a=1&b=two+words" (a leading "?" is allowed) into an object mapping each key to its decoded value, or to an array of values when the key repeats. Fails with ParseError for a bad percent-escape or a ";" separator.

#url.stringifyQuery(object params) string

stringifyQuery encodes an object as a query string with sorted keys: {a: "1", b: "two words"} becomes "a=1&b=two+words". An array value repeats the key once per element; other values are converted to strings.

#url.unescape(string text) result<string, error>

unescape decodes a percent-encoded query component, turning "+" into a space. Fails with ParseError for a malformed escape such as "%zz".

#url.withParams(string url, object params) result<string, error>

withParams returns url with the given query parameters set, replacing existing values for those keys and keeping the others. An array value repeats the key. Fails with ParseError when url is malformed.