示例#1
0
        /// <summary>
        /// Parses a select or expand term into a query token
        /// </summary>
        /// <returns>parsed query token</returns>
        private PathSegmentToken ParseSelectExpandProperty()
        {
            PathSegmentToken token = null;
            int currentDepth       = 0;

            do
            {
                currentDepth++;
                if (currentDepth > this.maxDepth)
                {
                    throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_TooDeep);
                }

                this.Lexer.NextToken();

                // allow a single trailing slash for backwards compatibility with the WCF DS Server parser.
                if (currentDepth > 1 && this.Lexer.CurrentToken.Kind == ExpressionTokenKind.End)
                {
                    break;
                }

                token = this.ParseNext(token);
            }while (this.Lexer.CurrentToken.Kind == ExpressionTokenKind.Slash);

            return(token);
        }
示例#2
0
        /// <summary>
        /// Parses a single term in a comma seperated list of things to expand.
        /// </summary>
        /// <returns>A token representing thing to expand.</returns>
        private ExpandTermToken ParseSingleExpandTerm()
        {
            this.isSelect = false;

            var termParser             = new SelectExpandTermParser(this.lexer, this.MaxPathDepth, this.isSelect);
            PathSegmentToken pathToken = termParser.ParseTerm();

            string optionsText = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                optionsText = this.lexer.AdvanceThroughBalancedParentheticalExpression();

                // Move lexer to what is after the parenthesis expression. Now CurrentToken will be the next thing.
                this.lexer.NextToken();
            }

            if (expandOptionParser == null)
            {
                expandOptionParser = new ExpandOptionParser(this.maxRecursiveDepth, enableCaseInsensitiveBuiltinIdentifier)
                {
                    MaxFilterDepth  = MaxFilterDepth,
                    MaxOrderByDepth = MaxOrderByDepth,
                    MaxSearchDepth  = MaxSearchDepth
                };
            }

            return(this.expandOptionParser.BuildExpandTermToken(pathToken, optionsText));
        }
        /// <summary>
        /// Build a wildcard selection item
        /// </summary>
        /// <param name="tokenIn">the token to bind to a wildcard</param>
        /// <param name="model">the model to search for this wildcard</param>
        /// <param name="item">the new wildcard selection item, if we found one</param>
        /// <returns>true if we successfully bound to a wildcard, false otherwise</returns>
        public static bool TryBindAsWildcard(PathSegmentToken tokenIn, IEdmModel model, out SelectItem item)
        {
            bool isTypeToken = tokenIn.IsNamespaceOrContainerQualified();
            bool wildcard = tokenIn.Identifier.EndsWith("*", StringComparison.Ordinal);

            if (isTypeToken && wildcard)
            {
                string namespaceName = tokenIn.Identifier.Substring(0, tokenIn.Identifier.Length - 2);

                if (model.DeclaredNamespaces.Any(declaredNamespace => declaredNamespace.Equals(namespaceName, StringComparison.Ordinal)))
                {
                    item = new NamespaceQualifiedWildcardSelectItem(namespaceName);
                    return true;
                }
            }

            if (tokenIn.Identifier == "*")
            {
                item = new WildcardSelectItem();
                return true;
            }

            item = null;
            return false;
        }
示例#4
0
        /// <summary>
        /// Uses the ExpressionLexer to visit the next ExpressionToken, and delegates parsing of segments, type segments, identifiers,
        /// and the star token to other methods.
        /// </summary>
        /// <param name="previousSegment">Previously parsed PathSegmentToken, or null if this is the first token.</param>
        /// <returns>A parsed PathSegmentToken representing the next segment in this path.</returns>
        private PathSegmentToken ParseSegment(PathSegmentToken previousSegment)
        {
            if (this.lexer.CurrentToken.Text.StartsWith("$", StringComparison.CurrentCulture))
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_SystemTokenInSelectExpand(this.lexer.CurrentToken.Text, this.lexer.ExpressionText));
            }

            string propertyName;

            if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
            {
                propertyName = this.lexer.ReadDottedIdentifier(this.isSelect);
            }
            else if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star)
            {
                if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash)
                {
                    throw new ODataException(ODataErrorStrings.ExpressionToken_IdentifierExpected(this.lexer.Position));
                }

                propertyName = this.lexer.CurrentToken.Text;
                this.lexer.NextToken();
            }
            else
            {
                propertyName = this.lexer.CurrentToken.GetIdentifier();
                this.lexer.NextToken();
            }

            return(new NonSystemToken(propertyName, null, previousSegment));
        }
