UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

146 lines (124 loc) 4.69 kB
/** @typedef {import('../../lib/srv/cds.Service')} Service */ const cds = require('../..') const { decodeURIComponent } = cds.utils const odata2cqn = require('./parse/parser').parse const cqn2odata = require('./parse/cqn2odata') const afterburner = require('./parse/afterburner') const { getSafeNumber: safeNumber, skipToken } = require('./utils') // used for function validation in peggy parser // ----- should all be lowercase, as peggy compares to lowercase ----- // (plus: odata is case insensitive) const strict = { functions: { // --- String + Collection: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360980 concat: 1, contains: 1, endswith: 1, indexof: 1, length: 1, startswith: 1, substring: 1, // --- Collection: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360988 // REVISIT: not supported // hassubset:1, // hassubsequence:1, // --- String: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360991 matchespattern: 1, tolower: 1, toupper: 1, trim: 1, // --- Date + Time: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360996 date: 1, day: 1, fractionalseconds: 1, hour: 1, maxdatetime: 1, mindatetime: 1, minute: 1, month: 1, now: 1, second: 1, time: 1, totaloffsetminutes: 1, totalseconds: 1, year: 1, // --- Arithemetic: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361011 ceiling: 1, floor: 1, round: 1, // --- Type: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361015 // REVISIT: not supported // cast: 1, // REVISIT: has to be implemented inside the odata adapter // isof: 1, // --- Geo: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361018 // REVISIT: not supported // 'geo.distance': 1, // 'geo.intersects': 1, // 'geo.length': 1, // --- Conditional: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361022 case: 1 } } const _enhanceCqn = (cqn, options) => { if (options.afterburner) { /** @type Service */ const service = options.service let { model, namespace } = service if (service.isExtensible) model = cds.context?.model || model cqn = options.afterburner(cqn, model, namespace, options.protocol) } const query = cds.ql(cqn) // REVISIT: _target vs __target, i.e., pseudo csn vs actual csn // DO NOT USE __target outside of libx/rest!!! if (options.protocol === 'rest' && cqn.__target) query.__target = cqn.__target if (cqn._propertyAccess) Object.defineProperty(query, '_propertyAccess', { value: cqn._propertyAccess, enumerable: false }) return query } /* * cds.odata API */ module.exports = { parse: (url, options = {}) => { // first arg may also be req if (url.url) url = url.url url = decodeURIComponent(url) // REVISIT: compat for bad url in mtxs tests (cf. #957) if (url.match(/\?\?/)) { const split = url.split('?') url = split.shift() + '?' while (split[0] === '') split.shift() url += split.join('?') } options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options if (options.service?.model) Object.assign(options, { minimal: true, afterburner }) options.safeNumber = safeNumber options.skipToken = skipToken let cqn try { cqn = odata2cqn(url, options) } catch (err) { if (err.statusCode === 501) throw new cds.error(err.statusCode, err.message) let offset = err.location && err.location.start.offset if (!offset && err.statusCode && err.message) throw err // we need to add the number of chars from base url to the offset offset += options.baseUrl ? options.baseUrl.length : 0 // TODO adjust this to behave like above err.message = `Parsing URL failed at position ${offset}: ${err.message}` err.statusCode = err.statusCode || 400 throw err } // cqn is an array, if concat is used if (Array.isArray(cqn)) { for (let i = 0; i < cqn.length; i++) cqn[i] = _enhanceCqn(cqn[i], options) } else { cqn = _enhanceCqn(cqn, options) } // REVISIT: SELECT.from._params is a temporary hack if (cqn.SELECT?.from?._params) delete cqn.SELECT.from._params return cqn }, urlify: (cqn, options = {}) => { return cqn2odata(cqn, options) } }