@sap/odata-v4
Version:
OData V4.0 server library
188 lines (167 loc) • 8.37 kB
JavaScript
;
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;