示例#5
0
        /// <summary>
        /// Parses a segment; a expression that is followed by a slash.
        /// </summary>
        /// <param name="parent">The parent of the segment node.</param>
        /// <returns>The lexical token representing the segment.</returns>
        private PathSegmentToken ParseSegment(PathSegmentToken parent)
        {
            string propertyName;

            if (this.Lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
            {
                propertyName = this.Lexer.ReadDottedIdentifier(this.isSelect);
            }
            else if (this.Lexer.CurrentToken.Kind == ExpressionTokenKind.Star)
            {
                if (this.Lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash)
                {
                    throw new ODataException(ODataErrorStrings.ExpressionToken_IdentifierExpected(this.Lexer.Position));
                }

                propertyName = this.Lexer.CurrentToken.Text;
                this.Lexer.NextToken();
            }
            else
            {
                propertyName = this.Lexer.CurrentToken.GetIdentifier();
                this.Lexer.NextToken();
            }

            return(new NonSystemToken(propertyName, null, parent));
        }
        /// <summary>
        /// Follow a chain of structrual properties until we hit a non-structural property
        /// </summary>
        /// <param name="firstStructuralProperty">the first structural property we hit</param>
        /// <param name="firstNonStructuralProperty">the first non structural property we hit</param>
        /// <returns>a comma separated list of structural properties</returns>
        private string WriteNextStructuralProperties(PathSegmentToken firstStructuralProperty, out PathSegmentToken firstNonStructuralProperty)
        {
            firstNonStructuralProperty = firstStructuralProperty;
            string stringToWrite = "";

            while (firstNonStructuralProperty.IsStructuralProperty)
            {
                if (firstNonStructuralProperty.NextToken != null)
                {
                    if (firstNonStructuralProperty.NextToken.IsStructuralProperty)
                    {
                        stringToWrite += firstNonStructuralProperty.Identifier + ",";
                    }
                    else
                    {
                        stringToWrite += firstNonStructuralProperty.Identifier;
                    }

                    firstNonStructuralProperty = firstNonStructuralProperty.NextToken;
                }
                else
                {
                    stringToWrite += firstNonStructuralProperty.Identifier;
                    firstNonStructuralProperty = null;
                    return(stringToWrite);
                }
            }

            return(stringToWrite);
        }
        public void NormalizeTreeResultsInReversedPath()
        {
            // Arrange: $select=1/2/3
            NonSystemToken endPath = new NonSystemToken("3", null, new NonSystemToken("2", null, new NonSystemToken("1", null, null)));

            Assert.Equal("3/2/1", endPath.ToPathString());

            SelectToken selectToken = new SelectToken(new SelectTermToken[]
            {
                new SelectTermToken(endPath)
            });

            // Act
            SelectToken normalizedToken = SelectTreeNormalizer.NormalizeSelectTree(selectToken);

            // Assert
            Assert.NotNull(normalizedToken);
            SelectTermToken  updatedSegmentToken = Assert.Single(normalizedToken.SelectTerms);
            PathSegmentToken segmentToken        = updatedSegmentToken.PathToProperty;

            segmentToken.ShouldBeNonSystemToken("1")
            .NextToken.ShouldBeNonSystemToken("2")
            .NextToken.ShouldBeNonSystemToken("3");

            Assert.Equal("1/2/3", segmentToken.ToPathString());
        }
        /// <summary>
        /// Build a segment from a token.
        /// </summary>
        /// <param name="tokenIn">the token to bind</param>
        /// <param name="model">The model.</param>
        /// <param name="edmType">the type of the current scope based on type segments.</param>
        /// <returns>The segment created from the token.</returns>
        public static ODataPathSegment ConvertNonTypeTokenToSegment(PathSegmentToken tokenIn, IEdmModel model, IEdmStructuredType edmType)
        {
            ODataPathSegment nextSegment;

            if (TryBindAsDeclaredProperty(tokenIn, edmType, out nextSegment))
            {
                return(nextSegment);
            }

            // Operations must be container-qualified, and because the token type indicates it was not a .-seperated identifier, we should not try to look up operations.
            if (tokenIn.IsNamespaceOrContainerQualified())
            {
                if (TryBindAsOperation(tokenIn, model, edmType, out nextSegment))
                {
                    return(nextSegment);
                }

                // If an action or function is requested in a selectItem using a qualifiedActionName or a qualifiedFunctionName
                // and that operation cannot be bound to the entities requested, the service MUST ignore the selectItem.
                if (!edmType.IsOpen)
                {
                    return(null);
                }
            }

            if (edmType.IsOpen)
            {
                return(new OpenPropertySegment(tokenIn.Identifier));
            }

            throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(edmType.ODataFullName(), tokenIn.Identifier));
        }
示例#9
0
        /// <summary>
        /// Get the path string for a path segment token.
        /// </summary>
        /// <param name="head">The head of the path</param>
        /// <returns>The path string.</returns>
        public static string ToPathString(this PathSegmentToken head)
        {
            StringBuilder    sb   = new StringBuilder();
            PathSegmentToken curr = head;

            while (curr != null)
            {
                sb.Append(curr.Identifier);

                NonSystemToken nonSystem = curr as NonSystemToken;
                if (nonSystem != null && nonSystem.NamedValues != null)
                {
                    sb.Append("(");
                    sb.Append(string.Join(",", nonSystem.NamedValues.Select(c => c.Name + "=" + c.Value.Value)));
                    sb.Append(")");
                }

                curr = curr.NextToken;
                if (curr != null)
                {
                    sb.Append("/");
                }
            }

            return(sb.ToString());
        }
        /// <summary>
        /// Invert the all of the paths in an expandToken, such that they are now in the same order as they are present in the
        /// base url
        /// </summary>
        /// <param name="treeToInvert">the tree to invert paths on</param>
        /// <returns>a new tree with all of its paths inverted</returns>
        public ExpandToken NormalizePaths(ExpandToken treeToInvert)
        {
            // iterate through each expand term token, and reverse the tree in its path property
            List <ExpandTermToken> updatedTerms = new List <ExpandTermToken>();

            foreach (ExpandTermToken term in treeToInvert.ExpandTerms)
            {
                PathReverser     pathReverser = new PathReverser();
                PathSegmentToken reversedPath = term.PathToNavProp.Accept(pathReverser);

                // we also need to call the select token normalizer for this level to reverse the select paths
                SelectToken newSelectToken = term.SelectOption;
                if (term.SelectOption != null)
                {
                    SelectTreeNormalizer selectTreeNormalizer = new SelectTreeNormalizer();
                    newSelectToken = selectTreeNormalizer.NormalizeSelectTree(term.SelectOption);
                }

                ExpandToken subExpandTree;
                if (term.ExpandOption != null)
                {
                    subExpandTree = this.NormalizePaths(term.ExpandOption);
                }
                else
                {
                    subExpandTree = null;
                }

                ExpandTermToken newTerm = new ExpandTermToken(reversedPath, term.FilterOption, term.OrderByOptions, term.TopOption, term.SkipOption, term.CountQueryOption, term.LevelsOption, term.SearchOption, newSelectToken, subExpandTree);
                updatedTerms.Add(newTerm);
            }

            return(new ExpandToken(updatedTerms));
        }
示例#11
0
        /// <summary>
        /// Initializes a new instance of  <see cref="SelectExpandTermToken"/> class.
        /// </summary>
        /// <param name="pathToProperty">the path to property for this select or expand term</param>
        /// <param name="filterOption">the filter option for this select or expand term</param>
        /// <param name="orderByOptions">the orderby options for this select or expand term</param>
        /// <param name="topOption">the top option for this select or expand term</param>
        /// <param name="skipOption">the skip option for this select or expand term</param>
        /// <param name="countQueryOption">the query count option for this select or expand term</param>
        /// <param name="searchOption">the search option for this select or expand term</param>
        /// <param name="selectOption">the select option for this select or expand term</param>
        /// <param name="expandOption">the expand option for this select or expand term</param>
        /// <param name="computeOption">the compute option for this select or expand term.</param>
        protected SelectExpandTermToken(
            PathSegmentToken pathToProperty,
            QueryToken filterOption,
            IEnumerable <OrderByToken> orderByOptions,
            long?topOption,
            long?skipOption,
            bool?countQueryOption,
            QueryToken searchOption,
            SelectToken selectOption,
            ExpandToken expandOption,
            ComputeToken computeOption)
        {
            ExceptionUtils.CheckArgumentNotNull(pathToProperty, "property");

            PathToProperty   = pathToProperty;
            FilterOption     = filterOption;
            OrderByOptions   = orderByOptions;
            TopOption        = topOption;
            SkipOption       = skipOption;
            CountQueryOption = countQueryOption;
            SearchOption     = searchOption;
            SelectOption     = selectOption;
            ExpandOption     = expandOption;
            ComputeOption    = computeOption;
        }
