UNPKG

@sap/odata-v4

Version:

OData V4.0 server library

793 lines (644 loc) 32.1 kB
'use strict'; const KeyPredicateParser = require('./KeyPredicateParser'); const FunctionParameterParser = require('./FunctionParameterParser'); const UriResource = require('./UriResource'); const UriTokenizer = require('./UriTokenizer'); const TokenKind = UriTokenizer.TokenKind; const FullQualifiedName = require('../FullQualifiedName'); const EdmTypeKind = require('../edm/EdmType').TypeKind; const EdmPrimitiveTypeKind = require('../edm/EdmPrimitiveTypeKind'); const UriSyntaxError = require('../errors/UriSyntaxError'); const UriSemanticError = require('../errors/UriSemanticError'); const NotImplementedError = require('../errors/NotImplementedError'); const FeatureSupport = require('../FeatureSupport'); /** * OData ABNF resource-path parser */ class ResourcePathParser { /** * Creates an instance of ResourcePathParser. * @param {Edm} edm The current instance of EDM * @param {Object} aliases Alias definitions */ constructor(edm, aliases) { this._edm = edm; this._aliases = aliases; this._edmContainer = this._edm.getEntityContainer(); this._target = null; } /** * Parse the current URI segments. These segments must not include any '/'. * * @param {string[]} uriPathSegments The current already percent-decoded URI segments * @throws {UriSyntaxError|UriSemanticError} if the URI could not be parsed * @returns {UriResource[]} An array of UriResource objects */ parse(uriPathSegments) { this._target = null; let result = []; let tokenizer = new UriTokenizer(uriPathSegments[0]); tokenizer.requireNext(TokenKind.ODataIdentifier); const currentToken = tokenizer.getText(); let currentResource = new UriResource(); let edmResult = this._edmContainer.getEntitySet(currentToken); if (edmResult) { currentResource .setKind(UriResource.ResourceKind.ENTITY_COLLECTION) .setIsCollection(true) .setEntitySet(edmResult); this._target = edmResult; return result .concat(this._parseCollectionNavigation(uriPathSegments, currentResource, tokenizer)); } edmResult = this._edmContainer.getSingleton(currentToken); if (edmResult) { FeatureSupport.failUnsupported(FeatureSupport.features.Singleton); currentResource .setKind(UriResource.ResourceKind.SINGLETON) .setSingleton(edmResult) .setIsCollection(false); this._target = edmResult; tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); return result.concat(this._parseSingleNavigation(uriPathSegments, currentResource)); } edmResult = this._edmContainer.getActionImport(currentToken); if (edmResult) { const unboundAction = edmResult.getUnboundAction(); this._target = edmResult.getReturnedEntitySet(); currentResource.setKind(UriResource.ResourceKind.ACTION_IMPORT) .setActionImport(edmResult) .setAction(unboundAction) .setIsCollection( unboundAction.getReturnType() ? unboundAction.getReturnType().isCollection() : false) .setTarget(this._target); tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); return result.concat(currentResource); } edmResult = this._edmContainer.getFunctionImport(currentToken); if (edmResult) { const functions = edmResult.getUnboundFunctions(); const returnType = functions[0].getReturnType(); const isCollection = returnType.isCollection(); const kind = returnType.getType().getKind(); this._parseFunctionImport(currentResource, edmResult, tokenizer); const unboundFunction = currentResource.getFunction(); if (unboundFunction && !unboundFunction.isComposable() && uriPathSegments.length > 1) { throw new UriSyntaxError(UriSyntaxError.Message.FUNCTION_NOT_COMPOSABLE, unboundFunction.getName(), uriPathSegments[uriPathSegments.length - 1]); } this._target = edmResult.getReturnedEntitySet(); if (kind === EdmTypeKind.ENTITY) { currentResource.setTarget(this._target); if (isCollection) { // We do not need EOF / uriPathSegments.shift() because we try to parse // the next key (if available). This is done in the current segment. return [].concat(this._parseCollectionNavigation(uriPathSegments, currentResource, tokenizer)); } // If the return type is not a collection, no keys are allowed. if (tokenizer.next(TokenKind.OPEN)) { throw new UriSyntaxError(UriSyntaxError.Message.FUNCTION_IMPORT_EOF, edmResult.getName()); } tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); return result.concat(this._parseSingleNavigation(uriPathSegments, currentResource)); } // Only entities can have keys. if (tokenizer.next(TokenKind.OPEN)) { throw new UriSyntaxError(UriSyntaxError.Message.FUNCTION_IMPORT_EOF, edmResult.getName()); } tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); let functionRest; if (isCollection) { functionRest = kind === EdmTypeKind.COMPLEX ? this._parseComplexCollectionPath(uriPathSegments, currentResource) : this._parsePrimitiveCollectionPath(uriPathSegments, currentResource); } else { functionRest = kind === EdmTypeKind.COMPLEX ? this._parseComplexPath(uriPathSegments, currentResource) : this._parsePrimitivePath(uriPathSegments, currentResource); } return functionRest ? result.concat(currentResource, functionRest) : result.concat(currentResource); } throw new UriSemanticError(UriSemanticError.Message.WRONG_NAME, currentToken); } /** * Parse a function import. * * @param {UriResource} currentResource The current uri resource object * @param {EdmFunctionImport} functionImport The current function import * @param {UriTokenizer} tokenizer The current tokenizer * @private */ _parseFunctionImport(currentResource, functionImport, tokenizer) { currentResource.setKind(UriResource.ResourceKind.FUNCTION_IMPORT); currentResource.setFunctionImport(functionImport); // Parse function-import parameters. const functions = functionImport.getUnboundFunctions(); const parser = new FunctionParameterParser(this._edm, this._aliases); parser.parse(functionImport.getName(), functions, tokenizer, visitedParameters => { return functionImport.getUnboundFunction(Array.from(visitedParameters.keys())); }); const unboundFunction = parser.getFunction(); if (!unboundFunction) { throw new UriSyntaxError(UriSyntaxError.Message.FUNCTION_IMPORT_FUNCTION_NOT_FOUND, tokenizer.getText()); } currentResource .setFunction(unboundFunction) .setFunctionParameters(parser.getParameters()) .setIsCollection(unboundFunction.getReturnType().isCollection()); } /** * Parse OData ABNF collection navigation. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} currentResourceParam The current resource * @param {UriTokenizer} tokenizerParam The current tokenizer * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parseCollectionNavigation(uriPathSegments, currentResourceParam, tokenizerParam) { const result = []; let currentResource = currentResourceParam; let tokenizer = tokenizerParam; if (tokenizer.next(TokenKind.EOF) && uriPathSegments.length > 1) { const typeTokenizer = new UriTokenizer(uriPathSegments[1]); if (typeTokenizer.next(TokenKind.QualifiedName)) { const qualifiedName = typeTokenizer.getText(); const typeCastResource = this._parseTypeCast(currentResource, qualifiedName); if (typeCastResource) { uriPathSegments.shift(); result.push(currentResource); currentResource = typeCastResource; tokenizer = typeTokenizer; } } } return result.concat(this._parseCollectionNavPath(uriPathSegments, currentResource, tokenizer)); } /** * Parse OData type cast. * * @param {UriResource} prevResource The previous resource * @param {string} qualifiedName The qualified name of the current uri segment * @returns {?UriResource} a UriResource or null * @private */ _parseTypeCast(prevResource, qualifiedName) { const fqn = FullQualifiedName.createFromNameSpaceAndName(qualifiedName); const currentType = prevResource.getEdmType(); const type = currentType.getKind() === EdmTypeKind.ENTITY ? this._edm.getEntityType(fqn) : this._edm.getComplexType(fqn); if (!type) return null; // Type casts are explicitly not supported (although the parser can parse them). FeatureSupport.failUnsupported(FeatureSupport.features.TypeCast, qualifiedName, 0); if (!type.compatibleTo(currentType)) { throw new UriSemanticError(UriSemanticError.Message.INCOMPATIBLE_TYPE, qualifiedName, currentType.getFullQualifiedName()); } return new UriResource() .setKind(UriResource.ResourceKind.TYPE_CAST) .setTypeCast(type) .setIsCollection(prevResource.isCollection()); } /** * Parse OData ABNF collection nav path. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} currentResource The current resource * @param {UriTokenizer} tokenizer The current tokenizer * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parseCollectionNavPath(uriPathSegments, currentResource, tokenizer) { if (tokenizer.next(TokenKind.OPEN)) { if (tokenizer.next(TokenKind.CLOSE)) { throw new UriSyntaxError(UriSyntaxError.Message.KEY_EXPECTED); } const edmType = currentResource.getEdmType(); const keyPredicates = new KeyPredicateParser(this._edm, this._aliases) .parse(currentResource, edmType, tokenizer); tokenizer.requireNext(TokenKind.CLOSE); currentResource.setKeyPredicates(keyPredicates) .setIsCollection(false); if (currentResource.getKind() === UriResource.ResourceKind.ENTITY_COLLECTION) { currentResource.setKind(UriResource.ResourceKind.ENTITY); } if (currentResource.getKind() === UriResource.ResourceKind.NAVIGATION_TO_MANY) { currentResource.setKind(UriResource.ResourceKind.NAVIGATION_TO_ONE); } tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); return this._parseSingleNavigation(uriPathSegments, currentResource); } tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); if (uriPathSegments.length === 0) return currentResource; let nextTokenizer = new UriTokenizer(uriPathSegments[0]); const refResource = this._parseConstant( uriPathSegments, nextTokenizer, TokenKind.REF, currentResource.isCollection() ? UriResource.ResourceKind.REF_COLLECTION : UriResource.ResourceKind.REF); if (refResource) { refResource.setIsCollection(currentResource.isCollection()); return [currentResource].concat(refResource); } let countResource = this._parseConstant( uriPathSegments, nextTokenizer, TokenKind.COUNT, UriResource.ResourceKind.COUNT); if (countResource) { countResource.setIsCollection(currentResource.isCollection()); return [currentResource].concat(countResource); } if (nextTokenizer.next(TokenKind.QualifiedName)) { return [currentResource].concat(this._parseBoundOperation(uriPathSegments, currentResource, nextTokenizer)); } throw new UriSyntaxError(UriSyntaxError.Message.MUST_BE_COUNT_OR_REF_OR_BOUND_OPERATION, uriPathSegments[0]); } /** * Parse OData ABNF single navigation. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parseSingleNavigation(uriPathSegments, prevResource) { if (uriPathSegments.length === 0) return prevResource; let result = [prevResource]; let currentResource = prevResource; let tokenizer = new UriTokenizer(uriPathSegments[0]); // parse qualifiedEntityTypeName if (tokenizer.next(TokenKind.QualifiedName)) { const typeCastResource = this._parseTypeCast(currentResource, tokenizer.getText()); if (typeCastResource) { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); tokenizer = new UriTokenizer(uriPathSegments[0]); result.push(typeCastResource); currentResource = typeCastResource; if (uriPathSegments.length === 0) return result; } else { // parse boundOperation return result.concat(this._parseBoundOperation(uriPathSegments, currentResource, tokenizer)); } } // parse bound operation if (tokenizer.next(TokenKind.QualifiedName)) { return result.concat(this._parseBoundOperation(uriPathSegments, currentResource, tokenizer)); } // parse $ref const refResource = this._parseConstant( uriPathSegments, tokenizer, TokenKind.REF, UriResource.ResourceKind.REF); if (refResource) return result.concat(refResource); // parse $value const valueResource = this._parseConstant( uriPathSegments, tokenizer, TokenKind.VALUE, UriResource.ResourceKind.VALUE); if (valueResource) { const currentType = currentResource.getEdmType(); if (currentType.hasStream()) { return result.concat(valueResource); } throw new UriSyntaxError(UriSyntaxError.Message.PREVIOUS_TYPE_HAS_NO_MEDIA, currentType.getName()); } return result.concat(this._parsePropertyPath(uriPathSegments, currentResource, tokenizer)); } /** * Parse any of the OData bound operations. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} currentResource The current resource * @param {UriTokenizer} tokenizer The current tokenizer * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parseBoundOperation(uriPathSegments, currentResource, tokenizer) { const fqn = FullQualifiedName.createFromNameSpaceAndName(tokenizer.getText()); const bindingParamTypeFqn = currentResource.getEdmType().getFullQualifiedName(); // parse bound action const isCollection = currentResource.isCollection(); const boundAction = this._edm.getBoundAction(fqn, bindingParamTypeFqn, isCollection); if (boundAction) { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); this._target = boundAction.getReturnedEntitySet(this._target); return new UriResource() .setKind(UriResource.ResourceKind.BOUND_ACTION) .setAction(boundAction) .setIsCollection(boundAction.getReturnType() != null && boundAction.getReturnType().isCollection()) .setTarget(this._target); } // parse bound function return this._parseBoundFunction(uriPathSegments, tokenizer, fqn, bindingParamTypeFqn, isCollection); } /** * Parse an OData bound function. * * @param {string[]} uriPathSegments The uri path segments * @param {UriTokenizer} tokenizer The current tokenizer * @param {FullQualifiedName} fqn full qualified name of the function * @param {FullQualifiedName} bindingParamTypeFqn full qualified name of the type of the binding parameter * @param {boolean} previousIsCollection whether the previous resource is a collection * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parseBoundFunction(uriPathSegments, tokenizer, fqn, bindingParamTypeFqn, previousIsCollection) { let result = []; let currentResource = new UriResource().setKind(UriResource.ResourceKind.BOUND_FUNCTION); const availableBoundFunctions = this._edm.getBoundFunctions(fqn, bindingParamTypeFqn, previousIsCollection); const getOverloadingFunctionFn = (visitedParameters) => this._edm.getBoundFunction( fqn, bindingParamTypeFqn, previousIsCollection, Array.from(visitedParameters.keys())); const parser = new FunctionParameterParser(this._edm, this._aliases); parser.parse(fqn.toString(), availableBoundFunctions, tokenizer, getOverloadingFunctionFn); currentResource.setFunctionParameters(parser.getParameters()); let currentBoundFunction = parser.getFunction(); if (!currentBoundFunction) { throw new UriSemanticError(UriSemanticError.Message.NEITHER_STRUCTURED_TYPE_NOR_BOUND_OPERATION, fqn.toString()); } if (!currentBoundFunction.isComposable() && uriPathSegments.length > 1) { throw new UriSyntaxError(UriSyntaxError.Message.FUNCTION_NOT_COMPOSABLE, currentBoundFunction.getName(), uriPathSegments[uriPathSegments.length - 1]); } const returnTypeEdmTypeKind = currentBoundFunction.getReturnType().getType().getKind(); const returnTypeIsCollection = currentBoundFunction.getReturnType().isCollection(); currentResource .setFunction(currentBoundFunction) .setIsCollection(returnTypeIsCollection); this._target = currentBoundFunction.getReturnedEntitySet(this._target); if (returnTypeEdmTypeKind === EdmTypeKind.ENTITY) { currentResource.setTarget(this._target); if (returnTypeIsCollection) { result = result.concat(this._parseCollectionNavigation(uriPathSegments, currentResource, tokenizer)); } else { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); result = result.concat(this._parseSingleNavigation(uriPathSegments, currentResource)); } } else if (returnTypeEdmTypeKind === EdmTypeKind.COMPLEX) { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); result.push(currentResource); const resultTemp = returnTypeIsCollection ? this._parseComplexCollectionPath(uriPathSegments, currentResource) : this._parseComplexPath(uriPathSegments, currentResource); if (resultTemp) { result = result.concat(resultTemp); } } else { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); result.push(currentResource); const resultTemp = returnTypeIsCollection ? this._parsePrimitiveCollectionPath(uriPathSegments, currentResource) : this._parsePrimitivePath(uriPathSegments, currentResource); if (resultTemp) { result = result.concat(resultTemp); } } return result; } /** * Parse OData property path. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @param {UriTokenizer} tokenizer The current tokenizer * @throws UriSemanticError If no property path was found * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parsePropertyPath(uriPathSegments, prevResource, tokenizer) { tokenizer.requireNext(TokenKind.ODataIdentifier); // Try to find navigation property. let navProp = this._parseNavigationProperty(uriPathSegments, prevResource, tokenizer); if (navProp) return navProp; // Try to find structural property. let structuralProperty = this._parseProperty(uriPathSegments, prevResource, tokenizer); if (structuralProperty) return structuralProperty; // If we can not find any other artifact an error should be thrown. throw new UriSemanticError(UriSemanticError.Message.PROPERTY_NOT_FOUND, tokenizer.getText(), prevResource.getEdmType().getName()); } /** * Parse OData navigation property. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @param {UriTokenizer} tokenizer The current tokenizer * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parseNavigationProperty(uriPathSegments, prevResource, tokenizer) { let propertyName = tokenizer.getText(); let navProp = prevResource.getEdmType().getNavigationProperty(propertyName); if (!navProp) return null; const isCollection = navProp.isCollection(); this._target = this._target === null ? null : this._target.getRelatedBindingTarget(propertyName); let newResource = new UriResource() .setNavigationProperty(navProp) .setIsCollection(isCollection) .setTarget(this._target); if (isCollection) { newResource.setKind(UriResource.ResourceKind.NAVIGATION_TO_MANY); return this._parseCollectionNavigation(uriPathSegments, newResource, tokenizer); } newResource.setKind(UriResource.ResourceKind.NAVIGATION_TO_ONE); if (!tokenizer.next(TokenKind.EOF)) { throw new UriSyntaxError(UriSyntaxError.Message.NAVIGATION_PROPERTY_EOF, propertyName, tokenizer.getParseString(), tokenizer.getPosition()); } uriPathSegments.shift(); return this._parseSingleNavigation(uriPathSegments, newResource); } /** * Parse OData property. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @param {UriTokenizer} tokenizer The current tokenizer * @returns {UriResource|UriResource[]|null} Returns UriResource, array of UriResource, or null * @private */ _parseProperty(uriPathSegments, prevResource, tokenizer) { let propertyName = tokenizer.getText(); let property = prevResource.getEdmType().getStructuralProperty(propertyName); if (property) { const isCollection = property.isCollection(); let newResource = new UriResource().setProperty(property).setIsCollection(isCollection); let isComplex = false; switch (property.getType().getKind()) { case EdmTypeKind.PRIMITIVE: case EdmTypeKind.ENUM: case EdmTypeKind.DEFINITION: newResource.setKind(isCollection ? UriResource.ResourceKind.PRIMITIVE_COLLECTION_PROPERTY : UriResource.ResourceKind.PRIMITIVE_PROPERTY); break; case EdmTypeKind.COMPLEX: isComplex = true; newResource.setKind(isCollection ? UriResource.ResourceKind.COMPLEX_COLLECTION_PROPERTY : UriResource.ResourceKind.COMPLEX_PROPERTY); break; default: throw new NotImplementedError(); } if (!tokenizer.next(TokenKind.EOF)) { throw new UriSyntaxError(UriSyntaxError.Message.PROPERTY_EOF, propertyName, tokenizer.getParseString(), tokenizer.getPosition()); } uriPathSegments.shift(); let trailingPath; if (isCollection) { trailingPath = isComplex ? this._parseComplexCollectionPath(uriPathSegments, newResource) : this._parsePrimitiveCollectionPath(uriPathSegments, newResource); } else { trailingPath = isComplex ? this._parseComplexPath(uriPathSegments, newResource) : this._parsePrimitivePath(uriPathSegments, newResource); } return trailingPath ? [newResource].concat(trailingPath) : newResource; } return null; } /** * Parse OData complex path. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @returns {UriResource|UriResource[]} Returns an UriResource or array of UriResource * @private */ _parseComplexPath(uriPathSegments, prevResource) { if (uriPathSegments.length === 0) return null; let result = []; let currentResource = prevResource; let tokenizer = new UriTokenizer(uriPathSegments[0]); let qualifiedName = null; if (tokenizer.next(TokenKind.QualifiedName)) { qualifiedName = tokenizer.getText(); const typeCastResource = this._parseTypeCast(currentResource, qualifiedName); if (typeCastResource) { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); qualifiedName = null; if (uriPathSegments.length === 0) return typeCastResource; tokenizer = new UriTokenizer(uriPathSegments[0]); result.push(currentResource); currentResource = typeCastResource; } } if (!qualifiedName && tokenizer.next(TokenKind.QualifiedName)) { qualifiedName = tokenizer.getText(); } if (qualifiedName) { return result.concat(this._parseBoundOperation(uriPathSegments, currentResource, tokenizer)); } return result.concat(this._parsePropertyPath(uriPathSegments, currentResource, tokenizer)); } /** * Parse odata primitive path. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @returns {UriResource|UriResource[]|null} UriResource, array of UriResource or null * @private */ _parsePrimitivePath(uriPathSegments, prevResource) { if (uriPathSegments.length === 0) return null; const tokenizer = new UriTokenizer(uriPathSegments[0]); if (prevResource.getEdmType() !== EdmPrimitiveTypeKind.Stream) { const valueResource = this._parseConstant(uriPathSegments, tokenizer, TokenKind.VALUE, UriResource.ResourceKind.VALUE); if (valueResource) { valueResource.setIsCollection(false); return valueResource; } } tokenizer.requireNext(TokenKind.QualifiedName); return this._parseBoundOperation(uriPathSegments, prevResource, tokenizer); } /** * Parse odata primitive collection path. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @returns {UriResource|UriResource[]|null} UriResource, array of UriResource or null * @private */ _parsePrimitiveCollectionPath(uriPathSegments, prevResource) { if (uriPathSegments.length === 0) return null; // parse $count const tokenizer = new UriTokenizer(uriPathSegments[0]); const countResource = this._parseConstant(uriPathSegments, tokenizer, TokenKind.COUNT, UriResource.ResourceKind.COUNT); if (countResource) { countResource.setIsCollection(true); return countResource; } if (tokenizer.next(TokenKind.QualifiedName)) { return this._parseBoundOperation(uriPathSegments, prevResource, tokenizer); } throw new UriSyntaxError(UriSyntaxError.Message.MUST_BE_COUNT_OR_BOUND_OPERATION, uriPathSegments[0]); } /** * Parse OData complex collection path. * * @param {string[]} uriPathSegments The uri path segments * @param {UriResource} prevResource The previous resource * @returns {UriResource[]} array of UriResource * @private */ _parseComplexCollectionPath(uriPathSegments, prevResource) { if (uriPathSegments.length === 0) return null; let tokenizer = new UriTokenizer(uriPathSegments[0]); let result = []; let currentResource = prevResource; if (tokenizer.next(TokenKind.QualifiedName)) { const typeCastResource = this._parseTypeCast(prevResource, tokenizer.getText()); if (typeCastResource) { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); result.push(typeCastResource); if (uriPathSegments.length === 0) return result; tokenizer = new UriTokenizer(uriPathSegments[0]); currentResource = typeCastResource; } else { return result.concat(this._parseBoundOperation(uriPathSegments, currentResource, tokenizer)); } } // parse $count const countResource = this._parseConstant(uriPathSegments, tokenizer, TokenKind.COUNT, UriResource.ResourceKind.COUNT); if (countResource) { countResource.setIsCollection(true); result.push(countResource); return result; } if (tokenizer.next(TokenKind.QualifiedName)) { return result.concat(this._parseBoundOperation(uriPathSegments, currentResource, tokenizer)); } throw new UriSyntaxError(UriSyntaxError.Message.MUST_BE_COUNT_OR_BOUND_OPERATION, uriPathSegments[0]); } /** * Parse OData ABNF constant like $ref or $value. * * @param {string[]} uriPathSegments THe current uri path segments * @param {UriTokenizer} tokenizer The current tokenizer * @param {UriTokenizer.TokenKind} tokenKind The token kind to read next * @param {UriResource.ResourceKind} resourceKind The resource kind to set if token read was successful * @returns {?UriResource} UriResource or null * @private */ _parseConstant(uriPathSegments, tokenizer, tokenKind, resourceKind) { if (tokenizer.next(tokenKind)) { tokenizer.requireNext(TokenKind.EOF); uriPathSegments.shift(); return new UriResource().setKind(resourceKind); } return null; } } module.exports = ResourcePathParser;