@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
186 lines (166 loc) • 6.23 kB
JavaScript
;
/**
* Add <odata> to <context>>
* Uses <context>.<uriTree>
*
* @type {exports}
*/
//Includes
const oDataSegmentParser = require('../parsers/jison_segment_parser');
const queryParameterParser = require('./queryParameterParser');
const resourcePathParser = require('./resourcePathParser');
const uriSegmentTypes = require('./uriSegmentTypes');
const BadRequest = require('./../utils/errors/http/badRequest');
const Measurement = require('./../utils/measurement');
const typedObjects = require('./../utils/typedObjects');
const BadRequestError = require('./../utils/errors/http/badRequest');
// Values of the constants below correspond to the OData URI types
exports.URI_KIND_Service = 0;
exports.URI_KIND_MetaData = 8;
exports.URI_KIND_Batch = 9;
exports.URI_KIND_Resource = 4;
// unknown position indicator on URI parsing failure
const UNKNOWN_POSITION = -1;
function parseODataSegments(segments) {
const segmentsParsed = [];
if (segments.decoded.length === 0) {
segmentsParsed.push(new uriSegmentTypes.ServiceSegment(''));
} else {
for (const segment of segments.decoded) {
try {
const tmp = oDataSegmentParser.parse(segment);
segmentsParsed.push(tmp);
} catch (err) {
let position =
err.hash &&
err.hash.loc &&
err.hash.loc.last_column &&
Number.isInteger(err.hash.loc.last_column)
? err.hash.loc.last_column
: UNKNOWN_POSITION;
if (position === UNKNOWN_POSITION) {
throw new BadRequest(
`Syntax error in resource path. Character position could not be determined.`,
err
);
} else {
throw new BadRequest(
`Syntax error in resource path at position ${position + 1}`,
err
);
}
}
}
}
return segmentsParsed;
}
function parseQueryParameter(name, value) {
if (name === '$filter') {
return queryParameterParser.parseFilter(value);
} else if (name === '$orderby') {
return queryParameterParser.parseOrderBy(value);
} else if (name === '$expand') {
return queryParameterParser.parseExpand(value);
} else if (name === '$select') {
return queryParameterParser.parseSelect(value);
} else if (name === '$top') {
return queryParameterParser.parseTop(value);
} else if (name === '$skip') {
return queryParameterParser.parseSkip(value);
} else if (name === '$format') {
return queryParameterParser.parseFormat(value);
} else if (name === '$inlinecount') {
return queryParameterParser.parseInlineCount(value);
}
return null;
}
function parseSystemQueryParameters(queries) {
const queryParameter = {};
for (const prop in queries) {
if (Object.prototype.hasOwnProperty.call(queries, prop)) {
if (prop.startsWith('$')) {
const name = prop.substring(1);
queryParameter[name] = parseQueryParameter(prop, queries[prop]);
}
}
}
return queryParameter;
}
exports.parseODataUri = function (context, asyncDone) {
context.logger.silly('uri', 'parse OData uri');
const oData = {};
try {
context.logger.silly('uri', 'parse OData segments');
oData.segments = Measurement.measureSync(
parseODataSegments,
context.uriTree.segments,
'parseODataSegments'
);
} catch (err) {
return asyncDone(err, context);
}
try {
context.logger.silly('uri', 'parse OData system query parameters');
oData.systemQueryParameters = Measurement.measureSync(
parseSystemQueryParameters,
context.uriTree.rawUrlParts.query,
'parseSystemQueryParameters'
);
exports.validateQueryParameters(oData.systemQueryParameters, context);
} catch (err) {
return asyncDone(err, context);
}
const segment = oData.segments[0];
if (segment instanceof uriSegmentTypes.ServiceSegment) {
oData.kind = exports.URI_KIND_Service;
} else if (segment instanceof uriSegmentTypes.BatchSegment) {
oData.kind = exports.URI_KIND_Batch;
} else if (segment instanceof uriSegmentTypes.MetadataSegment) {
oData.kind = exports.URI_KIND_MetaData;
} else if (segment instanceof uriSegmentTypes.ResourceSegment) {
oData.kind = exports.URI_KIND_Resource;
try {
resourcePathParser.parseResourcePath(context, oData);
} catch (err) {
return asyncDone(err, context);
}
}
context.oData = oData;
return asyncDone(null, context);
};
/**
* Validates url query parameters. This method triggers validation of $filter query parameter
*
* @param queryParameters {Object} The query parameters
* @param context {Object} The xsodata context
*/
exports.validateQueryParameters = function (queryParameters, context) {
const query = context.uriTree.rawUrlParts.query;
if (queryParameters.filter || (query && query['$filter'])) {
exports.validate$filterExpression(queryParameters, context);
}
};
/**
* Validates url $filter expression.
*
* @param queryParameters {Object} The query parameters
* @param context {Object} The xsodata context
*/
exports.validate$filterExpression = function (queryParameters, context) {
const filter = queryParameters.filter;
const query = context.uriTree.rawUrlParts.query;
// Invalid $filter expression --> $filter=Id
if (filter && filter instanceof typedObjects.Property === true) {
throw new BadRequestError(
'Provided $filter expression is not valid',
context
);
}
// Invalid $filter expression --> $filter=Id$abc%20eq%20'1' --> with $ char
if (!filter && query && query['$filter']) {
throw new BadRequestError(
'Provided $filter expression is not valid',
context
);
}
};