示例#12
0
        /// <summary>
        /// Create an expand term token
        /// </summary>
        /// <param name="pathToNavigationProp">the nav prop for this expand term</param>
        /// <param name="filterOption">the filter option for this expand term</param>
        /// <param name="orderByOptions">the orderby options for this expand term</param>
        /// <param name="topOption">the top option for this expand term</param>
        /// <param name="skipOption">the skip option for this expand term</param>
        /// <param name="countQueryOption">the query count option for this expand term</param>
        /// <param name="levelsOption">the levels option for this expand term</param>
        /// <param name="searchOption">the search option for this expand term</param>
        /// <param name="selectOption">the select option for this expand term</param>
        /// <param name="expandOption">the expand option for this expand term</param>
        /// <param name="computeOption">the compute option for this expand term.</param>
        public ExpandTermToken(
            PathSegmentToken pathToNavigationProp,
            QueryToken filterOption,
            IEnumerable <OrderByToken> orderByOptions,
            long?topOption,
            long?skipOption,
            bool?countQueryOption,
            long?levelsOption,
            QueryToken searchOption,
            SelectToken selectOption,
            ExpandToken expandOption,
            ComputeToken computeOption)
        {
            ExceptionUtils.CheckArgumentNotNull(pathToNavigationProp, "property");

            this.pathToNavigationProp = pathToNavigationProp;
            this.filterOption         = filterOption;
            this.orderByOptions       = orderByOptions;
            this.topOption            = topOption;
            this.skipOption           = skipOption;
            this.countQueryOption     = countQueryOption;
            this.levelsOption         = levelsOption;
            this.searchOption         = searchOption;
            this.selectOption         = selectOption;
            this.computeOption        = computeOption;
            this.expandOption         = expandOption;
        }
        /// <summary>
        /// Build a wildcard selection item
        /// </summary>
        /// <param name="tokenIn">the token to bind to a wildcard</param>
        /// <param name="model">the model to search for this wildcard</param>
        /// <param name="item">the new wildcard selection item, if we found one</param>
        /// <returns>true if we successfully bound to a wildcard, false otherwise</returns>
        public static bool TryBindAsWildcard(PathSegmentToken tokenIn, IEdmModel model, out SelectItem item)
        {
            bool isTypeToken = tokenIn.IsNamespaceOrContainerQualified();
            bool wildcard    = tokenIn.Identifier.EndsWith("*", StringComparison.Ordinal);

            if (isTypeToken && wildcard)
            {
                string namespaceName = tokenIn.Identifier.Substring(0, tokenIn.Identifier.Length - 2);

                if (model.DeclaredNamespaces.Any(declaredNamespace => declaredNamespace.Equals(namespaceName, StringComparison.Ordinal)))
                {
                    item = new NamespaceQualifiedWildcardSelectItem(namespaceName);
                    return(true);
                }
            }

            if (tokenIn.Identifier == "*")
            {
                item = new WildcardSelectItem();
                return(true);
            }

            item = null;
            return(false);
        }
示例#14
0
        public static AndConstraint <NonSystemToken> ShouldBeNonSystemToken(this PathSegmentToken token, string tokenIdentifier)
        {
            token.Should().BeOfType <NonSystemToken>();
            NonSystemToken nonSystemToken = token.As <NonSystemToken>();

            nonSystemToken.Identifier.Should().Be(tokenIdentifier);
            return(new AndConstraint <NonSystemToken>(nonSystemToken));
        }
示例#15
0
        public static AndConstraint <SystemToken> ShouldBeSystemToken(this PathSegmentToken token, string tokenIdentifier)
        {
            token.Should().BeOfType <SystemToken>();
            SystemToken systemToken = token.As <SystemToken>();

            systemToken.Identifier.Should().Be(tokenIdentifier);
            return(new AndConstraint <SystemToken>(systemToken));
        }
示例#16
0
 internal static string ToDebugString(this PathSegmentToken token)
 {
     if (token == null)
     {
         return("(null)");
     }
     return(token.Identifier);
 }
示例#17
0
        public static NonSystemToken ShouldBeNonSystemToken(this PathSegmentToken token, string tokenIdentifier)
        {
            Assert.NotNull(token);
            NonSystemToken nonSystemToken = Assert.IsType <NonSystemToken>(token);

            Assert.Equal(tokenIdentifier, nonSystemToken.Identifier);
            return(nonSystemToken);
        }
示例#18
0
        public static SystemToken ShouldBeSystemToken(this PathSegmentToken token, string tokenIdentifier)
        {
            Assert.NotNull(token);
            SystemToken systemToken = Assert.IsType <SystemToken>(token);

            Assert.Equal(tokenIdentifier, systemToken.Identifier);
            return(systemToken);
        }
示例#19
0
        /// <summary>
        /// Add a new ParameterExpression to the stack.
        /// </summary>
        /// <param name="pe">The parameter expression to add.</param>
        public void PushParamExpression(ParameterExpression pe)
        {
            PathSegmentToken basePath = expandPaths.LastOrDefault();

            basePaths.Add(pe, basePath);
            expandPaths.Remove(basePath);
            parameterExpressions.Push(pe);
        }
示例#20
0
        public void ReversePathWorksWithSingleSegment()
        {
            // $expand=1
            PathReverser     pathReverser    = new PathReverser();
            PathSegmentToken nonReversedPath = new NonSystemToken("1", null, null);
            PathSegmentToken reversedPath    = nonReversedPath.Accept(pathReverser);

            reversedPath.ShouldBeNonSystemToken("1").NextToken.Should().BeNull();
        }
        public void NullHeadReturnsNull()
        {
            // Arrange & Act
            PathSegmentToken head         = null;
            PathSegmentToken reversedPath = head.Reverse();

            // Assert
            Assert.Null(reversedPath);
        }
