nupp.io.uri
nupp.io.uri parses one absolute URI into an immutable value and answers its components from the parse rather than from a substring. Reach for it when a program routes, rewrites or inspects a resource identifier.
const uri = nupp.io.uri
local endpoint, reason = uri.newURI("https://user@example.com:8443/api?q=1#top")
assert(endpoint, reason)
assert(endpoint:scheme() == "https")
assert(endpoint:host() == "example.com")
assert(endpoint:port() == 8443)Two URIs are equal when their normalized text is, so a host or scheme written in another case does not make one URI unequal to an otherwise identical one. Nothing has to be closed.
URI has no public constructor. Creation goes through uri.newURI, which interns the 1024 most recently used normalized URIs. Repeated parsing and URI-producing operations therefore reuse the same immutable value while retention stays bounded.
Reading components#
scheme and path answer a string, and path is empty rather than nil when the URI names none. username is empty when there is no user information. Every other component is optional and answers nil when the URI does not write it:
const uri = nupp.io.uri
local plain = assert(uri.newURI("mailto:someone@example.com"))
assert(plain:host() == nil)
assert(plain:port() == nil)
assert(plain:path() == "someone@example.com")The remaining accessors are authority, password, userInfo, query and fragment. toString answers the normalized complete URI, and tostring(uri) is the same call.
Validating untrusted text#
uri.newURI answers nil and a reason for malformed input, because bad text is an ordinary answer. uri.validate asks the same question without retaining an object, and uri.isURI separates a URI from a string or a record:
const uri = nupp.io.uri
local ok, why = uri.validate("http://[")
assert(not ok and why)
assert(not uri.isURI("https://example.com"))Parsing answers a reason, modification raises
A parse takes text from somewhere else, so failure is data and the caller is handed it. A with operation starts from a URI that already parsed, so a failure means the caller asked for something the grammar cannot express, which is a mistake at the call site rather than a bad input. Text that came from outside the program therefore goes through uri.newURI, uri.validate or resolve, all of which answer a reason.
Deriving a new URI#
Every with operation answers a new URI and leaves the original unchanged. Supplying a component equal to its current normalized value answers the receiver itself rather than reparsing:
const uri = nupp.io.uri
local endpoint = assert(uri.newURI("https://user:pass@example.com:8443/api?q=1"))
local production = endpoint:withUserInfo(nil):withHost("api.example.com")
assert(production:userInfo() == nil)
assert(production:host() == "api.example.com")
assert(endpoint:host() == "example.com")The operations are withScheme, withUserInfo, withHost, withPort, withPath, withQuery and withFragment. Passing nil removes an optional component. A port must be an integer from 0 through 65535, and anything else raises.
concatPath appends path text without interpreting it as a reference:
const uri = nupp.io.uri
local api = assert(uri.newURI("https://api.example.com/v1"))
assert(api:concatPath("users"):path() == "/v1/users")withEndpoint(endpoint) takes another URI's scheme and authority and keeps the receiver's path, query and fragment, which is what reroutes a request URI through a configured service address.
Resolving a reference#
resolve applies RFC reference resolution, the way a browser resolves a link against the page it is on. It answers nil and a reason when the reference cannot be resolved:
const uri = nupp.io.uri
local page = assert(uri.newURI("https://example.com/docs/guide/index.html"))
local image, reason = page:resolve("../images/avatar.png")
assert(image, reason)
assert(image:path() == "/docs/images/avatar.png")Building from components#
uri.newURI also accepts a nupp.io.uri.Components record. The components are assembled into text and then parsed, so one grammar decides what is valid:
const uri = nupp.io.uri
local status = assert(uri.newURI({
scheme = "https",
userInfo = "reader:secret",
host = "example.com",
path = "/status",
query = "full=1",
}))
assert(status:userInfo() == "reader:secret")scheme is required and may not be empty. userInfo, host, path, query and fragment are optional strings, and port is an optional integer from 0 through 65535. Every field is URI text rather than a filesystem name.
Submodules
| Module | Description |
|---|---|
nupp.io.uri.provider |
Module contents
Constructors
| Constructor | Description |
|---|---|
newURI | Parses absolute URI text, or assembles one from components. |
Types
| Type | Kind | Description |
|---|---|---|
Components | record | The parts a URI is built from when it is not written out as text. |
URI | record | A parsed URI. |
Functions
| Function | Kind | Description |
|---|---|---|
isURI | function | Reports whether a value is a URI. |
validate | function | Reports whether text parses as an absolute URI, without building one. |
Constructors#
newURIconstructor#
function newURI(value: string | Components): URI?, string?Parses absolute URI text, or assembles one from components.
Text has to be absolute: a relative reference is resolved against a URI that already exists, through URI:resolve.
Examples#
Read a URI's parts:
const uri = nupp.io.uri
local address = assert(uri.newURI("https://example.com/a?b=1"))
assert(address:host() == "example.com" and address:query() == "b=1")Assemble one from components rather than writing the text out:
local endpoint = assert(uri.newURI({
scheme = "https",
host = "example.com",
path = "/a",
query = "b=1",
}))Arguments
| Name | Type | Description |
|---|---|---|
value | string | Components | the URI text, or the components to assemble |
Returns
| Type | Description |
|---|---|
URI? | the URI, or nil when it could not be parsed |
string? | a failure reason, when unsuccessful |
Types#
Componentsrecord#
record Components
scheme: string
userInfo: string?
host: string?
port: integer?
path: string?
query: string?
fragment: string?
endThe parts a URI is built from when it is not written out as text.
Fields
URIrecord#
record URI
function toString(self): string end
function scheme(self): string end
function authority(self): string? end
function username(self): string end
function password(self): string? end
function host(self): string? end
function port(self): integer? end
function path(self): string end
function query(self): string? end
function fragment(self): string? end
function userInfo(self): string? end
function withScheme(self, value: string): URI end
function withUserInfo(self, value: string?): URI end
function withHost(self, value: string?): URI end
function withPort(self, value: integer?): URI end
function withPath(self, value: string): URI end
function withQuery(self, value: string?): URI end
function withFragment(self, value: string?): URI end
function concatPath(self, value: string): URI end
function resolve(self, reference: string): (URI?, string?) end
function withEndpoint(self, endpoint: URI): URI end
__tostring: function(self): string
__eq: function(self, any): boolean
endA parsed URI.
Methods
toString#
toString: function toString(self): stringThe whole URI as text.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string | the URI text |
scheme#
scheme: function scheme(self): stringThe scheme, without the colon.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string | the scheme |
authority#
authority: function authority(self): string?The authority between // and the path.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string? | the authority, or nil when there is none |
username#
username: function username(self): stringThe user name before any password.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string | the user name, empty when there is none |
password#
password: function password(self): string?The password after the user name.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string? | the password, or nil when there is none |
host#
host: function host(self): string?The host.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string? | the host, or nil when there is no authority |
port#
port: function port(self): integer?The port, when one was written.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
integer? | the port, or nil when the URI names none |
path#
path: function path(self): stringThe path, which is empty rather than nil when absent.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string | the path |
query#
query: function query(self): string?The query, without the ?.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string? | the query, or nil when there is none |
fragment#
fragment: function fragment(self): string?The fragment, without the #.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string? | the fragment, or nil when there is none |
userInfo#
userInfo: function userInfo(self): string?The user information as written, user name and password together.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
Returns
| Type | Description |
|---|---|
string? | the user information, or nil when there is none |
withScheme#
The URI with its scheme replaced.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | string | the scheme to use, without the colon |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when nothing changes |
Raises
when the scheme cannot be used here
withUserInfo#
The URI with its user information replaced.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | string? | the user information, or nil to remove it |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when nothing changes |
Raises
when the user information cannot be used here
withHost#
The URI with its host replaced.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | string? | the host, or nil to remove the authority |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when nothing changes |
Raises
when the host cannot be used here
withPort#
The URI with its port replaced.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | integer? | the port, 0 through 65535, or nil to remove it |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when nothing changes |
Raises
when port is not a port number
withPath#
The URI with its path replaced.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | string | the path |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when nothing changes |
Raises
when the path cannot be used here
withQuery#
The URI with its query replaced.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | string? | the query, without the |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when nothing changes |
Raises
when the query cannot be used here
withFragment#
The URI with its fragment replaced.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | string? | the fragment, without the |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when nothing changes |
Raises
when the fragment cannot be used here
concatPath#
The URI with more appended to its path.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
value | string | the path to append |
Returns
| Type | Description |
|---|---|
URI | the new URI, or this one when path is empty |
Raises
when the path cannot be appended
resolve#
resolve: function resolve(self, reference: string): URI?, string?Resolves a reference against this URI, as a browser resolves a link.
The reference decides how much of this URI survives: an absolute one replaces all of it, a rooted path keeps the scheme and authority, and a relative path is taken from this one's directory.
Examples#
Resolve a relative reference:
local page = assert(newURI("https://example.com/docs/a.html"))
assert(assert(page:resolve("b.html")):toString()
== "https://example.com/docs/b.html")Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
reference | string | the reference to resolve |
Returns
| Type | Description |
|---|---|
URI? | the resolved URI, or nil when the reference cannot be resolved |
string? | a failure reason, when unsuccessful |
withEndpoint#
The URI with another's scheme and authority.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | this URI |
endpoint | URI | the URI to take the endpoint from |
Returns
| Type | Description |
|---|---|
URI | the new URI |
Raises
when endpoint is not a URI
__tostring#
__tostring: function(self): stringArguments
| Name | Type | Description |
|---|---|---|
? | self |
Returns
| Type | Description |
|---|---|
string |
Functions#
isURIfunction#
Reports whether a value is a URI.
Arguments
| Name | Type | Description |
|---|---|---|
value | any | the value to test |
Returns
| Type | Description |
|---|---|
boolean | whether it is a URI |
validatefunction#
function validate(value: string): boolean, string?Reports whether text parses as an absolute URI, without building one.
Arguments
| Name | Type | Description |
|---|---|---|
value | string | the text to test |
Returns
| Type | Description |
|---|---|
boolean | whether it parses |
string? | why it did not, when it did not |