# `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.
```nupp
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:
```nupp
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:
```nupp
const uri = nupp.io.uri
local ok, why = uri.validate("http://[")
assert(not ok and why)
assert(not uri.isURI("https://example.com"))
```
::: deepdive 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:
```nupp
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:
```nupp
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:
```nupp
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:
```nupp
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.
::: seealso
- `nupp.io.path` for filesystem names, where the platform's separators and
`..` rules apply instead
- [Standard library](../../../../learn/runtime/data/standard-library/index.html) for how reaching a
module selects the provider behind it
:::
## Submodules
| Module | Description |
| --- | --- |
| `nupp.io.uri.provider` | |
## Constructors
### `newURI` _constructor_
```nupp
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:
```nupp
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:
```nupp
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
### `Components` _record_
```nupp
record Components
scheme: string
userInfo: string?
host: string?
port: integer?
path: string?
query: string?
fragment: string?
end
```
The parts a URI is built from when it is not written out as text.
#### Fields
##### `scheme`
```nupp
scheme: string
```
The scheme, which is required and may not be empty.
##### `userInfo`
```nupp
userInfo: string?
```
The user information before `@`, when there is any.
##### `host`
```nupp
host: string?
```
The host, when there is an authority.
##### `port`
```nupp
port: integer?
```
The port, 0 through 65535.
##### `path`
```nupp
path: string?
```
The path, which may be empty.
##### `query`
```nupp
query: string?
```
The query after `?`, without the `?`.
##### `fragment`
```nupp
fragment: string?
```
The fragment after `#`, without the `#`.
### `URI` _record_
```nupp
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
metamethod __tostring: function(self): string
metamethod __eq: function(self, any): boolean
end
```
A parsed URI.
#### Methods
##### `toString`
```nupp
toString: function toString(self): string
```
The whole URI as text.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the URI text |
##### `scheme`
```nupp
scheme: function scheme(self): string
```
The scheme, without the colon.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the scheme |
##### `authority`
```nupp
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`
```nupp
username: function username(self): string
```
The 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`
```nupp
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`
```nupp
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`
```nupp
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`
```nupp
path: function path(self): string
```
The path, which is empty rather than nil when absent.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the path |
##### `query`
```nupp
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`
```nupp
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`
```nupp
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`
```nupp
withScheme: function withScheme(self, value: string): URI
```
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`
```nupp
withUserInfo: function withUserInfo(self, value: string?): URI
```
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`
```nupp
withHost: function withHost(self, value: string?): URI
```
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`
```nupp
withPort: function withPort(self, value: integer?): URI
```
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`
```nupp
withPath: function withPath(self, value: string): URI
```
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`
```nupp
withQuery: function withQuery(self, value: string?): URI
```
The URI with its query replaced.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this URI |
| `value` | `string?` | the query, without the `?`, or nil to remove it |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the new URI, or this one when nothing changes |
###### Raises
- when the query cannot be used here
##### `withFragment`
```nupp
withFragment: function withFragment(self, value: string?): URI
```
The URI with its fragment replaced.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this URI |
| `value` | `string?` | the fragment, without the `#`, or nil to remove it |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the new URI, or this one when nothing changes |
###### Raises
- when the fragment cannot be used here
##### `concatPath`
```nupp
concatPath: function concatPath(self, value: string): URI
```
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`
```nupp
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:
```nupp
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`
```nupp
withEndpoint: function withEndpoint(self, endpoint: URI): URI
```
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`
```nupp
__tostring: function(self): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
###### Returns
| Type | Description |
| --- | --- |
| `string` | |
##### `__eq`
```nupp
__eq: function(self, any): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `?` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
## Functions
### `isURI` _function_
```nupp
function isURI(value: any): boolean
```
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 |
### `validate` _function_
```nupp
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 |