示例#22
0
        public void ReversePathWorksWithATypeToken()
        {
            // $expand=Fully.Qualified.Namespace/1
            PathReverser     pathReverser    = new PathReverser();
            PathSegmentToken nonReversedPath = new NonSystemToken("1", null, new NonSystemToken("Fully.Qualified.Namespace", null, null));
            PathSegmentToken reversedPath    = nonReversedPath.Accept(pathReverser);

            reversedPath.ShouldBeNonSystemToken("Fully.Qualified.Namespace").NextToken.ShouldBeNonSystemToken("1");
        }
示例#23
0
        public void ReversePathWorksWithStarToken()
        {
            // $expand=1/*
            PathReverser     pathReverser    = new PathReverser();
            PathSegmentToken nonReversedPath = new NonSystemToken("*", null, new NonSystemToken("1", null, null));
            PathSegmentToken reversedPath    = nonReversedPath.Accept(pathReverser);

            reversedPath.ShouldBeNonSystemToken("1").NextToken.ShouldBeNonSystemToken("*");
        }
        /// <summary>
        /// Build the list of expand options
        /// Depends on whether options are allowed or not.
        /// </summary>
        /// <param name="isInnerTerm">is this an inner expand term</param>
        /// <param name="pathToken">the current level token, as a PathToken</param>
        /// <returns>An expand term token based on the path token.</returns>
        internal override ExpandTermToken BuildExpandTermToken(bool isInnerTerm, PathSegmentToken pathToken)
        {
            DebugUtils.CheckNoExternalCallers();
            if (this.IsNotEndOfTerm(false))
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.Lexer.ExpressionText));
            }

            return(new ExpandTermToken(pathToken));
        }
示例#25
0
        /// <summary>
        /// Parses a single term in a comma seperated list of things to expand.
        /// </summary>
        /// <param name="isInnerTerm">is this an inner or outer term.</param>
        /// <returns>A token representing thing to expand.</returns>
        public ExpandTermToken ParseSingleExpandTerm(bool isInnerTerm)
        {
            this.isSelect = false;
            this.RecurseEnter();

            PathSegmentToken property = this.ParseSelectExpandProperty();

            this.RecurseLeave();
            return(this.BuildExpandTermToken(isInnerTerm, property));
        }
示例#26
0
 /// <summary>
 /// Uses the ExpressionLexer to visit the next ExpressionToken, and delegates parsing of segments, type segments, identifiers,
 /// and the star token to other methods.
 /// </summary>
 /// <param name="previousToken">Previously parsed QueryToken, or null if this is the first token.</param>
 /// <returns>A parsed QueryToken representing the next part of the expression.</returns>
 private PathSegmentToken ParseNext(PathSegmentToken previousToken)
 {
     if (this.Lexer.CurrentToken.Text.StartsWith("$", StringComparison.CurrentCulture))
     {
         throw new ODataException(ODataErrorStrings.UriSelectParser_SystemTokenInSelectExpand(this.Lexer.CurrentToken.Text, this.Lexer.ExpressionText));
     }
     else
     {
         return(this.ParseSegment(previousToken));
     }
 }
示例#27
0
        /// <summary>
        /// Uses the ExpressionLexer to visit the next ExpressionToken, and delegates parsing of segments, type segments, identifiers,
        /// and the star token to other methods.
        /// </summary>
        /// <param name="previousSegment">Previously parsed PathSegmentToken, or null if this is the first token.</param>
        /// <param name="allowRef">Whether the $ref operation is valid in this token.</param>
        /// <returns>A parsed PathSegmentToken representing the next segment in this path.</returns>
        private PathSegmentToken ParseSegment(PathSegmentToken previousSegment, bool allowRef)
        {
            // TODO $count is defined in specification for expand, it is not supported now. Also note $count is not supported with star as expand option.
            if (this.lexer.CurrentToken.Text.StartsWith("$", StringComparison.Ordinal) && (!allowRef || this.lexer.CurrentToken.Text != UriQueryConstants.RefSegment))
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_SystemTokenInSelectExpand(this.lexer.CurrentToken.Text, this.lexer.ExpressionText));
            }

            // Some check here to throw exception, both prop1/*/prop2 and */$ref/prop will throw exception, both are for $expand cases
            if (!isSelect)
            {
                if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.Star && this.lexer.CurrentToken.GetIdentifier() != UriQueryConstants.RefSegment)
                {
                    // Star can only be followed with $ref
                    throw new ODataException(ODataErrorStrings.ExpressionToken_OnlyRefAllowWithStarInExpand);
                }
                else if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.RefSegment)
                {
                    // $ref should not have more property followed.
                    throw new ODataException(ODataErrorStrings.ExpressionToken_NoPropAllowedAfterRef);
                }
            }

            string propertyName;

            if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
            {
                propertyName = this.lexer.ReadDottedIdentifier(this.isSelect);
            }
            else if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star)
            {
                // "*/$ref" is supported in expand
                if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash && isSelect)
                {
                    throw new ODataException(ODataErrorStrings.ExpressionToken_IdentifierExpected(this.lexer.Position));
                }
                else if (previousSegment != null && !isSelect)
                {
                    // expand option like "customer?$expand=VIPCUstomer/*" is not allowed as specification does not allowed any property before *.
                    throw new ODataException(ODataErrorStrings.ExpressionToken_NoSegmentAllowedBeforeStarInExpand);
                }

                propertyName = this.lexer.CurrentToken.Text;
                this.lexer.NextToken();
            }
            else
            {
                propertyName = this.lexer.CurrentToken.GetIdentifier();
                this.lexer.NextToken();
            }

            return(new NonSystemToken(propertyName, null, previousSegment));
        }
示例#28
0
        /// <summary>
        /// Parses a single term in a comma seperated list of things to select.
        /// </summary>
        /// <param name="isInnerTerm">is this an inner or outer select term</param>
        /// <returns>A token representing thing to select.</returns>
        public PathSegmentToken ParseSingleSelectTerm(bool isInnerTerm)
        {
            this.isSelect = true;
            PathSegmentToken path = this.ParseSelectExpandProperty();

            if (this.IsNotEndOfTerm(isInnerTerm))
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.Lexer.ExpressionText));
            }

            return(path);
        }
示例#29
0
        public void ReversePathWorksWithDeepPath()
        {
            // $expand=1/2/3/4
            PathReverser     pathReverser = new PathReverser();
            NonSystemToken   endPath      = new NonSystemToken("4", null, new NonSystemToken("3", null, new NonSystemToken("2", null, new NonSystemToken("1", null, null))));
            PathSegmentToken reversedPath = endPath.Accept(pathReverser);

            reversedPath.ShouldBeNonSystemToken("1")
            .NextToken.ShouldBeNonSystemToken("2")
            .NextToken.ShouldBeNonSystemToken("3")
            .NextToken.ShouldBeNonSystemToken("4");
        }
