# `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 |