diff --git a/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs b/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs index 3f3a9f7067..738e855733 100644 --- a/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs +++ b/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs @@ -83,6 +83,11 @@ internal class ExpressionLexer /// Whether the lexer is being used to parse function parameters. If true, will allow/recognize parameter aliases and typed nulls. private readonly bool parsingFunctionParameters; + /// + /// Whether or not to interpret the semantics of a token that appears to be an identifier (i.e. consider if it is a keyword instead of an identifier) + /// + private readonly bool applySemanticsToIdentifiers; + /// Lexer ignores whitespace private bool ignoreWhitespace; @@ -108,6 +113,19 @@ internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemic /// If true, the lexer will tokenize based on semicolons as well. /// Whether the lexer is being used to parse function parameters. If true, will allow/recognize parameter aliases and typed nulls. internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemicolonDelimiter, bool parsingFunctionParameters) + : this(expression, moveToFirstToken, useSemicolonDelimiter, parsingFunctionParameters, true) + { + } + + /// Initializes a new . + /// Expression to parse. + /// If true, this constructor will call NextToken() to move to the first token. + /// If true, the lexer will tokenize based on semicolons as well. + /// Whether the lexer is being used to parse function parameters. If true, will allow/recognize parameter aliases and typed nulls. + /// + /// Whether or not to interpret the semantics of a token that appears to be an identifier (i.e. consider if it is a keyword instead of an identifier) + /// + internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemicolonDelimiter, bool parsingFunctionParameters, bool applySemanticsToIdentifiers) { Debug.Assert(expression != null, "expression != null"); @@ -116,6 +134,7 @@ internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemic this.TextLen = this.Text.Length; this.useSemicolonDelimiter = useSemicolonDelimiter; this.parsingFunctionParameters = parsingFunctionParameters; + this.applySemanticsToIdentifiers = applySemanticsToIdentifiers; this.parsingDoubleQuotedString = false; this.SetTextPos(0); @@ -810,7 +829,7 @@ private void HandleTypePrefixedLiterals() this.HandleQuotedValues(); } - else + else if (this.applySemanticsToIdentifiers) { // Handle keywords. // Get built in type literal prefix. diff --git a/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs b/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs index 976b734484..5f755a2437 100644 --- a/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs +++ b/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs @@ -81,7 +81,7 @@ public SelectExpandParser( // Sets up our lexer. We don't turn useSemicolonDelimiter on since the parsing code for expand options, // which is the only thing that needs it, is in a different class that uses it's own lexer. - this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false /*moveToFirstToken*/, false /*useSemicolonDelimiter*/) : null; + this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false, false, false, false) : null; this.enableCaseInsensitiveBuiltinIdentifier = enableCaseInsensitiveBuiltinIdentifier; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs index e0c9f122cf..0f5772c8cc 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs @@ -26,6 +26,410 @@ public class ODataUriParserTests private readonly Uri ServiceRoot = new Uri("http://host"); private readonly Uri FullUri = new Uri("http://host/People"); + /// + /// Selects a property named "true" + /// + [Fact] + public void SelectTrue() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + foo.AddStructuralProperty("true", EdmCoreModel.Instance.GetString(true)); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$select=true", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var pathSelectItem = selectedItems[0] as PathSelectItem; + var segments = pathSelectItem.SelectedPath.Segments.ToArray(); + Assert.Single(segments); + Assert.Equal("true", segments[0].Identifier); + Assert.Equal("Edm.String", segments[0].EdmType.FullTypeName()); + } + + /// + /// Selects a property named "false" + /// + [Fact] + public void SelectFalse() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + foo.AddStructuralProperty("false", EdmCoreModel.Instance.GetString(true)); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$select=false", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var pathSelectItem = selectedItems[0] as PathSelectItem; + var segments = pathSelectItem.SelectedPath.Segments.ToArray(); + Assert.Single(segments); + Assert.Equal("false", segments[0].Identifier); + Assert.Equal("Edm.String", segments[0].EdmType.FullTypeName()); + } + + /// + /// Selects a property named "INF" + /// + [Fact] + public void SelectInfinity() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + foo.AddStructuralProperty("INF", EdmCoreModel.Instance.GetString(true)); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$select=INF", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var pathSelectItem = selectedItems[0] as PathSelectItem; + var segments = pathSelectItem.SelectedPath.Segments.ToArray(); + Assert.Single(segments); + Assert.Equal("INF", segments[0].Identifier); + Assert.Equal("Edm.String", segments[0].EdmType.FullTypeName()); + } + + /// + /// Selects a property named "NaN" + /// + [Fact] + public void SelectNotANumber() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + foo.AddStructuralProperty("NaN", EdmCoreModel.Instance.GetString(true)); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$select=NaN", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var pathSelectItem = selectedItems[0] as PathSelectItem; + var segments = pathSelectItem.SelectedPath.Segments.ToArray(); + Assert.Single(segments); + Assert.Equal("NaN", segments[0].Identifier); + Assert.Equal("Edm.String", segments[0].EdmType.FullTypeName()); + } + + /// + /// Selects a property named "null" + /// + [Fact] + public void SelectNull() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + foo.AddStructuralProperty("null", EdmCoreModel.Instance.GetString(true)); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$select=null", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var pathSelectItem = selectedItems[0] as PathSelectItem; + var segments = pathSelectItem.SelectedPath.Segments.ToArray(); + Assert.Single(segments); + Assert.Equal("null", segments[0].Identifier); + Assert.Equal("Edm.String", segments[0].EdmType.FullTypeName()); + } + + /// + /// Selects a property named "null" when such a property doesn't exist on the entity + /// + [Fact] + public void SelectNullNotFound() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + foo.AddStructuralProperty("true", EdmCoreModel.Instance.GetString(true)); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$select=null", UriKind.Relative)); + var odataException = Assert.Throws(() => parser.ParseUri()); + + Assert.Equal("Could not find a property named 'null' on type 'foobar.foo'.", odataException.Message); + } + + /// + /// Expands a property named "true" + /// + [Fact] + public void ExpandTrue() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + var bar = new EdmEntityType("foobar", "bar"); + bar.AddKeys(bar.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + model.AddElement(bar); + foo.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() + { + Name = "true", + ContainsTarget = true, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne, + Target = bar , + }); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$expand=true", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var expandedNavigationSelectItem = selectedItems[0] as ExpandedNavigationSelectItem; + Assert.Equal("true", expandedNavigationSelectItem.NavigationSource.Name); + Assert.Equal("foobar.bar", expandedNavigationSelectItem.NavigationSource.EntityType().FullTypeName()); + Assert.Empty(expandedNavigationSelectItem.SelectAndExpand.SelectedItems); + } + + /// + /// Expands a property named "false" + /// + [Fact] + public void ExpandFalse() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + var bar = new EdmEntityType("foobar", "bar"); + bar.AddKeys(bar.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + model.AddElement(bar); + foo.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() + { + Name = "false", + ContainsTarget = true, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne, + Target = bar, + }); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$expand=false", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var expandedNavigationSelectItem = selectedItems[0] as ExpandedNavigationSelectItem; + Assert.Equal("false", expandedNavigationSelectItem.NavigationSource.Name); + Assert.Equal("foobar.bar", expandedNavigationSelectItem.NavigationSource.EntityType().FullTypeName()); + Assert.Empty(expandedNavigationSelectItem.SelectAndExpand.SelectedItems); + } + + /// + /// Expands a property named "INF" + /// + [Fact] + public void ExpandInfinity() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + var bar = new EdmEntityType("foobar", "bar"); + bar.AddKeys(bar.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + model.AddElement(bar); + foo.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() + { + Name = "INF", + ContainsTarget = true, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne, + Target = bar, + }); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$expand=INF", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var expandedNavigationSelectItem = selectedItems[0] as ExpandedNavigationSelectItem; + Assert.Equal("INF", expandedNavigationSelectItem.NavigationSource.Name); + Assert.Equal("foobar.bar", expandedNavigationSelectItem.NavigationSource.EntityType().FullTypeName()); + Assert.Empty(expandedNavigationSelectItem.SelectAndExpand.SelectedItems); + } + + /// + /// Expands a property named "NaN" + /// + [Fact] + public void ExpandNotANumber() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + var bar = new EdmEntityType("foobar", "bar"); + bar.AddKeys(bar.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + model.AddElement(bar); + foo.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() + { + Name = "NaN", + ContainsTarget = true, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne, + Target = bar, + }); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$expand=NaN", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var expandedNavigationSelectItem = selectedItems[0] as ExpandedNavigationSelectItem; + Assert.Equal("NaN", expandedNavigationSelectItem.NavigationSource.Name); + Assert.Equal("foobar.bar", expandedNavigationSelectItem.NavigationSource.EntityType().FullTypeName()); + Assert.Empty(expandedNavigationSelectItem.SelectAndExpand.SelectedItems); + } + + /// + /// Expands a property named "null" + /// + [Fact] + public void ExpandNull() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + var bar = new EdmEntityType("foobar", "bar"); + bar.AddKeys(bar.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + model.AddElement(bar); + foo.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() + { + Name = "null", + ContainsTarget = true, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne, + Target = bar, + }); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$expand=null", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var selectedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var expandedNavigationSelectItem = selectedItems[0] as ExpandedNavigationSelectItem; + Assert.Equal("null", expandedNavigationSelectItem.NavigationSource.Name); + Assert.Equal("foobar.bar", expandedNavigationSelectItem.NavigationSource.EntityType().FullTypeName()); + Assert.Empty(expandedNavigationSelectItem.SelectAndExpand.SelectedItems); + } + + /// + /// Expands a property named "null" when such a property doesn't exist on the entity + /// + [Fact] + public void ExpandNullNotFound() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + var bar = new EdmEntityType("foobar", "bar"); + bar.AddKeys(bar.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + model.AddElement(bar); + foo.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() + { + Name = "true", + ContainsTarget = true, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne, + Target = bar, + }); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$expand=null", UriKind.Relative)); + var odataException = Assert.Throws(() => parser.ParseUri()); + + Assert.Equal("Could not find a property named 'null' on type 'foobar.foo'.", odataException.Message); + } + + /// + /// Expands a property named "true" and selects its property named "false" + /// + [Fact] + public void ExpandTrueSelectFalse() + { + var model = new EdmModel(); + var foo = new EdmEntityType("foobar", "foo"); + foo.AddKeys(foo.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + var bar = new EdmEntityType("foobar", "bar"); + bar.AddKeys(bar.AddStructuralProperty("id", EdmCoreModel.Instance.GetInt32(false))); + bar.AddStructuralProperty("false", EdmCoreModel.Instance.GetString(true)); + model.AddElement(bar); + foo.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() + { + Name = "true", + ContainsTarget = true, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne, + Target = bar, + }); + model.AddElement(foo); + var container = new EdmEntityContainer("foobar", "FooService"); + container.AddEntitySet("foos", foo); + model.AddElement(container); + + var parser = new ODataUriParser(model, new Uri("/foos?$expand=true($select=false)", UriKind.Relative)); + var odataUri = parser.ParseUri(); + + var expandedItems = odataUri.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(expandedItems); + var expandedNavigationSelectItem = expandedItems[0] as ExpandedNavigationSelectItem; + Assert.Equal("true", expandedNavigationSelectItem.NavigationSource.Name); + Assert.Equal("foobar.bar", expandedNavigationSelectItem.NavigationSource.EntityType().FullTypeName()); + + var selectedItems = expandedNavigationSelectItem.SelectAndExpand.SelectedItems.ToArray(); + Assert.Single(selectedItems); + var pathSelectItem = selectedItems[0] as PathSelectItem; + var segments = pathSelectItem.SelectedPath.Segments.ToArray(); + Assert.Single(segments); + Assert.Equal("false", segments[0].Identifier); + Assert.Equal("Edm.String", segments[0].EdmType.FullTypeName()); + } + [Theory] [InlineData(true)] [InlineData(false)]