示例#30
0
        public void InnerSelectSetCorrectly()
        {
            // Arrange & Act
            SelectToken     select     = new SelectToken(new PathSegmentToken[] { new NonSystemToken("abc", null, null) });
            SelectTermToken selectTerm = new SelectTermToken(new NonSystemToken("stuff", null, null), select);

            // Assert
            Assert.NotNull(selectTerm.SelectOption);
            PathSegmentToken token          = Assert.Single(selectTerm.SelectOption.Properties);
            NonSystemToken   nonSystemToken = Assert.IsType <NonSystemToken>(token);

            Assert.Equal("abc", nonSystemToken.Identifier);
        }
        public void ReversePathWorksWithSingleSegment()
        {
            // Arrange: $expand=1
            PathSegmentToken nonReversedPath = new NonSystemToken("1", null, null);

            // Act
            PathSegmentToken reversedPath = nonReversedPath.Reverse();

            // Assert
            NonSystemToken nonSystemToken = reversedPath.ShouldBeNonSystemToken("1");

            Assert.Null(nonSystemToken.NextToken);
        }
        /// <summary>
        /// Translate a NonSystemToken.
        /// </summary>
        /// <param name="tokenIn">The NonSystemToken to translate.</param>
        public void Visit(NonSystemToken tokenIn)
        {
            if (tokenIn.Identifier != UriHelper.ASTERISK.ToString())
            {
                if (tokenIn.NextToken == null)
                {
                    return;
                }

                previous = tokenIn;
                tokenIn.NextToken.Accept(this);
            }
            else
            {
                previous.SetNextToken(null);
                return;
            }
        }
        /// <summary>
        /// Follow any type segments on the path, stopping at the first segment that isn't a type token.
        /// </summary>
        /// <param name="firstTypeToken">the first type segment</param>
        /// <param name="model">the model these types are contained in.</param>
        /// <param name="maxDepth">the maximum recursive depth</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <param name="currentLevelType">the top level type, will be overwritten with the last entity type in the chain</param>
        /// <param name="firstNonTypeToken">the first non type token in the path</param>
        /// <returns>A path with type segments added to it.</returns>
        public static IEnumerable<ODataPathSegment> FollowTypeSegments(PathSegmentToken firstTypeToken, IEdmModel model, int maxDepth, ODataUriResolver resolver, ref IEdmStructuredType currentLevelType, out PathSegmentToken firstNonTypeToken)
        {
            ExceptionUtils.CheckArgumentNotNull(firstTypeToken, "firstTypeToken");
            ExceptionUtils.CheckArgumentNotNull(model, "model");

            if (!firstTypeToken.IsNamespaceOrContainerQualified())
            {
                throw new ODataException(ODataErrorStrings.SelectExpandPathBinder_FollowNonTypeSegment(firstTypeToken.Identifier));
            }

            int index = 0;
            List<ODataPathSegment> pathToReturn = new List<ODataPathSegment>();
            PathSegmentToken currentToken = firstTypeToken;
            while (currentToken.IsNamespaceOrContainerQualified() && currentToken.NextToken != null)
            {
                IEdmType previousLevelEntityType = currentLevelType;
                currentLevelType = UriEdmHelpers.FindTypeFromModel(model, currentToken.Identifier, resolver) as IEdmStructuredType;
                if (currentLevelType == null)
                {
                    // TODO: fix this error message?
                    throw new ODataException(ODataErrorStrings.ExpandItemBinder_CannotFindType(currentToken.Identifier));
                }

                UriEdmHelpers.CheckRelatedTo(previousLevelEntityType, currentLevelType);
                pathToReturn.Add(new TypeSegment(currentLevelType, /*entitySet*/null));

                index++;
                currentToken = currentToken.NextToken;

                if (index >= maxDepth)
                {
                    throw new ODataException(ODataErrorStrings.ExpandItemBinder_PathTooDeep);
                }
            }

            firstNonTypeToken = currentToken;

            return pathToReturn;
        }
        /// <summary>
        /// Build a segment from a token.
        /// </summary>
        /// <param name="tokenIn">the token to bind</param>
        /// <param name="model">The model.</param>
        /// <param name="edmType">the type of the current scope based on type segments.</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <returns>The segment created from the token.</returns>
        public static ODataPathSegment ConvertNonTypeTokenToSegment(PathSegmentToken tokenIn, IEdmModel model, IEdmStructuredType edmType, ODataUriResolver resolver = null)
        {
            if (resolver == null)
            {
                resolver = ODataUriResolver.Default;
            }

            ODataPathSegment nextSegment;
            if (TryBindAsDeclaredProperty(tokenIn, edmType, resolver, out nextSegment))
            {
                return nextSegment;
            }

            // Operations must be container-qualified, and because the token type indicates it was not a .-seperated identifier, we should not try to look up operations.
            if (tokenIn.IsNamespaceOrContainerQualified())
            {
                if (TryBindAsOperation(tokenIn, model, edmType, out nextSegment))
                {
                    return nextSegment;
                }

                // If an action or function is requested in a selectItem using a qualifiedActionName or a qualifiedFunctionName 
                // and that operation cannot be bound to the entities requested, the service MUST ignore the selectItem.
                if (!edmType.IsOpen)
                {
                    return null;
                }
            }

            if (edmType.IsOpen)
            {
                return new OpenPropertySegment(tokenIn.Identifier);
            }

            throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(edmType.ODataFullName(), tokenIn.Identifier));
        }
