Validate and parse
RFC 3986 defines three kinds of identifier. Each one has functions to validate it:
| Kind | Example | Validate | Type |
|---|---|---|---|
| URI | https://example.com/a?b#c |
isUri, assertUri |
Uri |
| URI-reference | ../a#c |
isUriReference, assertUriReference |
UriReference |
| absolute-URI | https://example.com/a?b |
isAbsoluteUri, assertAbsoluteUri |
AbsoluteUri |
A URI has a scheme, like https, and can have a fragment, like #c. A
URI-reference is a URI or a relative reference, which doesn’t have a
scheme. An absolute-URI is a URI without a fragment, like a base URI.
IRIs have the same three kinds, with the same functions and types:
isIri, isIriReference, isAbsoluteIri, assertIri,
assertIriReference, assertAbsoluteIri, Iri, IriReference, and
AbsoluteIri.
Validate
Section titled “Validate”Each is function returns whether a string is that kind of identifier:
isUri("https://example.com/café"); // falseisIri("https://example.com/café"); // trueisIri("../schema.json"); // falseisIriReference("../schema.json"); // trueisAbsoluteIri("https://example.com/schema.json#foo"); // falseAn identifier has to follow the rules of its scheme too, if the package
knows the scheme. For example, an https URI needs a host, so
https:///a isn’t valid. An identifier with another scheme is checked
with the generic syntax only. For which schemes the functions know, see
Schemes:
isIri("https:///a"); // falseisIri("example:a"); // trueA relative reference doesn’t have a scheme, so it’s checked with the generic syntax only.
Each assert function gives the same answers, but throws an error that
says what’s wrong and where, instead of returning false. For more
information, see Errors.
assertIri("https://exa mple.com/"); // throws InvalidIriErrorCheck once
Section titled “Check once”The functions other than the validation functions trust what you give them. They don’t check it again, so you only pay for checking an identifier once. If you pass them a string that isn’t valid, the result isn’t defined.
In TypeScript, the validation functions return a type that says what they
checked, like Iri. An is function narrows the string’s type when it
returns true, and an assert function narrows it when it returns. The
other functions take these types, so TypeScript reports a string that you
haven’t checked:
const hostOf = (identifier: string) => { assertIri(identifier); return parseIri(identifier).host;};Without assertIri, TypeScript reports an error, because a string isn’t
an Iri.
The types are only for TypeScript. At runtime, they’re strings. Every
Uri is an Iri, and every AbsoluteIri is an Iri, so a function that
takes an Iri takes them too.
parseIri(value) splits an identifier into its components:
assertIri(iri);parseIri(iri);{ "scheme": "https", "userinfo": "ada", "host": "example.com", "port": "8080", "path": "/a/b", "query": "c", "fragment": "d"}parseIri takes URIs, references, and absolute identifiers too. Its
return type depends on what it’s given: an IriReference might not have a
scheme, and an AbsoluteIri doesn’t have a fragment.
Components
Section titled “Components”A component that isn’t in the identifier isn’t a property of the result.
A component that’s there but empty is "":
const tag = "tag:example.com,2004:a";const empty = "https://example.com/?";assertIri(tag);assertIri(empty);
Object.keys(parseIri(tag)); // ["scheme", "path"]parseIri(empty).query; // ""An identifier always has a path, but the path can be empty. Only an
identifier with an authority, like //example.com, has a host, a
userinfo, or a port.
The components are as written. parseIri doesn’t decode or lowercase
anything, so call normalizeIri first if you need that. For more
information, see Normalize.
The components are readonly. To change one, spread the components into a new object.
Get one component
Section titled “Get one component”To get one component, use getScheme, getPath, getQuery, or
getFragment. They give what parseIri gives for that component,
without splitting out the others, which makes them faster:
const uri = "https://example.com/schema?a#/$defs/b";assertIri(uri);
getScheme(uri); // "https"getPath(uri); // "/schema"getQuery(uri); // "a"getFragment(uri); // "/$defs/b"getScheme takes an IRI, which always has a scheme. The others take
references too. getQuery and getFragment return undefined for a
component that isn’t there.
hasScheme tells you whether a reference you’ve already checked is an
IRI, without checking it again. It’s a type guard, so after it, the
reference has the Iri type, or Uri for a URI-reference:
const reference = "https://example.com/schema";assertIriReference(reference);
if (hasScheme(reference)) { getScheme(reference); // "https"}Parse a URN
Section titled “Parse a URN”parseUrn(value) splits a Uniform Resource Name (URN) into the parts that
RFC 8141 defines:
const urn = "urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66#a";assertIri(urn);parseUrn(urn);{ "nid": "uuid", "nss": "6e8bc430-9c3a-11d9-9669-0800200c9a66", "fComponent": "a"}The parts are as written, like the components from parseIri.
parseUrn doesn’t check that value is a URN.
Errors
Section titled “Errors”The functions throw the following errors:
| Error | When |
|---|---|
InvalidUriError |
A string passed to an assert function for URIs isn’t that kind of URI, or urlToIri’s result breaks its scheme’s rules |
InvalidIriError |
A string passed to an assert function for IRIs isn’t that kind of IRI, or resolveIri’s result breaks its scheme’s rules |
Each error has a value property with the string that caused it.
InvalidUriError and InvalidIriError have a rule property, with the
rule that the string doesn’t match. It’s the kind of identifier, like
"absolute-IRI", or the scheme’s rule, like "http-URI", when the
string breaks its scheme’s rules.
What’s wrong and where
Section titled “What’s wrong and where”InvalidUriError and InvalidIriError also say what’s wrong with the
string, and where:
reasonis a code for what’s wrong, like"invalid-character".componentis the component with the problem, like"host".startandendare where the problem is in the string, sovalue.slice(start, end)is the part that’s wrong.
The message says the same thing, after the rule and the string:
try { assertIri("https://exa mple.com/");} catch (error) { if (error instanceof InvalidIriError) { error.message; // 'Invalid IRI: https://exa mple.com/ — " " isn't allowed in a // host, at 11' error.reason; // "invalid-character" error.component; // "host" error.value.slice(error.start, error.end); // " " }}reason is one of the following codes:
reason |
What’s wrong | Example |
|---|---|---|
invalid-character |
A character that the component doesn’t allow | https://exa mple.com |
invalid-percent-encoding |
A % that isn’t followed by two hex digits |
https://example.com/%zz |
invalid-ip-literal |
A host in brackets that isn’t an IP literal | https://[::g]/ |
missing-scheme |
No scheme, for a function that needs one | /foo passed to assertIri |
fragment-not-allowed |
A fragment, for a function that needs an absolute identifier | https://example.com#foo passed to assertAbsoluteIri |
colon-in-first-segment |
A relative reference with : before its first /, which reads like a scheme |
1a:b, which you can write as ./1a:b |
missing-component |
A component that the scheme requires isn’t there | http:/a, which needs an authority |
forbidden-component |
A component that the scheme doesn’t allow is there | file://h:1/a, which can’t have a port |
component-mismatch |
A component doesn’t match the scheme’s rule for it | did:Example:a, whose method’s name has to be lowercase |
Something that’s missing is an empty span where it would go. For example,
a missing scheme has start and end both 0. The span of a component
that the scheme doesn’t allow includes its delimiter, like :1.
The message for a scheme’s rule says what the rule needs:
Invalid http-URI: http:/a — an http URI must have an authorityInvalid file-URI: file://h:1/a — a file URI can't have a portInvalid did-url: did:Example:a — a did URI's path must be a method name, ":", and a method-specific ID, in ASCII, like example:123456start and end count UTF-16 code units, like every index of a
JavaScript string, and like positions in the Language Server Protocol. A
character outside the Basic Multilingual Plane, like an emoji, counts as
two.
Future IP literals
Section titled “Future IP literals”RFC 3986 reserves a syntax for IP literals in formats that don’t exist
yet, like [v1.fe80::a+en1]. The number after the v is the version of
the literal’s format. It isn’t a version of IP, like the 6 of IPv6.
No format is defined, so RFC 3986 says only which characters one can have. The validation functions check that a literal stays within that syntax, so an identifier with one is valid:
isIri("http://[v1.fe80::a+en1]/"); // trueisIri("http://[v1.fe80::a/en1]/"); // false: a literal can't have "/"