UNPKG

@sap/odata-v4

Version:

OData V4.0 server library

188 lines (167 loc) 8.37 kB
'use strict'; const UriSyntaxError = require('../errors/UriSyntaxError'); const UriSemanticError = require('../errors/UriSemanticError'); const ErrorNames = require('../errors/AbstractError').ErrorNames; const UriHelper = require('./UriHelper'); const TokenKind = require('./UriTokenizer').TokenKind; const UriParameter = require('./UriParameter'); const EdmTypeKind = require('../edm/EdmType').TypeKind; const EdmPrimitiveTypeKind = require('../edm/EdmPrimitiveTypeKind'); const ExpressionKind = require('./Expression').ExpressionKind; const FullQualifiedName = require('../FullQualifiedName'); const typeKindToToken = new Map([ [EdmPrimitiveTypeKind.String, [TokenKind.StringValue]], [EdmPrimitiveTypeKind.Boolean, [TokenKind.BooleanValue]], [EdmPrimitiveTypeKind.SByte, [TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Byte, [TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Int16, [TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Int32, [TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Int64, [TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Guid, [TokenKind.GuidValue]], [EdmPrimitiveTypeKind.Decimal, [TokenKind.DecimalValue, TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Double, [TokenKind.DoubleValue, TokenKind.DecimalValue, TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Single, [TokenKind.DoubleValue, TokenKind.DecimalValue, TokenKind.IntegerValue]], [EdmPrimitiveTypeKind.Date, [TokenKind.DateValue]], [EdmPrimitiveTypeKind.DateTimeOffset, [TokenKind.DateTimeOffsetValue]], [EdmPrimitiveTypeKind.TimeOfDay, [TokenKind.TimeOfDayValue]], [EdmPrimitiveTypeKind.Duration, [TokenKind.DurationValue]], [EdmPrimitiveTypeKind.Binary, [TokenKind.BinaryValue]]]); /** * Abstract class for parsing simple key=value pairs in an uri. */ class KeyValueParser { /** * Constructor to set common values. * @param {Edm} edm The Entity Data Model * @param {Object} aliases Alias definitions */ constructor(edm, aliases) { this._edm = edm; this._aliases = new Object(aliases); // eslint-disable-line no-new-object } /** * Parses a uri parameter. This method decides which data type to read next in the uri string. * * @param {EdmType} edmType The current emd type to read for * @param {UriTokenizer} tokenizer The current tokenizer * @returns {?UriParameter} Returns an UriParameter (where the value could be null if a literal null was found) * or null if no token was found at all */ parseUriParameter(edmType, tokenizer) { if (tokenizer.next(TokenKind.ParameterAliasName)) { const alias = tokenizer.getText(); let parameter = new UriParameter().setAlias(alias); // ExpressionParser must be loaded at run-time, not statically at the beginning of this file, // to break the dependency cycle. const ExpressionParser = require('./ExpressionParser'); // eslint-disable-line global-require let expression = null; try { expression = new ExpressionParser(this._edm).parseAlias(alias, edmType, this._aliases); } catch (err) { throw err.name === ErrorNames.URI_QUERY_OPTION_SEMANTIC ? new UriSemanticError(err.message).setRootCause(err) : err; } if (expression.getKind() === ExpressionKind.LITERAL) { parameter.setAliasValue(expression.getText()); } else { parameter.setExpression(expression); } return parameter; } let type = edmType; let tokenKinds = null; if (edmType === 'nullable') { tokenKinds = [TokenKind.NULL]; } else if (edmType.getKind() === EdmTypeKind.ENUM) { tokenKinds = [TokenKind.EnumValue]; } else { if (edmType.getKind() === EdmTypeKind.DEFINITION) { type = edmType.getUnderlyingType(); } tokenKinds = typeKindToToken.get(type); } return tokenKinds.find(kind => tokenizer.next(kind)) ? new UriParameter().setText(this.fromUriLiteral(tokenizer.getText(), type)) : null; } /** * Build the normalized string literal form of a value according to its edm type. * * @param {string} uriLiteral The current uri literal * @param {EdmType} edmType The current edm type for converting the literal into * @returns {?string} the converted string or null if uriLiteral is null */ fromUriLiteral(uriLiteral, edmType) { if (edmType === 'nullable') { // We return null if the value of the parameter is // 'http://service/.../FunctionImport(ParameterString=null)/... return null; } if (edmType.getKind() === EdmTypeKind.ENUM) { // An enumeration-type literal value must start with the full-qualified type name. const typeName = uriLiteral.substring(0, uriLiteral.indexOf("'")); if (edmType !== this._edm.getEnumType(FullQualifiedName.createFromNameSpaceAndName(typeName))) { throw new UriSemanticError(UriSemanticError.Message.INCOMPATIBLE_TYPE, typeName, edmType.getFullQualifiedName()); } // Only pre-defined values (and their combinations, for flags) are allowed. const literalValue = uriLiteral.substring(typeName.length + 1, uriLiteral.length - 1); let enumValue = null; for (const value of literalValue.split(',')) { let memberValue = null; for (const [name, member] of edmType.getMembers()) { if (value === name || value === member.getValue().toString()) { memberValue = member.getValue(); break; } } if (memberValue === null || enumValue !== null && !edmType.isFlags()) { throw new UriSemanticError(UriSemanticError.Message.WRONG_VALUE, typeName, literalValue); } enumValue = enumValue === null ? memberValue : enumValue | memberValue; } } return UriHelper.fromUriLiteral(uriLiteral, edmType); } /** * Parse the key=value pairs in an uri. * * @param {UriTokenizer} tokenizer The current tokenizer * @param {Function} getPropertyFn The callback function to get the EdmProperty for a uri key. * Function will be called with string and must return an EdmProperty. * @param {Function} getTypesFn The callback function to get the EdmType for a given edm property. * Function will be called with EdmProperty and must return the EdmType * of that property. * @returns {Map.<string, UriParameter>} Map of key, value pairs */ parse(tokenizer, getPropertyFn, getTypesFn) { let result = new Map(); do { tokenizer.requireNext(TokenKind.ODataIdentifier); const keyName = tokenizer.getText(); if (result.get(keyName)) { throw new UriSemanticError(UriSemanticError.Message.DUPLICATE_KEY, keyName); } const property = getPropertyFn(keyName); if (!property) { // Throw an error if property does not exist for provided uri key name throw new UriSemanticError(UriSemanticError.Message.WRONG_KEY_NAME, keyName); } tokenizer.requireNext(TokenKind.EQ); const propertyTypes = getTypesFn(property); let parameter = null; for (const propertyType of propertyTypes) { parameter = this.parseUriParameter(propertyType, tokenizer); if (parameter) break; } if (!parameter) { throw new UriSyntaxError(UriSyntaxError.Message.KEY_VALUE_NOT_FOUND, propertyTypes.join("' or '"), keyName); } result.set(keyName, parameter); } while (tokenizer.next(TokenKind.COMMA)); return result; } } module.exports = KeyValueParser;