urns
Version:
An RFC 8141 compliant URN library with some interesting type related functionality
150 lines (149 loc) • 6.05 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseURN = exports.nss = exports.nid = exports.unparseURN = exports.createURN = exports.createFullURN = void 0;
/**
* This is a Javascript regular expression for a URN that is compliant with RFC 8141.
*
* You can find a bit more commentary on this here:
*
* https://stackoverflow.com/questions/59032211/regex-which-matches-urn-by-rfc8141#comment118833940_59048720
*
* You can also play around with the Regexp interactively here:
*
* https://regex101.com/r/WMty99/1
*/
var rfc8141 = /^urn:([a-z0-9][a-z0-9-]{1,31}):((?:[-a-z0-9()+,.:=@;$_!*'&~\/]|%[0-9a-f]{2})+)(?:(\?\+)((?:(?!\?=)(?:[-a-z0-9()+,.:=@;$_!*'&~\/\?]|%[0-9a-f]{2}))*))?(?:(\?=)((?:(?!#).)*))?(?:(#)((?:[-a-z0-9()+,.:=@;$_!*'&~\/\?]|%[0-9a-f]{2})*))?$/i;
/**
* This function takes a given NID and NSS and creates a full URN. This is relatively straight forward. The
* only real complexity comes from handling the URI encoding.
* @param nid
* @param nss
* @param {boolean=} skipVerification skip test against rfc8141 regex (optional, default false)
* @returns
*/
function createFullURN(nid, nss, components, skipVerification) {
/** Encode the NID */
var encoded_nid = encodeURI(nid);
/** Encode the NSS */
var encoded_nss = encodeURI(nss);
var ret = "urn:" + encoded_nid + ":" + encoded_nss;
if (components === null || components === void 0 ? void 0 : components.r) {
ret += "?+" + encodeURIComponent(components.r);
}
if (components === null || components === void 0 ? void 0 : components.q) {
var elements = Object.entries(components.q).map(function (_a) {
var key = _a[0], value = _a[1];
return encodeURIComponent(key) + "=" + encodeURIComponent(value);
});
ret += "?=" + elements.join("&");
}
if (components === null || components === void 0 ? void 0 : components.f) {
ret += "#" + components.f;
}
/** Ensure the result satisfies the regular expression */
if (!skipVerification && !rfc8141.test(ret)) {
throw new Error("Unable to create a syntactically valid URN");
}
return ret;
}
exports.createFullURN = createFullURN;
/**
* This function takes a given NID and NSS and creates a URN. This is relatively straight forward. The
* only real complexity comes from handling the URI encoding.
* @param nid
* @param nss
* @param {boolean=} skipVerification skip test against rfc8141 regex (optional, default false)
* @returns
*/
function createURN(nid, nss, skipVerification) {
/** Encode the NID */
var encoded_nid = encodeURI(nid);
/** Encode the NSS */
var encoded_nss = encodeURI(nss);
var ret = "urn:" + encoded_nid + ":" + encoded_nss;
/** Ensure the result satisfies the regular expression */
if (!skipVerification && !rfc8141.test(ret)) {
throw new Error("Unable to create a syntactically valid URN");
}
return ret;
}
exports.createURN = createURN;
/**
* This function "unparses" a URN. That is to say that it takes the normal output
* of the `parse` function and reverses it to form the original URN. This is quite
* similar to the `createURN` function above except that it handles components as
* well.
* @param p
* @param {boolean=} skipVerification skip test against rfc8141 regex (optional, default false)
* @returns
*/
function unparseURN(p, skipVerification) {
/** Again, ensure everything is properly URI encoded */
var nid = encodeURI(p.nid);
var nss = encodeURI(p.nss);
var rcomponent = p.rcomponent ? "?+" + encodeURI(p.rcomponent) : "";
var qcomponent = p.qcomponent ? "?=" + encodeURI(p.qcomponent) : "";
var fragment = p.fragment ? "#" + encodeURI(p.fragment) : "";
var ret = "urn:" + nid + ":" + nss + rcomponent + qcomponent + fragment;
/** Ensure the result is a valid URN */
if (!skipVerification && !rfc8141.test(ret)) {
throw new Error("Unable to create a syntactically valid URN");
}
return ret;
}
exports.unparseURN = unparseURN;
/** A helper function to extract just the namespace identifier (NID) */
function nid(s) {
return parseURN(s).nid;
}
exports.nid = nid;
/** A helper function to extract just the namespace specific string (NSS) */
function nss(s) {
return parseURN(s).nss;
}
exports.nss = nss;
/**
* This function parses a string as a URN and returns an object containing all
* the various aspects of the URN. Much of the "heavy lifting" here is done
* by the regular expression parsing itself. But there are a few more bits
* we need to do in the function as well.
* @param s
* @returns
*/
function parseURN(s) {
/** Parse this using the regular expression at the top of this file */
var results = s.match(rfc8141);
/** If it doesn't conform, we are done. */
if (!results) {
throw new Error("String \"" + s + "\" is not a valid RFC8141 compliant URN");
}
/* istanbul ignore next */
if (results.length < 3) {
throw new Error("Error parsing URN \"" + s + "\""); // I don't see how this can happen
}
/** URI decode the NID and the NSS */
var nid = decodeURI(results[1]);
var nss = decodeURI(results[2]);
/** We keep the encoded NSS as well */
var nss_encoded = results[2];
/**
* Now we have to go through the Regexp output and match it up with
* any r-component, q-component or f-component, if present.
*/
var ridx = results.indexOf("?+");
var qidx = results.indexOf("?=");
var fidx = results.indexOf("#");
var rcomponent = ridx === -1 ? null : decodeURI(results[ridx + 1]);
var qcomponent = qidx === -1 ? null : decodeURI(results[qidx + 1]);
var fragment = fidx === -1 ? null : decodeURI(results[fidx + 1]);
/** Return the resulting fully parsed URN. */
return {
nid: nid,
nss: nss,
nss_encoded: nss_encoded,
rcomponent: rcomponent,
qcomponent: qcomponent,
fragment: fragment,
};
}
exports.parseURN = parseURN;