@std-uritemplate/std-uritemplate
Version:
std-uritemplate implementation for TS/JS
425 lines (423 loc) • 12.6 kB
JavaScript
class StdUriTemplate {
static expand(template, substitutions) {
return StdUriTemplate.expandImpl(template, substitutions);
}
static validateLiteral(c, col) {
switch (c) {
case "+":
case "#":
case "/":
case ";":
case "?":
case "&":
case " ":
case "!":
case "=":
case "$":
case "|":
case "*":
case ":":
case "~":
case "-":
throw new Error(`Illegal character identified in the token at col: ${col}`);
}
}
static getMaxChar(buffer, col) {
if (!buffer) {
return -1;
} else {
const value = buffer.join("");
if (value.length === 0) {
return -1;
} else {
try {
return parseInt(value, 10);
} catch (e) {
throw new Error(`Cannot parse max chars at col: ${col}`);
}
}
}
}
static getOperator(c, token, col) {
switch (c) {
case "+":
return 1 /* PLUS */;
case "#":
return 2 /* HASH */;
case ".":
return 3 /* DOT */;
case "/":
return 4 /* SLASH */;
case ";":
return 5 /* SEMICOLON */;
case "?":
return 6 /* QUESTION_MARK */;
case "&":
return 7 /* AMP */;
default:
StdUriTemplate.validateLiteral(c, col);
token.push(c);
return 0 /* NO_OP */;
}
}
static expandImpl(str, substitutions) {
const result = [];
let token = null;
let operator = null;
let composite = false;
let maxCharBuffer = null;
let firstToken = true;
for (let i = 0; i < str.length; i++) {
const character = str.charAt(i);
switch (character) {
case "{":
token = [];
firstToken = true;
break;
case "}":
if (token !== null) {
const expanded = StdUriTemplate.expandToken(
operator,
token.join(""),
composite,
StdUriTemplate.getMaxChar(maxCharBuffer, i),
firstToken,
substitutions,
result,
i
);
if (expanded && firstToken) {
firstToken = false;
}
token = null;
operator = null;
composite = false;
maxCharBuffer = null;
} else {
throw new Error(`Failed to expand token, invalid at col: ${i}`);
}
break;
case ",":
if (token !== null) {
const expanded = StdUriTemplate.expandToken(
operator,
token.join(""),
composite,
StdUriTemplate.getMaxChar(maxCharBuffer, i),
firstToken,
substitutions,
result,
i
);
if (expanded && firstToken) {
firstToken = false;
}
token = [];
composite = false;
maxCharBuffer = null;
break;
}
// Intentional fall-through for commas outside the {}
default:
if (token !== null) {
if (operator === null) {
operator = StdUriTemplate.getOperator(character, token, i);
} else if (maxCharBuffer !== null) {
if (character.match(/^\d$/)) {
maxCharBuffer.push(character);
} else {
throw new Error(`Illegal character identified in the token at col: ${i}`);
}
} else {
if (character === ":") {
maxCharBuffer = [];
} else if (character === "*") {
composite = true;
} else {
StdUriTemplate.validateLiteral(character, i);
token.push(character);
}
}
} else {
result.push(character);
}
break;
}
}
if (token === null) {
return result.join("");
} else {
throw new Error("Unterminated token");
}
}
static addPrefix(op, result) {
switch (op) {
case 2 /* HASH */:
result.push("#");
break;
case 3 /* DOT */:
result.push(".");
break;
case 4 /* SLASH */:
result.push("/");
break;
case 5 /* SEMICOLON */:
result.push(";");
break;
case 6 /* QUESTION_MARK */:
result.push("?");
break;
case 7 /* AMP */:
result.push("&");
break;
default:
return;
}
}
static addSeparator(op, result) {
switch (op) {
case 3 /* DOT */:
result.push(".");
break;
case 4 /* SLASH */:
result.push("/");
break;
case 5 /* SEMICOLON */:
result.push(";");
break;
case 6 /* QUESTION_MARK */:
case 7 /* AMP */:
result.push("&");
break;
default:
result.push(",");
return;
}
}
static addValue(op, token, value, result, maxChar) {
switch (op) {
case 1 /* PLUS */:
case 2 /* HASH */:
StdUriTemplate.addExpandedValue(null, value, result, maxChar, false);
break;
case 6 /* QUESTION_MARK */:
case 7 /* AMP */:
result.push(`${token}=`);
StdUriTemplate.addExpandedValue(null, value, result, maxChar, true);
break;
case 5 /* SEMICOLON */:
result.push(token);
StdUriTemplate.addExpandedValue("=", value, result, maxChar, true);
break;
case 3 /* DOT */:
case 4 /* SLASH */:
case 0 /* NO_OP */:
StdUriTemplate.addExpandedValue(null, value, result, maxChar, true);
break;
}
}
static addValueElement(op, token, value, result, maxChar) {
switch (op) {
case 1 /* PLUS */:
case 2 /* HASH */:
StdUriTemplate.addExpandedValue(null, value, result, maxChar, false);
break;
case 6 /* QUESTION_MARK */:
case 7 /* AMP */:
case 5 /* SEMICOLON */:
case 3 /* DOT */:
case 4 /* SLASH */:
case 0 /* NO_OP */:
StdUriTemplate.addExpandedValue(null, value, result, maxChar, true);
break;
}
}
static isSurrogate(cp) {
const codeUnit = cp.charCodeAt(0);
return codeUnit >= 55296 && codeUnit <= 56319;
}
static isIprivate(cp) {
return 57344 <= cp.charCodeAt(0) && cp.charCodeAt(0) <= 63743;
}
static isUcschar(cp) {
const codePoint = cp.codePointAt(0) || 0;
return 160 <= codePoint && codePoint <= 55295 || 63744 <= codePoint && codePoint <= 64975 || 65008 <= codePoint && codePoint <= 65519;
}
static addExpandedValue(prefix, value, result, maxChar, replaceReserved) {
const stringValue = StdUriTemplate.convertNativeTypes(value);
const max = maxChar !== -1 ? Math.min(maxChar, stringValue.length) : stringValue.length;
let reservedBuffer = void 0;
if (max > 0 && prefix != null) {
result.push(prefix);
}
for (let i = 0; i < max; i++) {
const character = stringValue.charAt(i);
if (character === "%" && !replaceReserved) {
reservedBuffer = [];
}
let toAppend = character;
if (StdUriTemplate.isSurrogate(character)) {
toAppend = encodeURIComponent(stringValue.charAt(i) + stringValue.charAt(i + 1));
i++;
} else if (replaceReserved || StdUriTemplate.isUcschar(character) || StdUriTemplate.isIprivate(character)) {
if (character === "!") {
toAppend = "%21";
} else {
toAppend = encodeURIComponent(toAppend);
}
}
if (reservedBuffer) {
reservedBuffer.push(toAppend);
if (reservedBuffer.length === 3) {
let isEncoded = false;
try {
const reserved = reservedBuffer.join("");
const decoded = decodeURIComponent(reservedBuffer.join(""));
isEncoded = reserved !== decoded;
} catch (e) {
}
if (isEncoded) {
result.push(reservedBuffer.join(""));
} else {
result.push("%25");
result.push(reservedBuffer.slice(1).join(""));
}
reservedBuffer = void 0;
}
} else {
if (character === " ") {
result.push("%20");
} else if (character === "%") {
result.push("%25");
} else {
result.push(toAppend);
}
}
}
if (reservedBuffer) {
result.push("%25");
result.push(reservedBuffer.slice(1).join(""));
}
}
static isList(value) {
return Array.isArray(value) || value instanceof Set;
}
static isMap(value) {
return value instanceof Map || typeof value === "object";
}
static getSubstitutionType(value, col) {
if (value === void 0 || value === null) {
return 0 /* EMPTY */;
} else if (StdUriTemplate.isNativeType(value)) {
return 1 /* STRING */;
} else if (StdUriTemplate.isList(value)) {
return 2 /* LIST */;
} else if (StdUriTemplate.isMap(value)) {
return 3 /* MAP */;
} else {
throw new Error(`Illegal class passed as substitution, found ${typeof value} at col: ${col}`);
}
}
static isEmpty(substType, value) {
if (value === void 0 || value === null) {
return true;
} else {
switch (substType) {
case 1 /* STRING */:
return false;
case 2 /* LIST */:
return value.length === 0;
case 3 /* MAP */:
return Object.keys(value).length === 0;
default:
return true;
}
}
}
static isNativeType(value) {
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
static convertNativeTypes(value) {
if (typeof value === "string") {
return value;
} else if (typeof value === "number" || typeof value === "boolean") {
return value.toString();
} else {
throw new Error(`Illegal class passed as substitution, found ${typeof value}`);
}
}
static expandToken(operator, token, composite, maxChar, firstToken, substitutions, result, col) {
if (token.length === 0) {
throw new Error(`Found an empty token at col: ${col}`);
}
const value = substitutions[token];
const substType = StdUriTemplate.getSubstitutionType(value, col);
if (substType === 0 /* EMPTY */ || StdUriTemplate.isEmpty(substType, value)) {
return false;
}
if (firstToken) {
StdUriTemplate.addPrefix(operator, result);
} else {
StdUriTemplate.addSeparator(operator, result);
}
switch (substType) {
case 1 /* STRING */:
StdUriTemplate.addStringValue(operator, token, value, result, maxChar);
break;
case 2 /* LIST */:
StdUriTemplate.addListValue(operator, token, value, result, maxChar, composite);
break;
case 3 /* MAP */:
StdUriTemplate.addMapValue(operator, token, value, result, maxChar, composite);
break;
}
return true;
}
static addStringValue(operator, token, value, result, maxChar) {
StdUriTemplate.addValue(operator, token, value, result, maxChar);
}
static addListValue(operator, token, value, result, maxChar, composite) {
let first = true;
for (const v of value) {
if (first) {
StdUriTemplate.addValue(operator, token, v, result, maxChar);
first = false;
} else {
if (composite) {
StdUriTemplate.addSeparator(operator, result);
StdUriTemplate.addValue(operator, token, v, result, maxChar);
} else {
result.push(",");
StdUriTemplate.addValueElement(operator, token, v, result, maxChar);
}
}
}
}
static addMapValue(operator, token, value, result, maxChar, composite) {
let first = true;
if (maxChar !== -1) {
throw new Error("Value trimming is not allowed on Maps");
}
for (const key in value) {
const v = value[key];
if (composite) {
if (!first) {
StdUriTemplate.addSeparator(operator, result);
}
StdUriTemplate.addValueElement(operator, token, key, result, maxChar);
result.push("=");
} else {
if (first) {
StdUriTemplate.addValue(operator, token, key, result, maxChar);
} else {
result.push(",");
StdUriTemplate.addValueElement(operator, token, key, result, maxChar);
}
result.push(",");
}
StdUriTemplate.addValueElement(operator, token, v, result, maxChar);
first = false;
}
}
}
export { StdUriTemplate };