示例#35
0
        /// <summary>
        /// Building off of a PathSegmentToken whose value is star, only nested level options is allowed.
        /// </summary>
        /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param>
        /// <returns>An expand term token based on the path token, and all available expand options.</returns>
        private List<ExpandTermToken> BuildStarExpandTermToken(PathSegmentToken pathToken)
        {
            List<ExpandTermToken> expandTermTokenList = new List<ExpandTermToken>();
            long? levelsOption = null;
            bool isRefExpand = (pathToken.Identifier == UriQueryConstants.RefSegment);

            // Based on the specification, 
            //   For star in expand, this will be supported,
            //   $expand=*
            //   $expand=EntitySet($expand=* )
            //   $expand=*/$ref 
            //   $expand=*,EntitySet
            //   $expand=EntitySet, *
            //   $expand=*/$ref,EntitySet
            //   Parenthesized set of expand options for star expand option supported are $level per specification.
            //   And this will throw exception,
            //   $expand= * /$count
            //   Parenthesized set of expand options for star expand option which will also cause exception are $filter, $select, $orderby, $skip, $top, $count, $search, and $expand per specification. 
            // And level is not supported with "*/$ref".

            // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future.
            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                // advance past the '('
                this.lexer.NextToken();

                // Check for (), which is not allowed.
                if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen)
                {
                    throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier));
                }

                // Only level option is supported by expand.
                while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen)
                {
                    string text = this.enableCaseInsensitiveBuiltinIdentifier
                        ? this.lexer.CurrentToken.Text.ToLowerInvariant()
                        : this.lexer.CurrentToken.Text;
                    switch (text)
                    {
                        case ExpressionConstants.QueryOptionLevels:
                            {
                                if (!isRefExpand) 
                                { 
                                    levelsOption = ResolveLevelOption();
                                }
                                else
                                {
                                    // no option is allowed when expand with star per specification
                                    throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStarRef(this.lexer.ExpressionText));
                                }

                                break;
                            }

                        default:
                            {
                                throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStar(this.lexer.ExpressionText));
                            }
                    }
                }

                // Move past the ')'
                this.lexer.NextToken();
            }

            // Either there was no '(' at all or we just read past the ')' so we should be at the end
            if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
            }

            // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future.
            var entityType = parentEntityType as IEdmEntityType;
            if (entityType == null)
            {
                throw new ODataException(ODataErrorStrings.UriExpandParser_ParentEntityIsNull(this.lexer.ExpressionText));
            }

            foreach (var navigationProperty in entityType.NavigationProperties())
            {
                var tmpPathToken = default(PathSegmentToken);

                // create path token for each navigation properties.
                if (pathToken.Identifier.Equals(UriQueryConstants.RefSegment))
                {
                    tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken.NextToken);
                    tmpPathToken = new NonSystemToken(UriQueryConstants.RefSegment, null, tmpPathToken);
                }
                else
                {
                    tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken);
                }

                ExpandTermToken currentToken = new ExpandTermToken(tmpPathToken, null, null,
                    null, null, null, levelsOption, null, null, null);
                expandTermTokenList.Add(currentToken);
            }

            return expandTermTokenList;
        }
示例#36
0
        /// <summary>
        /// Building off of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc)
        /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term.
        /// </summary>
        /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param>
        /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param>
        /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns>
        internal List<ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText)
        {
            // Setup a new lexer for parsing the optionsText
            this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/);

            // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted.
            if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star))
            {
                return BuildStarExpandTermToken(pathToken);
            }

            QueryToken filterOption = null;
            IEnumerable<OrderByToken> orderByOptions = null;
            long? topOption = null;
            long? skipOption = null;
            bool? countOption = null;
            long? levelsOption = null;
            QueryToken searchOption = null;
            SelectToken selectOption = null;
            ExpandToken expandOption = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                // advance past the '('
                this.lexer.NextToken();

                // Check for (), which is not allowed.
                if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen)
                {
                    throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier));
                }

                // Look for all the supported query options
                while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen)
                {
                    string text = this.enableCaseInsensitiveBuiltinIdentifier
                        ? this.lexer.CurrentToken.Text.ToLowerInvariant()
                        : this.lexer.CurrentToken.Text;
                    switch (text)
                    {
                        case ExpressionConstants.QueryOptionFilter:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string filterText = this.ReadQueryOption();

                                UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier);
                                filterOption = filterParser.ParseFilter(filterText);
                                break;
                            }

                        case ExpressionConstants.QueryOptionOrderby:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string orderByText = this.ReadQueryOption();

                                UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier);
                                orderByOptions = orderbyParser.ParseOrderBy(orderByText);
                                break;
                            }

                        case ExpressionConstants.QueryOptionTop:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string topText = this.ReadQueryOption();

                                // TryParse requires a non-nullable non-negative long.
                                long top;
                                if (!long.TryParse(topText, out top) || top < 0)
                                {
                                    throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText));
                                }

                                topOption = top;
                                break;
                            }

                        case ExpressionConstants.QueryOptionSkip:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string skipText = this.ReadQueryOption();

                                // TryParse requires a non-nullable non-negative long.
                                long skip;
                                if (!long.TryParse(skipText, out skip) || skip < 0)
                                {
                                    throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText));
                                }

                                skipOption = skip;
                                break;
                            }

                        case ExpressionConstants.QueryOptionCount:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string countText = this.ReadQueryOption();
                                switch (countText)
                                {
                                    case ExpressionConstants.KeywordTrue:
                                        {
                                            countOption = true;
                                            break;
                                        }

                                    case ExpressionConstants.KeywordFalse:
                                        {
                                            countOption = false;
                                            break;
                                        }

                                    default:
                                        {
                                            throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText));
                                        }
                                }

                                break;
                            }

                        case ExpressionConstants.QueryOptionLevels:
                            {
                                levelsOption = ResolveLevelOption();
                                break;
                            }

                        case ExpressionConstants.QueryOptionSearch:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string searchText = this.ReadQueryOption();

                                SearchParser searchParser = new SearchParser(this.MaxSearchDepth);
                                searchOption = searchParser.ParseSearch(searchText);

                                break;
                            }

                        case ExpressionConstants.QueryOptionSelect:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string selectText = this.ReadQueryOption();

                                SelectExpandParser innerSelectParser = new SelectExpandParser(selectText, this.maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier);
                                selectOption = innerSelectParser.ParseSelect();
                                break;
                            }

                        case ExpressionConstants.QueryOptionExpand:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string expandText = this.ReadQueryOption();

                                // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. 
                                IEdmStructuredType targetEntityType = null;
                                if (this.resolver != null && this.parentEntityType != null)
                                {
                                    var parentProperty = this.resolver.ResolveProperty(parentEntityType, pathToken.Identifier) as IEdmNavigationProperty;

                                    // it is a navigation property, need to find the type. Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)", find navigation property Trips of Friends, then get Entity type of Trips.
                                    if (parentProperty != null)
                                    { 
                                        targetEntityType = parentProperty.ToEntityType();
                                    }
                                }

                                SelectExpandParser innerExpandParser = new SelectExpandParser(resolver, expandText, targetEntityType, this.maxRecursionDepth - 1, enableCaseInsensitiveBuiltinIdentifier);
                                expandOption = innerExpandParser.ParseExpand();
                                break;
                            }

                        default:
                            {
                                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
                            }
                    }
                }

                // Move past the ')'
                this.lexer.NextToken();
            }

            // Either there was no '(' at all or we just read past the ')' so we should be at the end
            if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
            }

            // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed.
            List<ExpandTermToken> expandTermTokenList = new List<ExpandTermToken>();
            ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption,
                skipOption, countOption, levelsOption, searchOption, selectOption, expandOption);
            expandTermTokenList.Add(currentToken);

            return expandTermTokenList;
        }
        /// <summary>
        /// Uses the ExpressionLexer to visit the next ExpressionToken, and delegates parsing of segments, type segments, identifiers, 
        /// and the star token to other methods.
        /// </summary>
        /// <param name="previousSegment">Previously parsed PathSegmentToken, or null if this is the first token.</param>
        /// <param name="allowRef">Whether the $ref operation is valid in this token.</param>
        /// <returns>A parsed PathSegmentToken representing the next segment in this path.</returns>
        private PathSegmentToken ParseSegment(PathSegmentToken previousSegment, bool allowRef)
        {
            // TODO $count is defined in specification for expand, it is not supported now. Also note $count is not supported with star as expand option.
            if (this.lexer.CurrentToken.Text.StartsWith("$", StringComparison.Ordinal) && (!allowRef || this.lexer.CurrentToken.Text != UriQueryConstants.RefSegment))
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_SystemTokenInSelectExpand(this.lexer.CurrentToken.Text, this.lexer.ExpressionText));
            }

            // Some check here to throw exception, both prop1/*/prop2 and */$ref/prop will throw exception, both are for $expand cases
            if (!isSelect)
            {
                if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.Star && this.lexer.CurrentToken.GetIdentifier() != UriQueryConstants.RefSegment)
                {
                    // Star can only be followed with $ref
                    throw new ODataException(ODataErrorStrings.ExpressionToken_OnlyRefAllowWithStarInExpand);
                }
                else if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.RefSegment)
                {
                    // $ref should not have more property followed.
                    throw new ODataException(ODataErrorStrings.ExpressionToken_NoPropAllowedAfterRef);
                }
            }

            string propertyName;

            if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
            {
                propertyName = this.lexer.ReadDottedIdentifier(this.isSelect);
            }
            else if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star)
            {
                // "*/$ref" is supported in expand
                if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash && isSelect)
                {
                    throw new ODataException(ODataErrorStrings.ExpressionToken_IdentifierExpected(this.lexer.Position));
                }
                else if (previousSegment != null && !isSelect)
                {
                    // expand option like "customer?$expand=VIPCUstomer/*" is not allowed as specification does not allowed any property before *.
                    throw new ODataException(ODataErrorStrings.ExpressionToken_NoSegmentAllowedBeforeStarInExpand);
                }
                
                propertyName = this.lexer.CurrentToken.Text;
                this.lexer.NextToken();
            }
            else
            {
                propertyName = this.lexer.CurrentToken.GetIdentifier();
                this.lexer.NextToken();
            }

            return new NonSystemToken(propertyName, null, previousSegment);
        }
