# tecs.io.URI Immutable absolute URIs for protocols, resources, and application links. `URI` is not tied to HTTP. It accepts every absolute scheme supported by the native URL parser, while an API such as the HTTP client separately restricts the schemes it can use. Components are parsed once and copied into Lua, so accessors do not reparse or allocate: ```teal local endpoint = tecs.io.URI.new("https://api.example.com/v1") local scores = endpoint:concatPath("scores"):withQuery( "limit=20" ) print(scores:scheme(), scores:host(), scores:path()) print(scores) ``` URI values are immutable. Every `with` method and `concatPath` returns a new value and leaves its receiver unchanged. Supplying a component record to `tecs.io.URI.new` constructs the complete initial value without intermediate copies. A modifier clones the retained parsed value and validates only the replacement component; supplying the existing value returns the receiver. `withEndpoint` replaces the scheme, user information, host, and port from another URI, prefixes the receiver's path with the endpoint path, and preserves the receiver's query and fragment. That makes a configured service endpoint composable with a modeled resource URI without rebuilding either from strings. `resolve` applies an RFC-style relative reference to the receiver. The constructor itself requires an absolute URI, which keeps every stored value self-contained and leaves relative text at the operation where its base is known. A URI owns only immutable Lua strings and needs no `close`. ## Module contents ### Constructors | Constructor | Description | | --- | --- | | [`new`](/modules/io/URI/#tecs.io.URI.new) | Parses and normalizes an absolute URI. | ### Types | Type | Kind | Description | | --- | --- | --- | | [`Components`](/modules/io/URI/#tecs.io.URI.Components) | record | Components constructs one URI without intermediate immutable copies. | ### Functions | Function | Kind | Description | | --- | --- | --- | | [`isURI`](/modules/io/URI/#tecs.io.URI.isURI) | Static | Returns whether a value is a Tecs URI. | | [`validate`](/modules/io/URI/#tecs.io.URI.validate) | Static | Validates an absolute URI without retaining a value. | | [`authority`](/modules/io/URI/#tecs.io.URI.authority) | Instance | Returns the encoded authority. | | [`concatPath`](/modules/io/URI/#tecs.io.URI.concatPath) | Instance | Appends a path while preserving one separator at the boundary. | | [`fragment`](/modules/io/URI/#tecs.io.URI.fragment) | Instance | Returns the encoded fragment without its hash. | | [`host`](/modules/io/URI/#tecs.io.URI.host) | Instance | Returns the normalized host. | | [`password`](/modules/io/URI/#tecs.io.URI.password) | Instance | Returns the encoded password. | | [`path`](/modules/io/URI/#tecs.io.URI.path) | Instance | Returns the encoded path. | | [`port`](/modules/io/URI/#tecs.io.URI.port) | Instance | Returns the explicit port. | | [`query`](/modules/io/URI/#tecs.io.URI.query) | Instance | Returns the encoded query without its question mark. | | [`resolve`](/modules/io/URI/#tecs.io.URI.resolve) | Instance | Resolves a relative or absolute URI reference. | | [`scheme`](/modules/io/URI/#tecs.io.URI.scheme) | Instance | Returns the lower-cased scheme without its colon. | | [`toString`](/modules/io/URI/#tecs.io.URI.toString) | Instance | Returns the normalized absolute URI. | | [`userInfo`](/modules/io/URI/#tecs.io.URI.userInfo) | Instance | Returns the encoded user information. | | [`username`](/modules/io/URI/#tecs.io.URI.username) | Instance | Returns the encoded username. | | [`withEndpoint`](/modules/io/URI/#tecs.io.URI.withEndpoint) | Instance | Applies another URI as this resource's endpoint. | | [`withFragment`](/modules/io/URI/#tecs.io.URI.withFragment) | Instance | Replaces or removes the fragment and returns a new URI. | | [`withHost`](/modules/io/URI/#tecs.io.URI.withHost) | Instance | Replaces or removes the host and returns a new URI. | | [`withPath`](/modules/io/URI/#tecs.io.URI.withPath) | Instance | Replaces the path and returns a new URI. | | [`withPort`](/modules/io/URI/#tecs.io.URI.withPort) | Instance | Replaces or removes the explicit port and returns a new URI. | | [`withQuery`](/modules/io/URI/#tecs.io.URI.withQuery) | Instance | Replaces or removes the query and returns a new URI. | | [`withScheme`](/modules/io/URI/#tecs.io.URI.withScheme) | Instance | Replaces the scheme and returns a new URI. | | [`withUserInfo`](/modules/io/URI/#tecs.io.URI.withUserInfo) | Instance | Replaces or removes user information and returns a new URI. | ## Constructors ### tecs.io.URI.new Static Parses and normalizes an absolute URI. A component record performs one construction; it does not allocate an intermediate URI for each component. ```teal function tecs.io.URI.new( value: string | Components ): URI, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `value` | string | [`Components`](/modules/io/URI/#tecs.io.URI.Components) | The caller supplies absolute UTF-8 text or URI components. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns an immutable URI, or nil when the text is invalid or relative. | | `string` | Returns the parser's reason when the first return is nil. | #### Examples ```teal local endpoint, reason = tecs.io.URI.new( "https://user@example.com:8443/assets/list.json?page=2" ) if endpoint == nil then error(reason) end assert(endpoint:scheme() == "https") assert(endpoint:host() == "example.com") assert(endpoint:port() == 8443) assert(endpoint:path() == "/assets/list.json") assert(endpoint:query() == "page=2") ``` ## Types ### tecs.io.URI.Components record `Components` constructs one URI without intermediate immutable copies. ```teal record tecs.io.URI.Components scheme: string userInfo: string host: string port: integer path: string query: string fragment: string end ``` #### Examples ```teal local endpoint, reason = tecs.io.URI.new({ scheme = "https", userInfo = "builder:secret", host = "api.example.com", port = 8443, path = "/v2/scores", query = "limit=20", }) if endpoint == nil then error(reason) end assert( endpoint:toString( ) == "https://builder:secret@api.example.com:8443/v2/scores?limit=20" ) ``` #### tecs.io.URI.Components.scheme field Caller-writable. Supplies the required scheme without its colon. ```teal tecs.io.URI.Components.scheme: string ``` #### tecs.io.URI.Components.userInfo field Caller-writable. Supplies encoded `username` or `username:password`, or nil for none. ```teal tecs.io.URI.Components.userInfo: string ``` #### tecs.io.URI.Components.host field Caller-writable. Supplies the host, or nil for a scheme without an authority. IPv6 literals may include or omit brackets. ```teal tecs.io.URI.Components.host: string ``` #### tecs.io.URI.Components.port field Caller-writable. Supplies the explicit port, or nil for none. ```teal tecs.io.URI.Components.port: integer ``` #### tecs.io.URI.Components.path field Caller-writable. Supplies the path. Nil means an empty path. ```teal tecs.io.URI.Components.path: string ``` #### tecs.io.URI.Components.query field Caller-writable. Supplies the query without `?`, or nil for none. ```teal tecs.io.URI.Components.query: string ``` #### tecs.io.URI.Components.fragment field Caller-writable. Supplies the fragment without `#`, or nil for none. ```teal tecs.io.URI.Components.fragment: string ``` ## Functions ### tecs.io.URI.isURI Static Returns whether a value is a Tecs URI. ```teal function tecs.io.URI.isURI(value: any): boolean ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `value` | `any` | The caller supplies any Lua value. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true only for values created by this module. | ### tecs.io.URI.validate Static Validates an absolute URI without retaining a value. ```teal function tecs.io.URI.validate(text: string): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `text` | `string` | The caller supplies the UTF-8 text to validate. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true when `new` can parse the text. | | `string` | Returns the parser's reason when the first return is false. | #### Examples ```teal local valid = tecs.io.URI.validate("urn:tecs:asset:sprite") local relative , reason = tecs.io.URI.validate("../sprite.png") assert(valid) assert(not relative) assert(reason ~= nil) ``` ### tecs.io.URI:authority Instance Returns the encoded authority. ```teal function tecs.io.URI.authority(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns user information, host, and port, or nil when this URI has no authority. | ### tecs.io.URI:concatPath Instance Appends a path while preserving one separator at the boundary. ```teal function tecs.io.URI.concatPath(self, path: string): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `path` | `string` | The caller supplies the path suffix to append. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. | #### Examples ```teal local api, reason = tecs.io.URI.new("https://example.com/v1/") if api == nil then error(reason) end local manifest = api:concatPath("/games/42/manifest.json") assert(manifest:path() == "/v1/games/42/manifest.json") ``` ### tecs.io.URI:fragment Instance Returns the encoded fragment without its hash. ```teal function tecs.io.URI.fragment(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the fragment, or nil when the URI omits it. | ### tecs.io.URI:host Instance Returns the normalized host. ```teal function tecs.io.URI.host(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the host, or nil when this scheme has none. | ### tecs.io.URI:password Instance Returns the encoded password. ```teal function tecs.io.URI.password(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the password, or nil when none was supplied. | ### tecs.io.URI:path Instance Returns the encoded path. ```teal function tecs.io.URI.path(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the path, including its leading slash when present. | ### tecs.io.URI:port Instance Returns the explicit port. ```teal function tecs.io.URI.port(self): integer ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `integer` | Returns the explicit port, or nil when the URI omits it. | ### tecs.io.URI:query Instance Returns the encoded query without its question mark. ```teal function tecs.io.URI.query(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the query, or nil when the URI omits it. | ### tecs.io.URI:resolve Instance Resolves a relative or absolute URI reference. ```teal function tecs.io.URI.resolve(self, reference: string): URI, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The absolute base URI left unchanged. | | `reference` | `string` | The caller supplies an RFC-style URI reference. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns the resolved immutable URI. | | `string` | Returns the parser's reason when the reference is invalid. | #### Examples ```teal local page, reason = tecs.io.URI.new( "https://example.com/games/current/" ) if page == nil then error(reason) end local scores, resolveReason = page:resolve("../42/scores") if scores == nil then error(resolveReason) end assert(scores:toString() == "https://example.com/games/42/scores") ``` ### tecs.io.URI:scheme Instance Returns the lower-cased scheme without its colon. ```teal function tecs.io.URI.scheme(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the required scheme. | ### tecs.io.URI:toString Instance Returns the normalized absolute URI. `tostring(uri)` returns the same string. ```teal function tecs.io.URI.toString(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to render. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the complete normalized URI. | ### tecs.io.URI:userInfo Instance Returns the encoded user information. ```teal function tecs.io.URI.userInfo(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns `username`, `username:password`, or nil when neither was supplied. | ### tecs.io.URI:username Instance Returns the encoded username. ```teal function tecs.io.URI.username(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The immutable URI to inspect. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the username, or an empty string when none was supplied. | ### tecs.io.URI:withEndpoint Instance Applies another URI as this resource's endpoint. The endpoint supplies scheme, user information, host, and port. Its path prefixes this URI's path; this URI retains its query and fragment. ```teal function tecs.io.URI.withEndpoint(self, endpoint: URI): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The resource URI left unchanged. | | `endpoint` | [`URI`](/modules/io/URI/) | The caller supplies the service endpoint. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. | #### Examples ```teal local resource, resourceReason = tecs.io.URI.new( "smithy:/games/42/scores?limit=10" ) if resource == nil then error(resourceReason) end local endpoint, endpointReason = tecs.io.URI.new( "https://api.example.com/v2" ) if endpoint == nil then error(endpointReason) end local request = resource:withEndpoint(endpoint) assert( request:toString( ) == "https://api.example.com/v2/games/42/scores?limit=10" ) ``` ### tecs.io.URI:withFragment Instance Replaces or removes the fragment and returns a new URI. ```teal function tecs.io.URI.withFragment(self, fragment: string): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `fragment` | `string` | The caller supplies a fragment without `#`, or nil to remove it. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. | ### tecs.io.URI:withHost Instance Replaces or removes the host and returns a new URI. ```teal function tecs.io.URI.withHost(self, host: string): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `host` | `string` | The caller supplies a host, or nil when the scheme permits no host. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. | ### tecs.io.URI:withPath Instance Replaces the path and returns a new URI. ```teal function tecs.io.URI.withPath(self, path: string): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `path` | `string` | The caller supplies an encoded or unencoded path. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI with a normalized path. | ### tecs.io.URI:withPort Instance Replaces or removes the explicit port and returns a new URI. ```teal function tecs.io.URI.withPort(self, port: integer): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `port` | `integer` | The caller supplies a port from zero through 65535, or nil to omit it. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. | ### tecs.io.URI:withQuery Instance Replaces or removes the query and returns a new URI. ```teal function tecs.io.URI.withQuery(self, query: string): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `query` | `string` | The caller supplies a query without `?`, or nil to remove it. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. | #### Examples ```teal local scores, reason = tecs.io.URI.new("https://example.com/scores") if scores == nil then error(reason) end local firstPage = scores:withQuery("limit=20&offset=0") assert(scores:query() == nil) assert(firstPage:query() == "limit=20&offset=0") ``` ### tecs.io.URI:withScheme Instance Replaces the scheme and returns a new URI. ```teal function tecs.io.URI.withScheme(self, scheme: string): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `scheme` | `string` | The caller supplies a scheme without its colon. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. | ### tecs.io.URI:withUserInfo Instance Replaces or removes user information and returns a new URI. ```teal function tecs.io.URI.withUserInfo(self, userInfo: string): URI ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `URI` | The URI left unchanged. | | `userInfo` | `string` | The caller supplies `username`, `username:password`, or nil to remove both components. | #### Returns | Type | Description | | --- | --- | | [`URI`](/modules/io/URI/) | Returns a new immutable URI. |