@resin/pinejs
Version:
Pine.js is a sophisticated rules-driven API engine that enables you to define rules in a structured subset of English. Those rules are used in order for Pine.js to generate a database schema and the associated [OData](http://www.odata.org/) API. This make
238 lines • 9.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.translateUri = exports.parseOData = exports.metadataEndpoints = exports.memoizedParseOdata = exports.parseId = exports.SyntaxError = void 0;
const ODataParser = require("@balena/odata-parser");
const Bluebird = require("bluebird");
exports.SyntaxError = ODataParser.SyntaxError;
const odata_to_abstract_sql_1 = require("@resin/odata-to-abstract-sql");
const _ = require("lodash");
const memoize = require("memoizee");
const memoizeWeak = require("memoizee/weak");
var errors_1 = require("./errors");
Object.defineProperty(exports, "BadRequestError", { enumerable: true, get: function () { return errors_1.BadRequestError; } });
Object.defineProperty(exports, "ParsingError", { enumerable: true, get: function () { return errors_1.ParsingError; } });
Object.defineProperty(exports, "TranslationError", { enumerable: true, get: function () { return errors_1.TranslationError; } });
const deepFreeze = require("deep-freeze");
const env = require("../config-loader/env");
const errors_2 = require("./errors");
const sbvrUtils = require("./sbvr-utils");
exports.parseId = (b) => {
const { tree, binds } = ODataParser.parse(String(b), {
startRule: 'ProcessRule',
rule: 'KeyBind',
});
return binds[tree.bind];
};
exports.memoizedParseOdata = (() => {
const parseOdata = (url) => {
const odata = ODataParser.parse(url);
if (odata.tree.property != null &&
odata.tree.property.resource === 'canAccess') {
odata.tree.resource =
odata.tree.resource + '#' + odata.tree.property.resource;
}
return odata;
};
const _memoizedParseOdata = memoize(parseOdata, {
primitive: true,
max: env.cache.parseOData.max,
});
return (url) => {
const queryParamsIndex = url.indexOf('?');
if (queryParamsIndex !== -1) {
if (/[?&(]@/.test(url)) {
const parameterAliases = new URLSearchParams();
const queryParams = new URLSearchParams(url.slice(queryParamsIndex));
Array.from(queryParams.entries()).forEach(([key, value]) => {
if (key.startsWith('@')) {
parameterAliases.append(key, value);
queryParams.delete(key);
}
});
const parameterAliasesString = parameterAliases.toString();
if (parameterAliasesString !== '') {
const parsed = _.cloneDeep(_memoizedParseOdata(url.slice(0, queryParamsIndex) +
'?' +
decodeURIComponent(queryParams.toString())));
const parsedParams = ODataParser.parse(decodeURIComponent(parameterAliasesString), {
startRule: 'ProcessRule',
rule: 'QueryOptions',
});
if (parsed.tree.options == null) {
parsed.tree.options = {};
}
for (const key of Object.keys(parsedParams.tree)) {
parsed.tree.options[key] = parsedParams.tree[key];
parsed.binds[key] = parsedParams.binds[key];
}
return parsed;
}
}
return parseOdata(url);
}
else if (url.includes('(')) {
return parseOdata(url);
}
else {
return _.cloneDeep(_memoizedParseOdata(url));
}
};
})();
const memoizedGetOData2AbstractSQL = memoizeWeak((abstractSqlModel) => {
return new odata_to_abstract_sql_1.OData2AbstractSQL(abstractSqlModel);
});
const memoizedOdata2AbstractSQL = (() => {
const $memoizedOdata2AbstractSQL = memoizeWeak((abstractSqlModel, odataQuery, method, bodyKeys, existingBindVarsLength) => {
try {
const odata2AbstractSQL = memoizedGetOData2AbstractSQL(abstractSqlModel);
const abstractSql = odata2AbstractSQL.match(odataQuery, method, bodyKeys, existingBindVarsLength);
deepFreeze(abstractSql);
return abstractSql;
}
catch (e) {
if (e instanceof errors_2.PermissionError) {
throw e;
}
console.error('Failed to translate url: ', JSON.stringify(odataQuery, null, '\t'), method, e);
throw new errors_2.TranslationError('Failed to translate url');
}
}, {
normalizer: (_abstractSqlModel, [odataQuery, method, bodyKeys, existingBindVarsLength]) => {
return (JSON.stringify(odataQuery) +
method +
bodyKeys +
existingBindVarsLength);
},
max: env.cache.odataToAbstractSql.max,
});
return (request) => {
const { method, odataBinds, values } = request;
let { odataQuery } = request;
const abstractSqlModel = sbvrUtils.getAbstractSqlModel(request);
const sortedBody = Object.keys(values).sort();
if (odataQuery.options) {
odataQuery = {
...odataQuery,
options: _.pick(odataQuery.options, '$select', '$filter', '$expand', '$orderby', '$top', '$skip', '$count', '$inlinecount', '$format'),
};
}
const { tree, extraBodyVars, extraBindVars } = $memoizedOdata2AbstractSQL(abstractSqlModel, odataQuery, method, sortedBody, odataBinds.length);
Object.assign(values, extraBodyVars);
odataBinds.push(...extraBindVars);
return tree;
};
})();
exports.metadataEndpoints = ['$metadata', '$serviceroot'];
function parseOData(b) {
return Bluebird.try(async () => {
var _a;
try {
if (b._isChangeSet && b.changeSet != null) {
const sortedCS = _.sortBy(b.changeSet, (el) => el.url[0] !== '/');
const csReferences = await Bluebird.reduce(sortedCS, parseODataChangeset, new Map());
return Array.from(csReferences.values());
}
else {
const { url, apiRoot } = splitApiRoot(b.url);
const odata = exports.memoizedParseOdata(url);
return {
method: b.method,
url,
vocabulary: apiRoot,
resourceName: odata.tree.resource,
odataBinds: odata.binds,
odataQuery: odata.tree,
values: (_a = b.data) !== null && _a !== void 0 ? _a : {},
custom: {},
_defer: false,
};
}
}
catch (err) {
if (err instanceof ODataParser.SyntaxError) {
throw new errors_2.BadRequestError(`Malformed url: '${b.url}'`);
}
if (!(err instanceof errors_2.BadRequestError || err instanceof errors_2.ParsingError)) {
console.error('Failed to parse url: ', b.method, b.url, err);
throw new errors_2.ParsingError(`Failed to parse url: '${b.url}'`);
}
throw err;
}
});
}
exports.parseOData = parseOData;
const parseODataChangeset = (csReferences, b) => {
var _a;
const contentId = mustExtractHeader(b, 'content-id');
if (csReferences.has(contentId)) {
throw new errors_2.BadRequestError('Content-Id must be unique inside a changeset');
}
let defer;
let odata;
let apiRoot;
let url;
if (b.url[0] === '/') {
({ url, apiRoot } = splitApiRoot(b.url));
odata = exports.memoizedParseOdata(url);
defer = false;
}
else {
url = b.url;
odata = exports.memoizedParseOdata(url);
const { bind } = odata.tree.resource;
const [, id] = odata.binds[bind];
const ref = csReferences.get(id);
if (ref === undefined) {
throw new errors_2.BadRequestError('Content-Id refers to a non existent resource');
}
apiRoot = ref.vocabulary;
odata.tree.resource = ref.resourceName;
defer = true;
}
const parseResult = {
method: b.method,
url,
vocabulary: apiRoot,
resourceName: odata.tree.resource,
odataBinds: odata.binds,
odataQuery: odata.tree,
values: (_a = b.data) !== null && _a !== void 0 ? _a : {},
custom: {},
id: contentId,
_defer: defer,
};
csReferences.set(contentId, parseResult);
return csReferences;
};
const splitApiRoot = (url) => {
const urlParts = url.split('/');
const apiRoot = urlParts[1];
if (apiRoot == null) {
throw new errors_2.ParsingError(`No such api root: ${apiRoot}`);
}
url = '/' + urlParts.slice(2).join('/');
return { url, apiRoot };
};
const mustExtractHeader = (body, header) => {
var _a, _b;
const h = (_b = (_a = body.headers) === null || _a === void 0 ? void 0 : _a[header]) === null || _b === void 0 ? void 0 : _b[0];
if (_.isEmpty(h)) {
throw new errors_2.BadRequestError(`${header} must be specified`);
}
return h;
};
exports.translateUri = (request) => {
if (request.abstractSqlQuery != null) {
return request;
}
const isMetadataEndpoint = exports.metadataEndpoints.includes(request.resourceName) ||
request.method === 'OPTIONS';
if (!isMetadataEndpoint) {
const abstractSqlQuery = memoizedOdata2AbstractSQL(request);
request = { ...request };
request.abstractSqlQuery = abstractSqlQuery;
return request;
}
return request;
};
//# sourceMappingURL=uri-parser.js.map