示例#38
0
        private static void VerifyPathSegmentTokensAreEqual(PathSegmentToken expected, PathSegmentToken actual, AssertionHandler assert)
        {
            try
            {
                if (!VerifyNullnessMatches(expected, actual, assert, "token")) return;

                assert.AreEqual(expected.GetType(), actual.GetType(), "The token kinds are different.");

                assert.AreEqual(expected.Identifier, actual.Identifier, "The token identifiers are different.");

                VerifyPathSegmentTokensAreEqual(expected.NextToken, actual.NextToken, assert);
            }
            catch (Exception)
            {
                assert.Warn("Expected query token: " + expected.ToDebugString());
                assert.Warn("Actual query token: " + actual.ToDebugString());
                throw;
            }
        }
示例#39
0
        /// <summary>
        /// Building off of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc)
        /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term.
        /// </summary>
        /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param>
        /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param>
        /// <returns>An expand term token based on the path token, and all available expand options.</returns>
        internal ExpandTermToken BuildExpandTermToken(PathSegmentToken pathToken, string optionsText)
        {
            // Setup a new lexer for parsing the optionsText
            this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/);

            QueryToken filterOption = null;
            IEnumerable<OrderByToken> orderByOptions = null;
            long? topOption = null;
            long? skipOption = null;
            bool? countOption = null;
            long? levelsOption = null;
            QueryToken searchOption = null;
            SelectToken selectOption = null;
            ExpandToken expandOption = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                // advance past the '('
                this.lexer.NextToken();

                // Check for (), which is not allowed.
                if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen)
                {
                    throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier));
                }

                // Look for all the supported query options
                while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen)
                {
                    string text = this.enableCaseInsensitiveBuiltinIdentifier
                        ? this.lexer.CurrentToken.Text.ToLowerInvariant()
                        : this.lexer.CurrentToken.Text;
                    switch (text)
                    {
                        case ExpressionConstants.QueryOptionFilter:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string filterText = this.ReadQueryOption();

                                UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier);
                                filterOption = filterParser.ParseFilter(filterText);
                                break;
                            }

                        case ExpressionConstants.QueryOptionOrderby:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string orderByText = this.ReadQueryOption();

                                UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier);
                                orderByOptions = orderbyParser.ParseOrderBy(orderByText);
                                break;
                            }

                        case ExpressionConstants.QueryOptionTop:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string topText = this.ReadQueryOption();

                                // TryParse requires a non-nullable non-negative long.
                                long top;
                                if (!long.TryParse(topText, out top) || top < 0)
                                {
                                    throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText));
                                }

                                topOption = top;
                                break;
                            }

                        case ExpressionConstants.QueryOptionSkip:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string skipText = this.ReadQueryOption();

                                // TryParse requires a non-nullable non-negative long.
                                long skip;
                                if (!long.TryParse(skipText, out skip) || skip < 0)
                                {
                                    throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText));
                                }

                                skipOption = skip;
                                break;
                            }

                        case ExpressionConstants.QueryOptionCount:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string countText = this.ReadQueryOption();
                                switch (countText)
                                {
                                    case ExpressionConstants.KeywordTrue:
                                        {
                                            countOption = true;
                                            break;
                                        }

                                    case ExpressionConstants.KeywordFalse:
                                        {
                                            countOption = false;
                                            break;
                                        }

                                    default:
                                        {
                                            throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText));
                                        }
                                }

                                break;
                            }

                        case ExpressionConstants.QueryOptionLevels:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string levelsText = this.ReadQueryOption();
                                long level;

                                if (string.Equals(
                                    ExpressionConstants.KeywordMax,
                                    levelsText,
                                    this.enableCaseInsensitiveBuiltinIdentifier ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
                                {
                                    levelsOption = long.MinValue;
                                }
                                else if (!long.TryParse(levelsText, out level) || level < 0)
                                {
                                    throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidLevelsOption(levelsText));
                                }
                                else
                                {
                                    levelsOption = level;
                                }

                                break;
                            }

                        case ExpressionConstants.QueryOptionSearch:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string searchText = this.ReadQueryOption();

                                SearchParser searchParser = new SearchParser(this.MaxSearchDepth);
                                searchOption = searchParser.ParseSearch(searchText);

                                break;
                            }

                        case ExpressionConstants.QueryOptionSelect:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string selectText = this.ReadQueryOption();

                                SelectExpandParser innerSelectParser = new SelectExpandParser(selectText, this.maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier);
                                selectOption = innerSelectParser.ParseSelect();
                                break;
                            }

                        case ExpressionConstants.QueryOptionExpand:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string expandText = this.ReadQueryOption();

                                SelectExpandParser innerExpandParser = new SelectExpandParser(expandText, this.maxRecursionDepth - 1, enableCaseInsensitiveBuiltinIdentifier);
                                expandOption = innerExpandParser.ParseExpand();
                                break;
                            }

                        default:
                            {
                                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
                            }
                    }
                }

                // Move past the ')'
                this.lexer.NextToken();
            }

            // Either there was no '(' at all or we just read past the ')' so we should be at the end
            if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
            }

            return new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption);
        }
 /// <summary>
 /// Create a new AddNewEndingTokenVisitor, with the new token to add at the end.
 /// </summary>
 /// <param name="newTokenToAdd">a new token to add at the end of the path, can be null</param>
 public AddNewEndingTokenVisitor(PathSegmentToken newTokenToAdd)
 {
     this.newTokenToAdd = newTokenToAdd;
 }
        internal static bool TryBindAsOperation(PathSegmentToken pathToken, IEdmModel model, IEdmStructuredType entityType, out ODataPathSegment segment)
        {
            Debug.Assert(pathToken != null, "pathToken != null");
            Debug.Assert(entityType != null, "bindingType != null");

            List<IEdmOperation> possibleFunctions = new List<IEdmOperation>();

            // Catch all catchable exceptions as FindDeclaredBoundOperations is implemented by anyone.
            // If an exception occurs it will be supressed and the possible functions will be empty and return false.
            try
            {
                int wildCardPos = pathToken.Identifier.IndexOf("*", StringComparison.Ordinal);
                if (wildCardPos > -1)
                {
                    string namespaceName = pathToken.Identifier.Substring(0, wildCardPos - 1);
                    possibleFunctions = model.FindBoundOperations(entityType).Where(o => o.Namespace == namespaceName).ToList();
                }
                else
                {
                    NonSystemToken nonSystemToken = pathToken as NonSystemToken;
                    IList<string> parameterNames = new List<string>();
                    if (nonSystemToken != null && nonSystemToken.NamedValues != null)
                    {
                        parameterNames = nonSystemToken.NamedValues.Select(s => s.Name).ToList();
                    }

                    if (parameterNames.Count > 0)
                    {
                        // Always force to use fully qualified name when select operation
                        possibleFunctions = model.FindBoundOperations(entityType).FilterByName(true, pathToken.Identifier).FilterOperationsByParameterNames(parameterNames, false).ToList();
                    }
                    else
                    {
                        possibleFunctions = model.FindBoundOperations(entityType).FilterByName(true, pathToken.Identifier).ToList();
                    }
                }
            }
            catch (Exception exc)
            {
                if (!ExceptionUtils.IsCatchableExceptionType(exc))
                {
                    throw;
                }
            }

            possibleFunctions = possibleFunctions.EnsureOperationsBoundWithBindingParameter().ToList();

            // Only filter if there is more than one and its needed.
            if (possibleFunctions.Count > 1)
            {
                possibleFunctions = possibleFunctions.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(entityType).ToList();
            }

            if (possibleFunctions.Count <= 0)
            {
                segment = null;
                return false;
            }

            segment = new OperationSegment(possibleFunctions, null /*entitySet*/);
            return true;
        }
        private static bool TryBindAsDeclaredProperty(PathSegmentToken tokenIn, IEdmStructuredType edmType, ODataUriResolver resolver, out ODataPathSegment segment)
        {
            IEdmProperty prop = resolver.ResolveProperty(edmType, tokenIn.Identifier);
            if (prop == null)
            {
                segment = null;
                return false;
            }

            if (prop.PropertyKind == EdmPropertyKind.Structural)
            {
                segment = new PropertySegment((IEdmStructuralProperty)prop);
                return true;
            }

            if (prop.PropertyKind == EdmPropertyKind.Navigation)
            {
                segment = new NavigationPropertySegment((IEdmNavigationProperty)prop, null /*TODO: set*/);
                return true;
            }

            throw new ODataException(ODataErrorStrings.SelectExpandBinder_UnknownPropertyType(prop.Name));
        }
        /// <summary>
        /// Uses the ExpressionLexer to visit the next ExpressionToken, and delegates parsing of segments, type segments, identifiers, 
        /// and the star token to other methods.
        /// </summary>
        /// <param name="previousSegment">Previously parsed PathSegmentToken, or null if this is the first token.</param>
        /// <returns>A parsed PathSegmentToken representing the next segment in this path.</returns>
        private PathSegmentToken ParseSegment(PathSegmentToken previousSegment)
        {
            if (this.lexer.CurrentToken.Text.StartsWith("$", StringComparison.CurrentCulture))
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_SystemTokenInSelectExpand(this.lexer.CurrentToken.Text, this.lexer.ExpressionText));
            }

            string propertyName;

            if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
            {
                propertyName = this.lexer.ReadDottedIdentifier(this.isSelect);
            }
            else if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star)
            {
                if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash)
                {
                    throw new ODataException(ODataErrorStrings.ExpressionToken_IdentifierExpected(this.lexer.Position));
                }

                propertyName = this.lexer.CurrentToken.Text;
                this.lexer.NextToken();
            }
            else
            {
                propertyName = this.lexer.CurrentToken.GetIdentifier();
                this.lexer.NextToken();
            }

            return new NonSystemToken(propertyName, null, previousSegment);
        }