UNPKG

@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
'use strict'; /** * 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 ); } };