@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
JavaScript
;
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;