@metrichor/jmespath
Version:
Typescript implementation of the JMESPath spec (100% compliant)
648 lines • 24.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runtime = exports.InputArgument = void 0;
const Lexer_1 = require("./Lexer");
const utils_1 = require("./utils");
var InputArgument;
(function (InputArgument) {
InputArgument[InputArgument["TYPE_NUMBER"] = 0] = "TYPE_NUMBER";
InputArgument[InputArgument["TYPE_ANY"] = 1] = "TYPE_ANY";
InputArgument[InputArgument["TYPE_STRING"] = 2] = "TYPE_STRING";
InputArgument[InputArgument["TYPE_ARRAY"] = 3] = "TYPE_ARRAY";
InputArgument[InputArgument["TYPE_OBJECT"] = 4] = "TYPE_OBJECT";
InputArgument[InputArgument["TYPE_BOOLEAN"] = 5] = "TYPE_BOOLEAN";
InputArgument[InputArgument["TYPE_EXPREF"] = 6] = "TYPE_EXPREF";
InputArgument[InputArgument["TYPE_NULL"] = 7] = "TYPE_NULL";
InputArgument[InputArgument["TYPE_ARRAY_NUMBER"] = 8] = "TYPE_ARRAY_NUMBER";
InputArgument[InputArgument["TYPE_ARRAY_STRING"] = 9] = "TYPE_ARRAY_STRING";
})(InputArgument = exports.InputArgument || (exports.InputArgument = {}));
class Runtime {
constructor(interpreter) {
this.TYPE_NAME_TABLE = {
[InputArgument.TYPE_NUMBER]: 'number',
[InputArgument.TYPE_ANY]: 'any',
[InputArgument.TYPE_STRING]: 'string',
[InputArgument.TYPE_ARRAY]: 'array',
[InputArgument.TYPE_OBJECT]: 'object',
[InputArgument.TYPE_BOOLEAN]: 'boolean',
[InputArgument.TYPE_EXPREF]: 'expression',
[InputArgument.TYPE_NULL]: 'null',
[InputArgument.TYPE_ARRAY_NUMBER]: 'Array<number>',
[InputArgument.TYPE_ARRAY_STRING]: 'Array<string>',
};
this.functionAbs = ([inputValue]) => {
return Math.abs(inputValue);
};
this.functionAvg = ([inputArray]) => {
let sum = 0;
for (let i = 0; i < inputArray.length; i += 1) {
sum += inputArray[i];
}
return sum / inputArray.length;
};
this.functionCeil = ([inputValue]) => {
return Math.ceil(inputValue);
};
this.functionContains = resolvedArgs => {
const [searchable, searchValue] = resolvedArgs;
return searchable.includes(searchValue);
};
this.functionEndsWith = resolvedArgs => {
const [searchStr, suffix] = resolvedArgs;
return searchStr.includes(suffix, searchStr.length - suffix.length);
};
this.functionFloor = ([inputValue]) => {
return Math.floor(inputValue);
};
this.functionJoin = resolvedArgs => {
const [joinChar, listJoin] = resolvedArgs;
return listJoin.join(joinChar);
};
this.functionKeys = ([inputObject]) => {
return Object.keys(inputObject);
};
this.functionLength = ([inputValue]) => {
if (!utils_1.isObject(inputValue)) {
return inputValue.length;
}
return Object.keys(inputValue).length;
};
this.functionMap = (resolvedArgs) => {
if (!this._interpreter) {
return [];
}
const mapped = [];
const interpreter = this._interpreter;
const exprefNode = resolvedArgs[0];
const elements = resolvedArgs[1];
for (let i = 0; i < elements.length; i += 1) {
mapped.push(interpreter.visit(exprefNode, elements[i]));
}
return mapped;
};
this.functionMax = ([inputValue]) => {
if (!inputValue.length) {
return null;
}
const typeName = this.getTypeName(inputValue[0]);
if (typeName === InputArgument.TYPE_NUMBER) {
return Math.max(...inputValue);
}
const elements = inputValue;
let maxElement = elements[0];
for (let i = 1; i < elements.length; i += 1) {
if (maxElement.localeCompare(elements[i]) < 0) {
maxElement = elements[i];
}
}
return maxElement;
};
this.functionMaxBy = (resolvedArgs) => {
const exprefNode = resolvedArgs[1];
const resolvedArray = resolvedArgs[0];
const keyFunction = this.createKeyFunction(exprefNode, [InputArgument.TYPE_NUMBER, InputArgument.TYPE_STRING]);
let maxNumber = -Infinity;
let maxRecord;
let current;
for (let i = 0; i < resolvedArray.length; i += 1) {
current = keyFunction && keyFunction(resolvedArray[i]);
if (current !== undefined && current > maxNumber) {
maxNumber = current;
maxRecord = resolvedArray[i];
}
}
return maxRecord;
};
this.functionMerge = resolvedArgs => {
let merged = {};
for (let i = 0; i < resolvedArgs.length; i += 1) {
const current = resolvedArgs[i];
merged = Object.assign(merged, current);
// for (const key in current) {
// merged[key] = current[key];
// }
}
return merged;
};
this.functionMin = ([inputValue]) => {
if (!inputValue.length) {
return null;
}
const typeName = this.getTypeName(inputValue[0]);
if (typeName === InputArgument.TYPE_NUMBER) {
return Math.min(...inputValue);
}
const elements = inputValue;
let minElement = elements[0];
for (let i = 1; i < elements.length; i += 1) {
if (elements[i].localeCompare(minElement) < 0) {
minElement = elements[i];
}
}
return minElement;
};
this.functionMinBy = (resolvedArgs) => {
const exprefNode = resolvedArgs[1];
const resolvedArray = resolvedArgs[0];
const keyFunction = this.createKeyFunction(exprefNode, [InputArgument.TYPE_NUMBER, InputArgument.TYPE_STRING]);
let minNumber = Infinity;
let minRecord;
let current;
for (let i = 0; i < resolvedArray.length; i += 1) {
current = keyFunction && keyFunction(resolvedArray[i]);
if (current !== undefined && current < minNumber) {
minNumber = current;
minRecord = resolvedArray[i];
}
}
return minRecord;
};
this.functionNotNull = (resolvedArgs) => {
for (let i = 0; i < resolvedArgs.length; i += 1) {
if (this.getTypeName(resolvedArgs[i]) !== InputArgument.TYPE_NULL) {
return resolvedArgs[i];
}
}
return null;
};
this.functionReverse = ([inputValue]) => {
const typeName = this.getTypeName(inputValue);
if (typeName === InputArgument.TYPE_STRING) {
const originalStr = inputValue;
let reversedStr = '';
for (let i = originalStr.length - 1; i >= 0; i -= 1) {
reversedStr += originalStr[i];
}
return reversedStr;
}
const reversedArray = inputValue.slice(0);
reversedArray.reverse();
return reversedArray;
};
this.functionSort = ([inputValue]) => {
return [...inputValue].sort();
};
this.functionSortBy = (resolvedArgs) => {
if (!this._interpreter) {
return [];
}
const sortedArray = resolvedArgs[0].slice(0);
if (sortedArray.length === 0) {
return sortedArray;
}
const interpreter = this._interpreter;
const exprefNode = resolvedArgs[1];
const requiredType = this.getTypeName(interpreter.visit(exprefNode, sortedArray[0]));
if (requiredType !== undefined && ![InputArgument.TYPE_NUMBER, InputArgument.TYPE_STRING].includes(requiredType)) {
throw new Error(`TypeError: unexpected type (${this.TYPE_NAME_TABLE[requiredType]})`);
}
const decorated = [];
for (let i = 0; i < sortedArray.length; i += 1) {
decorated.push([i, sortedArray[i]]);
}
decorated.sort((a, b) => {
const exprA = interpreter.visit(exprefNode, a[1]);
const exprB = interpreter.visit(exprefNode, b[1]);
if (this.getTypeName(exprA) !== requiredType) {
throw new Error(`TypeError: expected (${this.TYPE_NAME_TABLE[requiredType]}), received ${this.TYPE_NAME_TABLE[this.getTypeName(exprA)]}`);
}
else if (this.getTypeName(exprB) !== requiredType) {
throw new Error(`TypeError: expected (${this.TYPE_NAME_TABLE[requiredType]}), received ${this.TYPE_NAME_TABLE[this.getTypeName(exprB)]}`);
}
if (exprA > exprB) {
return 1;
}
return exprA < exprB ? -1 : a[0] - b[0];
});
for (let j = 0; j < decorated.length; j += 1) {
sortedArray[j] = decorated[j][1];
}
return sortedArray;
};
this.functionStartsWith = ([searchable, searchStr]) => {
return searchable.startsWith(searchStr);
};
this.functionSum = ([inputValue]) => {
return inputValue.reduce((x, y) => x + y, 0);
};
this.functionToArray = ([inputValue]) => {
if (this.getTypeName(inputValue) === InputArgument.TYPE_ARRAY) {
return inputValue;
}
return [inputValue];
};
this.functionToNumber = ([inputValue]) => {
const typeName = this.getTypeName(inputValue);
let convertedValue;
if (typeName === InputArgument.TYPE_NUMBER) {
return inputValue;
}
if (typeName === InputArgument.TYPE_STRING) {
convertedValue = +inputValue;
if (!isNaN(convertedValue)) {
return convertedValue;
}
}
return null;
};
this.functionToString = ([inputValue]) => {
if (this.getTypeName(inputValue) === InputArgument.TYPE_STRING) {
return inputValue;
}
return JSON.stringify(inputValue);
};
this.functionType = ([inputValue]) => {
switch (this.getTypeName(inputValue)) {
case InputArgument.TYPE_NUMBER:
return 'number';
case InputArgument.TYPE_STRING:
return 'string';
case InputArgument.TYPE_ARRAY:
return 'array';
case InputArgument.TYPE_OBJECT:
return 'object';
case InputArgument.TYPE_BOOLEAN:
return 'boolean';
case InputArgument.TYPE_EXPREF:
return 'expref';
case InputArgument.TYPE_NULL:
return 'null';
default:
return;
}
};
this.functionValues = ([inputObject]) => {
return Object.values(inputObject);
};
this.functionTable = {
abs: {
_func: this.functionAbs,
_signature: [
{
types: [InputArgument.TYPE_NUMBER],
},
],
},
avg: {
_func: this.functionAvg,
_signature: [
{
types: [InputArgument.TYPE_ARRAY_NUMBER],
},
],
},
ceil: {
_func: this.functionCeil,
_signature: [
{
types: [InputArgument.TYPE_NUMBER],
},
],
},
contains: {
_func: this.functionContains,
_signature: [
{
types: [InputArgument.TYPE_STRING, InputArgument.TYPE_ARRAY],
},
{
types: [InputArgument.TYPE_ANY],
},
],
},
ends_with: {
_func: this.functionEndsWith,
_signature: [
{
types: [InputArgument.TYPE_STRING],
},
{
types: [InputArgument.TYPE_STRING],
},
],
},
floor: {
_func: this.functionFloor,
_signature: [
{
types: [InputArgument.TYPE_NUMBER],
},
],
},
join: {
_func: this.functionJoin,
_signature: [
{
types: [InputArgument.TYPE_STRING],
},
{
types: [InputArgument.TYPE_ARRAY_STRING],
},
],
},
keys: {
_func: this.functionKeys,
_signature: [
{
types: [InputArgument.TYPE_OBJECT],
},
],
},
length: {
_func: this.functionLength,
_signature: [
{
types: [InputArgument.TYPE_STRING, InputArgument.TYPE_ARRAY, InputArgument.TYPE_OBJECT],
},
],
},
map: {
_func: this.functionMap,
_signature: [
{
types: [InputArgument.TYPE_EXPREF],
},
{
types: [InputArgument.TYPE_ARRAY],
},
],
},
max: {
_func: this.functionMax,
_signature: [
{
types: [InputArgument.TYPE_ARRAY_NUMBER, InputArgument.TYPE_ARRAY_STRING],
},
],
},
max_by: {
_func: this.functionMaxBy,
_signature: [
{
types: [InputArgument.TYPE_ARRAY],
},
{
types: [InputArgument.TYPE_EXPREF],
},
],
},
merge: {
_func: this.functionMerge,
_signature: [
{
types: [InputArgument.TYPE_OBJECT],
variadic: true,
},
],
},
min: {
_func: this.functionMin,
_signature: [
{
types: [InputArgument.TYPE_ARRAY_NUMBER, InputArgument.TYPE_ARRAY_STRING],
},
],
},
min_by: {
_func: this.functionMinBy,
_signature: [
{
types: [InputArgument.TYPE_ARRAY],
},
{
types: [InputArgument.TYPE_EXPREF],
},
],
},
not_null: {
_func: this.functionNotNull,
_signature: [
{
types: [InputArgument.TYPE_ANY],
variadic: true,
},
],
},
reverse: {
_func: this.functionReverse,
_signature: [
{
types: [InputArgument.TYPE_STRING, InputArgument.TYPE_ARRAY],
},
],
},
sort: {
_func: this.functionSort,
_signature: [
{
types: [InputArgument.TYPE_ARRAY_STRING, InputArgument.TYPE_ARRAY_NUMBER],
},
],
},
sort_by: {
_func: this.functionSortBy,
_signature: [
{
types: [InputArgument.TYPE_ARRAY],
},
{
types: [InputArgument.TYPE_EXPREF],
},
],
},
starts_with: {
_func: this.functionStartsWith,
_signature: [
{
types: [InputArgument.TYPE_STRING],
},
{
types: [InputArgument.TYPE_STRING],
},
],
},
sum: {
_func: this.functionSum,
_signature: [
{
types: [InputArgument.TYPE_ARRAY_NUMBER],
},
],
},
to_array: {
_func: this.functionToArray,
_signature: [
{
types: [InputArgument.TYPE_ANY],
},
],
},
to_number: {
_func: this.functionToNumber,
_signature: [
{
types: [InputArgument.TYPE_ANY],
},
],
},
to_string: {
_func: this.functionToString,
_signature: [
{
types: [InputArgument.TYPE_ANY],
},
],
},
type: {
_func: this.functionType,
_signature: [
{
types: [InputArgument.TYPE_ANY],
},
],
},
values: {
_func: this.functionValues,
_signature: [
{
types: [InputArgument.TYPE_OBJECT],
},
],
},
};
this._interpreter = interpreter;
}
registerFunction(name, customFunction, signature) {
if (name in this.functionTable) {
throw new Error(`Function already defined: ${name}()`);
}
this.functionTable[name] = {
_func: customFunction.bind(this),
_signature: signature,
};
}
callFunction(name, resolvedArgs) {
const functionEntry = this.functionTable[name];
if (functionEntry === undefined) {
throw new Error(`Unknown function: ${name}()`);
}
this.validateArgs(name, resolvedArgs, functionEntry._signature);
return functionEntry._func.call(this, resolvedArgs);
}
validateInputSignatures(name, signature) {
for (let i = 0; i < signature.length; i += 1) {
if ('variadic' in signature[i] && i !== signature.length - 1) {
throw new Error(`ArgumentError: ${name}() 'variadic' argument ${i + 1} must occur last`);
}
}
}
validateArgs(name, args, signature) {
var _a, _b;
let pluralized;
this.validateInputSignatures(name, signature);
const numberOfRequiredArgs = signature.filter(argSignature => { var _a; return (_a = !argSignature.optional) !== null && _a !== void 0 ? _a : false; }).length;
const lastArgIsVariadic = (_b = (_a = signature[signature.length - 1]) === null || _a === void 0 ? void 0 : _a.variadic) !== null && _b !== void 0 ? _b : false;
const tooFewArgs = args.length < numberOfRequiredArgs;
const tooManyArgs = args.length > signature.length;
const tooFewModifier = tooFewArgs && ((!lastArgIsVariadic && numberOfRequiredArgs > 1) || lastArgIsVariadic) ? 'at least ' : '';
if ((lastArgIsVariadic && tooFewArgs) || (!lastArgIsVariadic && (tooFewArgs || tooManyArgs))) {
pluralized = signature.length > 1;
throw new Error(`ArgumentError: ${name}() takes ${tooFewModifier}${numberOfRequiredArgs} argument${(pluralized && 's') || ''} but received ${args.length}`);
}
let currentSpec;
let actualType;
let typeMatched;
for (let i = 0; i < signature.length; i += 1) {
typeMatched = false;
currentSpec = signature[i].types;
actualType = this.getTypeName(args[i]);
let j;
for (j = 0; j < currentSpec.length; j += 1) {
if (actualType !== undefined && this.typeMatches(actualType, currentSpec[j], args[i])) {
typeMatched = true;
break;
}
}
if (!typeMatched && actualType !== undefined) {
const expected = currentSpec
.map((typeIdentifier) => {
return this.TYPE_NAME_TABLE[typeIdentifier];
})
.join(' | ');
throw new Error(`TypeError: ${name}() expected argument ${i + 1} to be type (${expected}) but received type ${this.TYPE_NAME_TABLE[actualType]} instead.`);
}
}
}
typeMatches(actual, expected, argValue) {
if (expected === InputArgument.TYPE_ANY) {
return true;
}
if (expected === InputArgument.TYPE_ARRAY_STRING ||
expected === InputArgument.TYPE_ARRAY_NUMBER ||
expected === InputArgument.TYPE_ARRAY) {
if (expected === InputArgument.TYPE_ARRAY) {
return actual === InputArgument.TYPE_ARRAY;
}
if (actual === InputArgument.TYPE_ARRAY) {
let subtype;
if (expected === InputArgument.TYPE_ARRAY_NUMBER) {
subtype = InputArgument.TYPE_NUMBER;
}
else if (expected === InputArgument.TYPE_ARRAY_STRING) {
subtype = InputArgument.TYPE_STRING;
}
for (let i = 0; i < argValue.length; i += 1) {
const typeName = this.getTypeName(argValue[i]);
if (typeName !== undefined && subtype !== undefined && !this.typeMatches(typeName, subtype, argValue[i])) {
return false;
}
}
return true;
}
}
else {
return actual === expected;
}
return false;
}
getTypeName(obj) {
switch (Object.prototype.toString.call(obj)) {
case '[object String]':
return InputArgument.TYPE_STRING;
case '[object Number]':
return InputArgument.TYPE_NUMBER;
case '[object Array]':
return InputArgument.TYPE_ARRAY;
case '[object Boolean]':
return InputArgument.TYPE_BOOLEAN;
case '[object Null]':
return InputArgument.TYPE_NULL;
case '[object Object]':
if (obj.jmespathType === Lexer_1.Token.TOK_EXPREF) {
return InputArgument.TYPE_EXPREF;
}
return InputArgument.TYPE_OBJECT;
default:
return;
}
}
createKeyFunction(exprefNode, allowedTypes) {
if (!this._interpreter) {
return;
}
const interpreter = this._interpreter;
const keyFunc = (x) => {
const current = interpreter.visit(exprefNode, x);
if (!allowedTypes.includes(this.getTypeName(current))) {
const msg = `TypeError: expected one of (${allowedTypes
.map(t => this.TYPE_NAME_TABLE[t])
.join(' | ')}), received ${this.TYPE_NAME_TABLE[this.getTypeName(current)]}`;
throw new Error(msg);
}
return current;
};
return keyFunc;
}
}
exports.Runtime = Runtime;
//# sourceMappingURL=Runtime.js.map