UNPKG

@sap/xsodata

Version:

Expose data from a HANA database as OData V2 service with help of .xsodata files.

342 lines (272 loc) 10.5 kB
'use strict'; var InternalError = require('./../utils/errors/internalError'); var XsODataError = require('./../utils/errors/xsODataError'); var Measurement = require('./measurement'); var uriType = require('../uri/uriType'); /** * Checks weather a schema is allowed to be used directly in the xsodata file. E.g. for cross schema access * * @param {string} schema * @returns {boolean} Return true if the usage of this schema is allowed */ exports.schemaAllowed = function (schema) { return (schema === 'SYS') || (schema.indexOf('_SYS_') === 0); }; exports.injectContext = function (context) { return function (asyncDone) { return asyncDone(null, context); }; }; exports.try = function (fn /* and more args */) { var args = Array.prototype.slice.call(arguments, 1); return function (context, asyncDone) { var args1 = Array.prototype.slice.call(arguments); try { return fn.apply(null, args.concat(args1)); } catch (err) { if (err instanceof XsODataError) { return asyncDone(err, context); } return asyncDone(new InternalError(null, context, err), context); } }; }; exports.tryAndMeasure = function (/* fn, and more args , name */) { var args = Array.prototype.slice.call(arguments, 0, arguments.length - 1); var name = arguments[arguments.length - 1]; return Measurement.measureAsync(exports.try.apply(null, args), name); }; exports.endsWith = function (hay, needle) { return hay.indexOf(needle, hay.length - needle.length) !== -1; }; exports.startsWith = function (hay, needle) { return hay.indexOf(needle) === 0; }; exports.contains = function (hay, needle) { return hay.indexOf(needle) > -1; }; exports.ensureSlashEnding = function (path) { if (!exports.endsWith(path, '/')) { return path + '/'; } return path; }; exports.getObjectPropertiesAsList = function (object) { var list = []; for (var property in object) { if (object.hasOwnProperty(property)) { list.push(object[property]); } } return list; }; exports.wrap = function (text) { return '"' + text + '"'; }; exports.clone = function (objectToClone) { return JSON.parse(JSON.stringify(objectToClone)); }; /** * Iterating over a collection (object | array) recursively (optional). * * This methods iterates over the provided context object own properties or * if the context is an array it iterates over its elements. * The iteration will be done recursively only if options.recursive|options.deep * property is provided and strict equals true. * For each iteration (object property | array element) the callback function * will be called only if the current item in iteration is an object and has a * property. * * Options.recursive || Options.deep * If these parameters strict equals true then this function does recursive calls. * If not only the first level is processed. * * Options.filter * With this function you can "filter out" key/objects you do not want to * call the callback function with. The filter must return false to * wipe out the current element. Otherwise callback function will be called * with key/object values. * * @param context {Object} Context object to loop over properties * @param callback {Function} Function called on each element * @param options {Object} options object * @returns undefined */ exports.iterateObject = function iterateObject(context, callback, options) { // If context is an array we loop over each element recursive if (Array.isArray(context)) { return context.forEach(function (elem) { return iterateObject(elem, callback, options); }); } if (!options) { options = {}; } if (!options.$currentKeyPath) { options.$currentKeyPath = []; } // We are only interested in objec keys. // If context is not an object we return immediately if (typeof context !== "object") { return context; } // Loop over object keys and find properties which could be prefixed // with namespaces for (var key in context) { if (context.hasOwnProperty(key)) { options.$currentKeyPath.push(key); var callbackResult = true; // If any filter function is provided we use that filter to call // the callback only on filter(...) === true properties if (options && typeof options.filter === "function") { if (options.filter(key, context) === true) { callbackResult = callback(key, context, options.$currentKeyPath.slice()); } } else { // If there is no filter we call the callback immediately callbackResult = callback(key, context, options.$currentKeyPath.slice()); } // Iterate over values recursive if (options && (options.recursive === true || options.deep === true) && callbackResult !== false) { // Check property object value. If it's an object we will // iterate through all nested objects recursive var value = context[key]; /*jshint eqnull:true */ if (value != null) { iterateObject(value, callback, options); } options.$currentKeyPath.pop(); } } } }; /** * Checks if the provided object has at least one own object property. * This method acts faster then Object.keys() or Object.getOwnPropertyNames(). * * @param obj {object} Object to test for properties * @returns {Boolean} True if obj has properties and is an object, else false. */ exports.hasProperties = function hasProperties(obj) { if (!obj || Array.isArray(obj)) { return false; } for (var key in obj) { if (obj.hasOwnProperty(key)) { return true; } } return false; }; /** * Checks if the given data is type of atom xml. * * Retuning true does not mean that the provided xml is valid or wellformed. * The provided string must be trimed (left out whitespaces at start/end). * * @param data {String} Xml data string * @returns {Boolean} True if is xml, else false */ exports.isXml = function isAtomXml(data) { data = "" + data; return exports.startsWith(data, "<") && exports.endsWith(data, ">"); }; /** * Get random integer between min and max. * Throws an error if min > max * * @param min {Number} Min value * @param max {Number} Max value * @returns {Number} Random number */ exports.getRandomInt = function getRandomInt(min, max) { /*jshint eqnull:true */ if (min == null) { min = 0; } /*jshint eqnull:true */ if (max == null) { max = Number.POSITIVE_INFINITY; } if (min > max) { throw Error("Minimum can not be greater than maximum"); } return Math.floor(Math.random() * (max - min + 1)) + min; }; // requests, for which ETag property/header must be generated var requestsWithETagHeader = { GET: {}, POST: {}, PUT: {} }, requestsWithETagProperty; requestsWithETagHeader.GET[uriType.URI2] = true; requestsWithETagHeader.GET[uriType.URI5A] = true; requestsWithETagHeader.GET[uriType.URI5B] = true; requestsWithETagHeader.GET[uriType.URI6A] = true; requestsWithETagHeader.POST[uriType.URI1] = true; requestsWithETagHeader.PUT[uriType.URI2] = true; requestsWithETagProperty = exports.clone(requestsWithETagHeader); requestsWithETagProperty.GET[uriType.URI1] = true; requestsWithETagProperty.GET[uriType.URI6B] = true; /** * Returns <code>true</code> if for the current request, represented as the <code>context</code> instance, an ETag * (i.e. "etag" property of "__metadata") must be generated for the entities of the specified <code>dbSegment</code>. * @param {object} context - OData context representing the current request * @param {object} dbSegment - DB segment * @returns {boolean} <code>true</code> if the ETag must be generated and <code>false</code> otherwise. */ exports.isETagRequired = function isETagRequired(context, dbSegment) { return isETagRequiredForRequest(context, dbSegment, requestsWithETagProperty); }; /** * Returns <code>true</code> if for the current request, represented as the <code>context</code> instance, an ETag * header must be generated for the HTTP response. * @param {object} context - OData context representing the current request * @param {object} dbSegment - DB segment * @returns {boolean} <code>true</code> if the ETag HTTP header must be generated and <code>false</code> otherwise. */ exports.isETagHeaderRequired = function isETagHeaderRequired(context, dbSegment) { return isETagRequiredForRequest(context, dbSegment, requestsWithETagHeader); }; function isETagRequiredForRequest(context, dbSegment, requestsWithETag) { var urisWithETag; if (!dbSegment.entityType.hasConcurrentProperties()) { return false; } urisWithETag = requestsWithETag[context.request.method]; if (!urisWithETag) { return false; } return !!urisWithETag[context.uriTree.uriType]; } /** * * @param a A valid db version e.g. "2.00.030.00.1515544046" * @param b A valid db version e.g. "2.00.000.00.0000000000" * @returns {exports.VERSION_A_COVERS_VERSION_B|exports.VERSIONS_DIFFERENT} */ exports.compareDBVersion = (a, b, fixedTo) => { const A = a.split('.'); const B = b.split('.'); if ((A.length !== 5) || (B.length !== 5)) { throw new Error('Invalid db version'); } for (let i = 0; i < A.length; i++) { A[i] = Number.parseInt(A[i]); B[i] = Number.parseInt(B[i]); } if (a === b) { return exports.VERSION_A_COVERS_VERSION_B; } else if ((fixedTo <= 0) && (A[0] > B[0]) || (fixedTo <= 1) && (A[0] === B[0] && A[1] > B[1]) || (fixedTo <= 2) && (A[0] === B[0] && A[1] === B[1] && A[2] > B[2]) || (fixedTo <= 3) && (A[0] === B[0] && A[1] === B[1] && A[2] === B[2] && A[3] > B[3]) || (fixedTo <= 4) && (A[0] === B[0] && A[1] === B[1] && A[2] === B[2] && A[3] === B[3] && A[4] > B[4])) { return exports.VERSION_A_COVERS_VERSION_B; } return exports.VERSIONS_DIFFERENT; }; exports.VERSION_A_COVERS_VERSION_B = 1; exports.VERSIONS_DIFFERENT = -1; exports.NO_FIX_PART = 0; exports.SAME_1ST_HANA_REL = 1; exports.SAME_2ND = 2; exports.SAME_3RD_SPS = 3;