UNPKG

@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
"use strict"; 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