From a8d81956a35e39f8850aee0463ff63d2df6c473f Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Thu, 12 Aug 2021 10:10:00 +0300 Subject: [PATCH 01/13] Expand fully qualified types --- .../UriParser/Binders/SelectExpandBinder.cs | 6 ++++++ .../UriParser/Binders/ExpandBinderTests.cs | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index 2328bc0d12..d16c80a9a8 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -380,6 +380,12 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) if (firstNonTypeToken.NextToken != null) { + PathSegmentToken nextToken = firstNonTypeToken.NextToken; + if (nextToken.IsNamespaceOrContainerQualified()) + { + pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentLevelEntityType, out firstNonTypeToken)); + } + // lastly... make sure that, since we're on a NavProp, that the next token isn't null. if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.RefSegment) { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs index 6e6f3abae3..c66e4faa54 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs @@ -220,5 +220,18 @@ public void LowerLevelEmptySelectedItemsListDoesNotThrow() var ex = Record.Exception(bindWithEmptySelectedItemsList); Assert.Null(ex); } + + [Fact] + public void BindingOnTreeWithWithTypeTokenDoesNotThrow() + { + // Arrange: $expand=MyPeople/Fully.Qualified.Namespace.Employee + NonSystemToken innerSegment = new NonSystemToken("Fully.Qualified.Namespace.Employee", null, null); + NonSystemToken navProp = new NonSystemToken("MyPeople", null, innerSegment); + ExpandToken expandToken = new ExpandToken(new ExpandTermToken[] { new ExpandTermToken(navProp) }); + + // Act & Assert + var binderForDog = new SelectExpandBinder(this.V4configuration, new ODataPathInfo(HardCodedTestModel.GetDogType(), null), null); + var result = binderForDog.Bind(expandToken, null); + } } } From a7c99144380d954823424d79ee2344513fc7e088 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Mon, 16 Aug 2021 12:11:21 +0300 Subject: [PATCH 02/13] expand fully qualified type --- .../UriParser/Binders/SelectExpandBinder.cs | 47 ++++++++++++++++--- .../UriParser/Binders/ExpandBinderTests.cs | 14 ++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index d16c80a9a8..26292c59d4 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -377,15 +377,20 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) bool isRef = false; bool isCount = false; + PathSegmentToken derivedTypeToken = null; + IEdmEntityType derivedType = null; - if (firstNonTypeToken.NextToken != null) + // Handle $expand=Customer/VipCustomer + // The deriveTypeToken is VipCustomer + if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.IsNamespaceOrContainerQualified()) { - PathSegmentToken nextToken = firstNonTypeToken.NextToken; - if (nextToken.IsNamespaceOrContainerQualified()) - { - pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentLevelEntityType, out firstNonTypeToken)); - } + derivedTypeToken = firstNonTypeToken.NextToken; + derivedType = UriEdmHelpers.FindTypeFromModel(this.Model, derivedTypeToken.Identifier, this.configuration.Resolver) as IEdmEntityType; + firstNonTypeToken = derivedTypeToken; + } + if (firstNonTypeToken.NextToken != null) + { // lastly... make sure that, since we're on a NavProp, that the next token isn't null. if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.RefSegment) { @@ -405,6 +410,36 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) List parsedPath = new List(this.parsedSegments); parsedPath.AddRange(pathSoFar); + // Replace the currentNavProp with a Navigation property with the derived type. + if (derivedType != null) + { + EdmMultiplicity edmMultiplicity; + + if (currentNavProp.Type.IsCollection()) + { + edmMultiplicity = EdmMultiplicity.Many; + } + else if (currentNavProp.Type.IsNullable) + { + edmMultiplicity = EdmMultiplicity.ZeroOrOne; + } + else + { + edmMultiplicity = EdmMultiplicity.One; + } + + EdmNavigationPropertyInfo edmNavigationPropertyInfo = new EdmNavigationPropertyInfo() + { + Name = currentNavProp.Name, + Target = derivedType, + TargetMultiplicity = edmMultiplicity, + ContainsTarget = currentNavProp.ContainsTarget, + OnDelete = currentNavProp.OnDelete + }; + + currentNavProp = EdmNavigationProperty.CreateNavigationProperty(currentNavProp.DeclaringType, edmNavigationPropertyInfo); + } + IEdmNavigationSource targetNavigationSource = null; if (this.NavigationSource != null) { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs index c66e4faa54..7c86049d6a 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs @@ -229,9 +229,17 @@ public void BindingOnTreeWithWithTypeTokenDoesNotThrow() NonSystemToken navProp = new NonSystemToken("MyPeople", null, innerSegment); ExpandToken expandToken = new ExpandToken(new ExpandTermToken[] { new ExpandTermToken(navProp) }); - // Act & Assert - var binderForDog = new SelectExpandBinder(this.V4configuration, new ODataPathInfo(HardCodedTestModel.GetDogType(), null), null); - var result = binderForDog.Bind(expandToken, null); + // Act + var binderForDog = new SelectExpandBinder(this.V4configuration, new ODataPathInfo(HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet()), null); + SelectExpandClause selectExpandClause = binderForDog.Bind(expandToken, null); + + // Assert + Assert.NotNull(selectExpandClause); + var selectItem = Assert.Single(selectExpandClause.SelectedItems, x => x is ExpandedNavigationSelectItem); + ExpandedNavigationSelectItem expandedNavigationSelectItem = selectItem as ExpandedNavigationSelectItem; + Assert.Equal(1, expandedNavigationSelectItem.PathToNavigationProperty.Count); + Assert.Equal("MyPeople", expandedNavigationSelectItem.PathToNavigationProperty.Segments.First().Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedNavigationSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); } } } From 2c152a12dd4d432c76a86875b97ead968270eaa1 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Thu, 2 Sep 2021 10:56:00 +0300 Subject: [PATCH 03/13] Fix bug --- .../UriParser/Binders/SelectExpandBinder.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index 26292c59d4..dd2e9fea85 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -369,14 +369,9 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) currentNavProp = ParseComplexTypesBeforeNavigation(currentComplexProp, ref firstNonTypeToken, pathSoFar); } - // ensure that we're always dealing with proper V4 syntax - if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null) - { - throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); - } - bool isRef = false; bool isCount = false; + bool hasDerivedType = false; PathSegmentToken derivedTypeToken = null; IEdmEntityType derivedType = null; @@ -384,11 +379,18 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) // The deriveTypeToken is VipCustomer if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.IsNamespaceOrContainerQualified()) { + hasDerivedType = true; derivedTypeToken = firstNonTypeToken.NextToken; derivedType = UriEdmHelpers.FindTypeFromModel(this.Model, derivedTypeToken.Identifier, this.configuration.Resolver) as IEdmEntityType; firstNonTypeToken = derivedTypeToken; } + // ensure that we're always dealing with proper V4 syntax + if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null && !hasDerivedType) + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); + } + if (firstNonTypeToken.NextToken != null) { // lastly... make sure that, since we're on a NavProp, that the next token isn't null. From c0440252582fb17a886a34f322f59f4041c10ea3 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Thu, 2 Sep 2021 10:56:12 +0300 Subject: [PATCH 04/13] Add more tests --- .../UriParser/SelectExpandFunctionalTests.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs index 408fc8eb99..e2e694f6ed 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs @@ -1667,6 +1667,72 @@ public void ExpandWithNavigationPropCountWithSearchOptionWorks() Assert.NotNull(expandedCountSelectItem.SearchOption); } + // $expand=navProp/fully.qualified.type/$count + [Fact] + public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks() + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$count"} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Null(expandedCountSelectItem.FilterOption); + Assert.Null(expandedCountSelectItem.SearchOption); + } + + // $expand=navProp/fully.qualified.type/$count($filter=prop) + [Fact] + public void ExpandWithNavigationPropCountWithFilterAndFullyQualifiedTypeWorks() + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$count($filter=ID eq 1)"} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.NotNull(expandedCountSelectItem.FilterOption); + Assert.Null(expandedCountSelectItem.SearchOption); + } + + // $expand=navProp/fully.qualified.type/$count($search=prop) + [Fact] + public void ExpandWithNavigationPropCountWithSearchAndFullyQualifiedTypeWorks() + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$count($search=blue)"} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Null(expandedCountSelectItem.FilterOption); + Assert.NotNull(expandedCountSelectItem.SearchOption); + } + [Fact] public void SelectWithNestedSelectWorks() { From 6ebd2ce10dbfd0c65a67134145268b6b4d47a67c Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Thu, 2 Sep 2021 11:09:31 +0300 Subject: [PATCH 05/13] Add more assertions --- .../UriParser/SelectExpandFunctionalTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs index e2e694f6ed..98f3f81253 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs @@ -1687,6 +1687,9 @@ public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks() ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); Assert.Null(expandedCountSelectItem.FilterOption); Assert.Null(expandedCountSelectItem.SearchOption); + Assert.Equal(1, expandedCountSelectItem.PathToNavigationProperty.Count); + Assert.Equal("MyPeople", expandedCountSelectItem.PathToNavigationProperty.Segments.First().Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedCountSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); } // $expand=navProp/fully.qualified.type/$count($filter=prop) @@ -1709,6 +1712,9 @@ public void ExpandWithNavigationPropCountWithFilterAndFullyQualifiedTypeWorks() ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); Assert.NotNull(expandedCountSelectItem.FilterOption); Assert.Null(expandedCountSelectItem.SearchOption); + Assert.Equal(1, expandedCountSelectItem.PathToNavigationProperty.Count); + Assert.Equal("MyPeople", expandedCountSelectItem.PathToNavigationProperty.Segments.First().Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedCountSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); } // $expand=navProp/fully.qualified.type/$count($search=prop) @@ -1731,6 +1737,9 @@ public void ExpandWithNavigationPropCountWithSearchAndFullyQualifiedTypeWorks() ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); Assert.Null(expandedCountSelectItem.FilterOption); Assert.NotNull(expandedCountSelectItem.SearchOption); + Assert.Equal(1, expandedCountSelectItem.PathToNavigationProperty.Count); + Assert.Equal("MyPeople", expandedCountSelectItem.PathToNavigationProperty.Segments.First().Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedCountSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); } [Fact] From c5c7afe6253634046ee0f642eb2b5894e4cd79cf Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Thu, 9 Sep 2021 19:03:35 +0300 Subject: [PATCH 06/13] Create type segment --- .../Microsoft.OData.Core.cs | 2 +- .../Microsoft.OData.Core.txt | 2 +- .../Parameterized.Microsoft.OData.Core.cs | 6 +- .../UriParser/Binders/SelectExpandBinder.cs | 60 ++++++------------- .../UriParser/SemanticAst/ODataExpandPath.cs | 10 +--- .../UriParser/SelectExpandFunctionalTests.cs | 33 +++++++--- .../UriParser/Binders/ExpandBinderTests.cs | 10 +++- .../SemanticAst/ODataExpandPathTests.cs | 15 ++--- .../SemanticAst/ODataPathExtensionsTests.cs | 2 +- 9 files changed, 67 insertions(+), 73 deletions(-) diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index c1176419e1..c1340e9058 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -735,7 +735,7 @@ internal sealed class TextRes { internal const string PathParser_TypeCastOnlyAllowedAfterStructuralCollection = "PathParser_TypeCastOnlyAllowedAfterStructuralCollection"; internal const string PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint = "PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint"; internal const string ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink = "ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink"; - internal const string ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty = "ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty"; + internal const string ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment = "ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment"; internal const string ODataExpandPath_InvalidExpandPathSegment = "ODataExpandPath_InvalidExpandPathSegment"; internal const string ODataSelectPath_CannotOnlyHaveTypeSegment = "ODataSelectPath_CannotOnlyHaveTypeSegment"; internal const string ODataSelectPath_InvalidSelectPathSegmentType = "ODataSelectPath_InvalidSelectPathSegmentType"; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt index 61d00987a5..7a5f2ab14a 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt @@ -846,7 +846,7 @@ PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint=Type cast segment '{0}' on ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink=A resource set may contain a next page link, a delta link or neither, but must not contain both. -ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty=The last segment, and only the last segment, must be a navigation property in $expand. +ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment=The last segment, and only the last segment, must be a navigation property or type segment in $expand. ODataExpandPath_InvalidExpandPathSegment=Found a segment of type '{0} in an expand path, but only NavigationProperty, Property and Type segments are allowed. ODataSelectPath_CannotOnlyHaveTypeSegment=TypeSegment cannot be the only segment in a $select. diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index acc2b304f7..83b8932a3a 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -6407,13 +6407,13 @@ internal static string ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLi } /// - /// A string like "The last segment, and only the last segment, must be a navigation property in $expand." + /// A string like "The last segment, and only the last segment, must be a navigation property or type segment in $expand." /// - internal static string ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty + internal static string ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment { get { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); } } diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index dd2e9fea85..154dea773a 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -372,17 +372,15 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) bool isRef = false; bool isCount = false; bool hasDerivedType = false; - PathSegmentToken derivedTypeToken = null; - IEdmEntityType derivedType = null; + IEdmType derivedType = null; + TypeSegment derivedTypeSegment = null; - // Handle $expand=Customer/VipCustomer - // The deriveTypeToken is VipCustomer + // Handle $expand=Customer/Fully.Qualified.Namespace.VipCustomer + // The deriveTypeToken is Fully.Qualified.Namespace.VipCustomer if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.IsNamespaceOrContainerQualified()) { hasDerivedType = true; - derivedTypeToken = firstNonTypeToken.NextToken; - derivedType = UriEdmHelpers.FindTypeFromModel(this.Model, derivedTypeToken.Identifier, this.configuration.Resolver) as IEdmEntityType; - firstNonTypeToken = derivedTypeToken; + derivedType = UriEdmHelpers.FindTypeFromModel(this.Model, firstNonTypeToken.NextToken.Identifier, this.configuration.Resolver); } // ensure that we're always dealing with proper V4 syntax @@ -391,14 +389,17 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); } - if (firstNonTypeToken.NextToken != null) + if ((firstNonTypeToken.NextToken != null && !hasDerivedType) || + (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null && hasDerivedType)) { + PathSegmentToken nextToken = hasDerivedType ? firstNonTypeToken.NextToken.NextToken : firstNonTypeToken.NextToken; + // lastly... make sure that, since we're on a NavProp, that the next token isn't null. - if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.RefSegment) + if (nextToken.Identifier == UriQueryConstants.RefSegment) { isRef = true; } - else if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.CountSegment) + else if (nextToken.Identifier == UriQueryConstants.CountSegment) { isCount = true; } @@ -411,37 +412,6 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) // Add the segments in select and expand to parsed segments List parsedPath = new List(this.parsedSegments); parsedPath.AddRange(pathSoFar); - - // Replace the currentNavProp with a Navigation property with the derived type. - if (derivedType != null) - { - EdmMultiplicity edmMultiplicity; - - if (currentNavProp.Type.IsCollection()) - { - edmMultiplicity = EdmMultiplicity.Many; - } - else if (currentNavProp.Type.IsNullable) - { - edmMultiplicity = EdmMultiplicity.ZeroOrOne; - } - else - { - edmMultiplicity = EdmMultiplicity.One; - } - - EdmNavigationPropertyInfo edmNavigationPropertyInfo = new EdmNavigationPropertyInfo() - { - Name = currentNavProp.Name, - Target = derivedType, - TargetMultiplicity = edmMultiplicity, - ContainsTarget = currentNavProp.ContainsTarget, - OnDelete = currentNavProp.OnDelete - }; - - currentNavProp = EdmNavigationProperty.CreateNavigationProperty(currentNavProp.DeclaringType, edmNavigationPropertyInfo); - } - IEdmNavigationSource targetNavigationSource = null; if (this.NavigationSource != null) { @@ -452,6 +422,14 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) NavigationPropertySegment navSegment = new NavigationPropertySegment(currentNavProp, targetNavigationSource); pathSoFar.Add(navSegment); parsedPath.Add(navSegment); // Add the navigation property segment to parsed segments for future usage. + + if (hasDerivedType) + { + derivedTypeSegment = new TypeSegment(derivedType, targetNavigationSource); + pathSoFar.Add(derivedTypeSegment); + parsedPath.Add(derivedTypeSegment); + } + ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar); // $apply diff --git a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs index 5f02248ee5..8814e7e511 100644 --- a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs +++ b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs @@ -60,23 +60,19 @@ private void ValidatePath() { if (segment is TypeSegment) { - if (index == this.Count - 1) - { - throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); - } } else if (segment is PropertySegment) { if (index == this.Count - 1) { - throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); } } else if (segment is NavigationPropertySegment) { - if (index < this.Count - 1 || foundNavProp) + if (foundNavProp) { - throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); } foundNavProp = true; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs index 98f3f81253..6e54844195 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs @@ -1685,11 +1685,16 @@ public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks() // Assert Assert.NotNull(selectExpandClause); ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedCountSelectItem.NavigationSource); Assert.Null(expandedCountSelectItem.FilterOption); Assert.Null(expandedCountSelectItem.SearchOption); - Assert.Equal(1, expandedCountSelectItem.PathToNavigationProperty.Count); - Assert.Equal("MyPeople", expandedCountSelectItem.PathToNavigationProperty.Segments.First().Identifier); - Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedCountSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); + Assert.Equal(2, expandedCountSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedCountSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedCountSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); } // $expand=navProp/fully.qualified.type/$count($filter=prop) @@ -1710,11 +1715,16 @@ public void ExpandWithNavigationPropCountWithFilterAndFullyQualifiedTypeWorks() // Assert Assert.NotNull(selectExpandClause); ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedCountSelectItem.NavigationSource); Assert.NotNull(expandedCountSelectItem.FilterOption); Assert.Null(expandedCountSelectItem.SearchOption); - Assert.Equal(1, expandedCountSelectItem.PathToNavigationProperty.Count); - Assert.Equal("MyPeople", expandedCountSelectItem.PathToNavigationProperty.Segments.First().Identifier); - Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedCountSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); + Assert.Equal(2, expandedCountSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedCountSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedCountSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); } // $expand=navProp/fully.qualified.type/$count($search=prop) @@ -1735,11 +1745,16 @@ public void ExpandWithNavigationPropCountWithSearchAndFullyQualifiedTypeWorks() // Assert Assert.NotNull(selectExpandClause); ExpandedCountSelectItem expandedCountSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedCountSelectItem.NavigationSource); Assert.Null(expandedCountSelectItem.FilterOption); Assert.NotNull(expandedCountSelectItem.SearchOption); - Assert.Equal(1, expandedCountSelectItem.PathToNavigationProperty.Count); - Assert.Equal("MyPeople", expandedCountSelectItem.PathToNavigationProperty.Segments.First().Identifier); - Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedCountSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); + Assert.Equal(2, expandedCountSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedCountSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedCountSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); } [Fact] diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs index 7c86049d6a..afe5325784 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/ExpandBinderTests.cs @@ -237,9 +237,13 @@ public void BindingOnTreeWithWithTypeTokenDoesNotThrow() Assert.NotNull(selectExpandClause); var selectItem = Assert.Single(selectExpandClause.SelectedItems, x => x is ExpandedNavigationSelectItem); ExpandedNavigationSelectItem expandedNavigationSelectItem = selectItem as ExpandedNavigationSelectItem; - Assert.Equal(1, expandedNavigationSelectItem.PathToNavigationProperty.Count); - Assert.Equal("MyPeople", expandedNavigationSelectItem.PathToNavigationProperty.Segments.First().Identifier); - Assert.Equal("Collection(Fully.Qualified.Namespace.Employee)", expandedNavigationSelectItem.PathToNavigationProperty.Segments.First().EdmType.FullTypeName()); + Assert.Equal(2, expandedNavigationSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); } } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs index 0bbe9efa65..ca04e05d35 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs @@ -31,18 +31,19 @@ public void ExpandPathShouldNotAllowValueSegment() createWithValueSegment.Throws(ODataErrorStrings.ODataExpandPath_InvalidExpandPathSegment("ValueSegment")); } - [Fact] - public void ExpandPathShouldNotAllowTypeSegmentToBeLast() - { - Action createWithTypeSegmentLast = () => new ODataExpandPath(this.typeSegment); - createWithTypeSegmentLast.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); - } + // This test is not relevant since we are now supporting type segments as the last segment in $expand + //[Fact] + //public void ExpandPathShouldNotAllowTypeSegmentToBeLast() + //{ + // Action createWithTypeSegmentLast = () => new ODataExpandPath(this.typeSegment); + // createWithTypeSegmentLast.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + //} [Fact] public void ExpandPathShouldNotAllowMultipleNavigations() { Action createWithTypeSegmentLast = () => new ODataExpandPath(this.navigationSegment, this.navigationSegment); - createWithTypeSegmentLast.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + createWithTypeSegmentLast.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); } [Fact] diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs index 0f7ea49244..1082b7c240 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs @@ -79,7 +79,7 @@ public void PathExtensionsToExpandPathWithNonNavigationPropertyThrows() ); Action expandPathAction = () => path.ToExpandPath(); - expandPathAction.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + expandPathAction.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); } [Fact] From 79194adeb5e792e2c57bc1547491b6010f0559ff Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Thu, 9 Sep 2021 20:42:42 +0300 Subject: [PATCH 07/13] Add validation --- .../UriParser/Binders/SelectExpandBinder.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index 154dea773a..422ffd2f17 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -373,7 +373,6 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) bool isCount = false; bool hasDerivedType = false; IEdmType derivedType = null; - TypeSegment derivedTypeSegment = null; // Handle $expand=Customer/Fully.Qualified.Namespace.VipCustomer // The deriveTypeToken is Fully.Qualified.Namespace.VipCustomer @@ -381,6 +380,10 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) { hasDerivedType = true; derivedType = UriEdmHelpers.FindTypeFromModel(this.Model, firstNonTypeToken.NextToken.Identifier, this.configuration.Resolver); + + // In this example: $expand=Customer/Fully.Qualified.Namespace.VipCustomer + // We validate that the derived type Fully.Qualified.Namespace.VipCustomer is related to Navigation property Customer. + UriEdmHelpers.CheckRelatedTo(currentNavProp.ToEntityType(), derivedType); } // ensure that we're always dealing with proper V4 syntax @@ -425,7 +428,7 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) if (hasDerivedType) { - derivedTypeSegment = new TypeSegment(derivedType, targetNavigationSource); + TypeSegment derivedTypeSegment = new TypeSegment(derivedType, targetNavigationSource); pathSoFar.Add(derivedTypeSegment); parsedPath.Add(derivedTypeSegment); } From eec3212403696dae05feff7f95e9d47b579382d3 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Mon, 13 Sep 2021 09:32:06 +0300 Subject: [PATCH 08/13] Fix based on review comments --- .../UriParser/Binders/SelectExpandBinder.cs | 14 +++++++------- .../UriParser/SemanticAst/ODataExpandPath.cs | 7 ++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index 422ffd2f17..3a969da94f 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -371,14 +371,14 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) bool isRef = false; bool isCount = false; - bool hasDerivedType = false; + bool hasDerivedTypeSegment = false; IEdmType derivedType = null; // Handle $expand=Customer/Fully.Qualified.Namespace.VipCustomer // The deriveTypeToken is Fully.Qualified.Namespace.VipCustomer if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.IsNamespaceOrContainerQualified()) { - hasDerivedType = true; + hasDerivedTypeSegment = true; derivedType = UriEdmHelpers.FindTypeFromModel(this.Model, firstNonTypeToken.NextToken.Identifier, this.configuration.Resolver); // In this example: $expand=Customer/Fully.Qualified.Namespace.VipCustomer @@ -387,15 +387,15 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) } // ensure that we're always dealing with proper V4 syntax - if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null && !hasDerivedType) + if (firstNonTypeToken?.NextToken?.NextToken != null && !hasDerivedTypeSegment) { throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); } - if ((firstNonTypeToken.NextToken != null && !hasDerivedType) || - (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null && hasDerivedType)) + if ((firstNonTypeToken.NextToken != null && !hasDerivedTypeSegment) || + (firstNonTypeToken?.NextToken?.NextToken != null && hasDerivedTypeSegment)) { - PathSegmentToken nextToken = hasDerivedType ? firstNonTypeToken.NextToken.NextToken : firstNonTypeToken.NextToken; + PathSegmentToken nextToken = hasDerivedTypeSegment ? firstNonTypeToken.NextToken.NextToken : firstNonTypeToken.NextToken; // lastly... make sure that, since we're on a NavProp, that the next token isn't null. if (nextToken.Identifier == UriQueryConstants.RefSegment) @@ -426,7 +426,7 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) pathSoFar.Add(navSegment); parsedPath.Add(navSegment); // Add the navigation property segment to parsed segments for future usage. - if (hasDerivedType) + if (hasDerivedTypeSegment) { TypeSegment derivedTypeSegment = new TypeSegment(derivedType, targetNavigationSource); pathSoFar.Add(derivedTypeSegment); diff --git a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs index 8814e7e511..50b7589001 100644 --- a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs +++ b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs @@ -58,10 +58,7 @@ private void ValidatePath() bool foundNavProp = false; foreach (ODataPathSegment segment in this) { - if (segment is TypeSegment) - { - } - else if (segment is PropertySegment) + if (segment is PropertySegment) { if (index == this.Count - 1) { @@ -77,7 +74,7 @@ private void ValidatePath() foundNavProp = true; } - else + else if (!(segment is TypeSegment)) { throw new ODataException(ODataErrorStrings.ODataExpandPath_InvalidExpandPathSegment(segment.GetType().Name)); } From 7b4a2e4f1ba443669eac87ce9eb0fe03234467d5 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Wed, 15 Sep 2021 10:10:48 +0300 Subject: [PATCH 09/13] Throw exception when derivedType is unknown/null --- .../UriParser/Binders/SelectExpandBinder.cs | 6 +++ .../UriParser/SelectExpandFunctionalTests.cs | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index 3a969da94f..ccdd9cf600 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -381,6 +381,12 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) hasDerivedTypeSegment = true; derivedType = UriEdmHelpers.FindTypeFromModel(this.Model, firstNonTypeToken.NextToken.Identifier, this.configuration.Resolver); + if (derivedType == null) + { + // Exception example: The type Fully.Qualified.Namespace.UndefinedType is not defined in the model. + throw new ODataException(Strings.ExpandItemBinder_CannotFindType(firstNonTypeToken.NextToken.Identifier)); + } + // In this example: $expand=Customer/Fully.Qualified.Namespace.VipCustomer // We validate that the derived type Fully.Qualified.Namespace.VipCustomer is related to Navigation property Customer. UriEdmHelpers.CheckRelatedTo(currentNavProp.ToEntityType(), derivedType); diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs index 6e54844195..1915031f4c 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs @@ -1667,6 +1667,34 @@ public void ExpandWithNavigationPropCountWithSearchOptionWorks() Assert.NotNull(expandedCountSelectItem.SearchOption); } + // $expand=navProp/fully.qualified.type/$ref + [Fact] + public void ExpandWithNavigationPropRefWithFullyQualifiedTypeWorks() + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$ref"} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedReferenceSelectItem expandedRefSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedRefSelectItem.NavigationSource); + Assert.Equal(2, expandedRefSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedRefSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedRefSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); + } + // $expand=navProp/fully.qualified.type/$count [Fact] public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks() @@ -1757,6 +1785,31 @@ public void ExpandWithNavigationPropCountWithSearchAndFullyQualifiedTypeWorks() Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); } + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.UndefinedType")] + [InlineData("MyPeople/Fully.Qualified.Namespace.UndefinedType/$ref")] + [InlineData("MyPeople/Fully.Qualified.Namespace.UndefinedType/$count")] + [InlineData("MyPeople/Fully.Qualified.Namespace.UndefinedType/$count($search=blue)")] + [InlineData("MyPeople/Fully.Qualified.Namespace.UndefinedType/$count($filter=ID eq 1)")] + public void ExpandWithNavigationPropWithUndefinedTypeThrows(string query) + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", query} + }); + + // Act + Action action = () => odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + + // Exception: The type Fully.Qualified.Namespace.UndefinedType is not defined in the model. + action.Throws(ODataErrorStrings.ExpandItemBinder_CannotFindType("Fully.Qualified.Namespace.UndefinedType")); + } + [Fact] public void SelectWithNestedSelectWorks() { From fbfed148ca19d3cdb7cf7433191d1401dbb87023 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Wed, 15 Sep 2021 12:03:59 +0300 Subject: [PATCH 10/13] Handle null childType --- src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs b/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs index 3474f18062..d36b5836e4 100644 --- a/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs +++ b/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs @@ -54,7 +54,8 @@ public static void CheckRelatedTo(IEdmType parentType, IEdmType childType) { // If the parentType is an open property, parentType will be null and can't have an ODataFullName. string parentTypeName = (parentType != null) ? parentType.FullTypeName() : ""; - throw new ODataException(Strings.MetadataBinder_HierarchyNotFollowed(childType.FullTypeName(), parentTypeName)); + string childTypeName = (childType != null) ? childType.FullTypeName() : ""; + throw new ODataException(Strings.MetadataBinder_HierarchyNotFollowed(childTypeName, parentTypeName)); } } From 5bc16553ec4e8e226662c752fa5038cab3d0ad0b Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Fri, 17 Sep 2021 09:46:30 +0300 Subject: [PATCH 11/13] Add schema alias tests --- .../UriParser/SelectExpandFunctionalTests.cs | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs index 1915031f4c..fb9bcd5e3c 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs @@ -1668,15 +1668,17 @@ public void ExpandWithNavigationPropCountWithSearchOptionWorks() } // $expand=navProp/fully.qualified.type/$ref - [Fact] - public void ExpandWithNavigationPropRefWithFullyQualifiedTypeWorks() + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee/$ref")] + [InlineData("MyPeople/MainAlias.Employee/$ref")] + public void ExpandWithNavigationPropRefWithFullyQualifiedTypeWorks(string query) { // Arrange var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), new Dictionary() { - {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$ref"} + {"$expand", query} }); // Act @@ -1696,15 +1698,17 @@ public void ExpandWithNavigationPropRefWithFullyQualifiedTypeWorks() } // $expand=navProp/fully.qualified.type/$count - [Fact] - public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks() + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee/$count")] + [InlineData("MyPeople/MainAlias.Employee/$count")] // With schema alias + public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks(string query) { // Arrange var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), new Dictionary() { - {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$count"} + {"$expand", query} }); // Act @@ -1726,15 +1730,17 @@ public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks() } // $expand=navProp/fully.qualified.type/$count($filter=prop) - [Fact] - public void ExpandWithNavigationPropCountWithFilterAndFullyQualifiedTypeWorks() + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee/$count($filter=ID eq 1)")] + [InlineData("MyPeople/MainAlias.Employee/$count($filter=ID eq 1)")] // With schema alias + public void ExpandWithNavigationPropCountWithFilterAndFullyQualifiedTypeWorks(string query) { // Arrange var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), new Dictionary() { - {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$count($filter=ID eq 1)"} + {"$expand", query} }); // Act @@ -1756,15 +1762,17 @@ public void ExpandWithNavigationPropCountWithFilterAndFullyQualifiedTypeWorks() } // $expand=navProp/fully.qualified.type/$count($search=prop) - [Fact] - public void ExpandWithNavigationPropCountWithSearchAndFullyQualifiedTypeWorks() + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee/$count($search=blue)")] + [InlineData("MyPeople/MainAlias.Employee/$count($search=blue)")] // With schema alias + public void ExpandWithNavigationPropCountWithSearchAndFullyQualifiedTypeWorks(string query) { // Arrange var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), new Dictionary() { - {"$expand", "MyPeople/Fully.Qualified.Namespace.Employee/$count($search=blue)"} + {"$expand", query} }); // Act From b5165f9241f3d5433422ad467f2345a570554e3e Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Fri, 17 Sep 2021 17:01:32 +0300 Subject: [PATCH 12/13] Fix tests --- .../Microsoft.OData.Core.cs | 3 ++- .../Microsoft.OData.Core.txt | 3 ++- .../Parameterized.Microsoft.OData.Core.cs | 17 ++++++++++++++--- .../UriParser/Binders/SelectExpandBinder.cs | 2 +- .../UriParser/SemanticAst/ODataExpandPath.cs | 4 ++-- .../SemanticAst/ODataExpandPathTests.cs | 2 +- .../SemanticAst/ODataPathExtensionsTests.cs | 2 +- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index c1340e9058..9f5ddc2f9d 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -735,7 +735,8 @@ internal sealed class TextRes { internal const string PathParser_TypeCastOnlyAllowedAfterStructuralCollection = "PathParser_TypeCastOnlyAllowedAfterStructuralCollection"; internal const string PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint = "PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint"; internal const string ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink = "ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink"; - internal const string ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment = "ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment"; + internal const string ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty = "ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty"; + internal const string ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment = "ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment "; internal const string ODataExpandPath_InvalidExpandPathSegment = "ODataExpandPath_InvalidExpandPathSegment"; internal const string ODataSelectPath_CannotOnlyHaveTypeSegment = "ODataSelectPath_CannotOnlyHaveTypeSegment"; internal const string ODataSelectPath_InvalidSelectPathSegmentType = "ODataSelectPath_InvalidSelectPathSegmentType"; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt index 7a5f2ab14a..1d3ff882da 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt @@ -846,7 +846,8 @@ PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint=Type cast segment '{0}' on ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink=A resource set may contain a next page link, a delta link or neither, but must not contain both. -ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment=The last segment, and only the last segment, must be a navigation property or type segment in $expand. +ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty =The last segment, and only the last segment, can be a navigation property in $expand. +ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment =The last segment must be a navigation property or type segment in $expand. ODataExpandPath_InvalidExpandPathSegment=Found a segment of type '{0} in an expand path, but only NavigationProperty, Property and Type segments are allowed. ODataSelectPath_CannotOnlyHaveTypeSegment=TypeSegment cannot be the only segment in a $select. diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index 83b8932a3a..61da8a91ae 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -6407,13 +6407,24 @@ internal static string ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLi } /// - /// A string like "The last segment, and only the last segment, must be a navigation property or type segment in $expand." + /// A string like "The last segment, and only the last segment, can be a navigation property in $expand." /// - internal static string ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment + internal static string ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty { get { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty); + } + } + + /// + /// A string like "The last segment must be a navigation property or type segment in $expand." + /// + internal static string ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment + { + get + { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment); } } diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index ccdd9cf600..a1b9ef0491 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -384,7 +384,7 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) if (derivedType == null) { // Exception example: The type Fully.Qualified.Namespace.UndefinedType is not defined in the model. - throw new ODataException(Strings.ExpandItemBinder_CannotFindType(firstNonTypeToken.NextToken.Identifier)); + throw new ODataException(ODataErrorStrings.ExpandItemBinder_CannotFindType(firstNonTypeToken.NextToken.Identifier)); } // In this example: $expand=Customer/Fully.Qualified.Namespace.VipCustomer diff --git a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs index 50b7589001..9cedda5b09 100644 --- a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs +++ b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs @@ -62,14 +62,14 @@ private void ValidatePath() { if (index == this.Count - 1) { - throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); + throw new ODataException(ODataErrorStrings.ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment); } } else if (segment is NavigationPropertySegment) { if (foundNavProp) { - throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); + throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty); } foundNavProp = true; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs index ca04e05d35..717c1c018a 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataExpandPathTests.cs @@ -43,7 +43,7 @@ public void ExpandPathShouldNotAllowValueSegment() public void ExpandPathShouldNotAllowMultipleNavigations() { Action createWithTypeSegmentLast = () => new ODataExpandPath(this.navigationSegment, this.navigationSegment); - createWithTypeSegmentLast.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); + createWithTypeSegmentLast.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty); } [Fact] diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs index 1082b7c240..23d9f8aa68 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs @@ -79,7 +79,7 @@ public void PathExtensionsToExpandPathWithNonNavigationPropertyThrows() ); Action expandPathAction = () => path.ToExpandPath(); - expandPathAction.Throws(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationPropertyOrTypeSegment); + expandPathAction.Throws(ODataErrorStrings.ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment); } [Fact] From d1cd491b03063a22ef1d4032a41f317a617f4d58 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Date: Fri, 17 Sep 2021 17:22:08 +0300 Subject: [PATCH 13/13] Fix typo --- src/Microsoft.OData.Core/Microsoft.OData.Core.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index 9f5ddc2f9d..65a0e6500b 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -736,7 +736,7 @@ internal sealed class TextRes { internal const string PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint = "PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint"; internal const string ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink = "ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink"; internal const string ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty = "ODataExpandPath_OnlyLastSegmentCanBeNavigationProperty"; - internal const string ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment = "ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment "; + internal const string ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment = "ODataExpandPath_LastSegmentMustBeNavigationPropertyOrTypeSegment"; internal const string ODataExpandPath_InvalidExpandPathSegment = "ODataExpandPath_InvalidExpandPathSegment"; internal const string ODataSelectPath_CannotOnlyHaveTypeSegment = "ODataSelectPath_CannotOnlyHaveTypeSegment"; internal const string ODataSelectPath_InvalidSelectPathSegmentType = "ODataSelectPath_InvalidSelectPathSegmentType";