diff --git a/src/Microsoft.OData.Core/ODataContextUrlInfo.cs b/src/Microsoft.OData.Core/ODataContextUrlInfo.cs index 6ec3b7170c..86cc7b85cc 100644 --- a/src/Microsoft.OData.Core/ODataContextUrlInfo.cs +++ b/src/Microsoft.OData.Core/ODataContextUrlInfo.cs @@ -297,7 +297,7 @@ private static string ComputeNavigationPath(EdmNavigationSourceKind kind, ODataU string navigationPath = null; if (kind == EdmNavigationSourceKind.ContainedEntitySet && odataUri != null && odataUri.Path != null) { - ODataPath odataPath = odataUri.Path.TrimEndingTypeSegment().TrimEndingKeySegment(); + ODataPath odataPath = odataUri.Path.TrimEndingTypeAndKeySegments(); if (!(odataPath.LastSegment is NavigationPropertySegment) && !(odataPath.LastSegment is OperationSegment)) { throw new ODataException(Strings.ODataContextUriBuilder_ODataPathInvalidForContainedElement(odataPath.ToContextUrlPathString())); @@ -322,15 +322,13 @@ private static string ComputeNavigationPath(EdmNavigationSourceKind kind, in ODa string navigationPath = null; if (kind == EdmNavigationSourceKind.ContainedEntitySet && odataUri.Path != null) { - ODataPath odataPath = odataUri.Path.TrimEndingTypeSegment().TrimEndingKeySegment(); + ODataPath odataPath = odataUri.Path.TrimEndingTypeAndKeySegments(); if (!(odataPath.LastSegment is NavigationPropertySegment) && !(odataPath.LastSegment is OperationSegment)) { throw new ODataException(Strings.ODataContextUriBuilder_ODataPathInvalidForContainedElement(odataPath.ToContextUrlPathString())); } - navigationPath = odataPath.ToContextUrlPathString(); } - return navigationPath ?? navigationSource; } diff --git a/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt index e69de29bb2..103f295f00 100644 --- a/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.OData.UriParser.ODataPathExtensions.TrimEndingTypeAndKeySegments(this Microsoft.OData.UriParser.ODataPath path) -> Microsoft.OData.UriParser.ODataPath \ No newline at end of file diff --git a/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt index e69de29bb2..103f295f00 100644 --- a/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.OData.UriParser.ODataPathExtensions.TrimEndingTypeAndKeySegments(this Microsoft.OData.UriParser.ODataPath path) -> Microsoft.OData.UriParser.ODataPath \ No newline at end of file diff --git a/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt index e69de29bb2..103f295f00 100644 --- a/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.OData.UriParser.ODataPathExtensions.TrimEndingTypeAndKeySegments(this Microsoft.OData.UriParser.ODataPath path) -> Microsoft.OData.UriParser.ODataPath \ No newline at end of file diff --git a/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb2..103f295f00 100644 --- a/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.OData.UriParser.ODataPathExtensions.TrimEndingTypeAndKeySegments(this Microsoft.OData.UriParser.ODataPath path) -> Microsoft.OData.UriParser.ODataPath \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs index 87b4fa1bbc..f0d02cd1c5 100644 --- a/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs +++ b/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs @@ -137,6 +137,22 @@ public static ODataPath TrimEndingTypeSegment(this ODataPath path) return handler.FirstPart; } + /// <summary> + /// Creates a <see cref="ODataPath"/> that is <paramref name="path"/> with the type segments and key segments removed from the end + /// </summary> + /// <param name="path">The <see cref="ODataPath"/> to trim the ending of</param> + /// <returns>A <see cref="ODataPath"/> without type-cast and key segments at the end</returns> + /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="path"/> is <see langword="null"/></exception> + public static ODataPath TrimEndingTypeAndKeySegments(this ODataPath path) + { + if (path == null) + { + throw Error.ArgumentNull(nameof(path)); + } + + return new ODataPath(path.Segments.Take(path.Segments.FindLastIndex(segment => !(segment is KeySegment || segment is TypeSegment)) + 1)); + } + /// <summary> /// Creates a new ODataPath with the specified segment added. /// </summary> diff --git a/src/Microsoft.OData.Edm/PublicAPI/net45/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Edm/PublicAPI/net45/PublicAPI.Unshipped.txt index 5f282702bb..9271da2b65 100644 --- a/src/Microsoft.OData.Edm/PublicAPI/net45/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Edm/PublicAPI/net45/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +static System.Collections.Generic.ReadOnlyListExtensions.FindLastIndex<T>(this System.Collections.Generic.IReadOnlyList<T> list, System.Func<T, bool> predicate) -> int +System.Collections.Generic.ReadOnlyListExtensions \ No newline at end of file diff --git a/src/Microsoft.OData.Edm/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Edm/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt index 5f282702bb..9271da2b65 100644 --- a/src/Microsoft.OData.Edm/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Edm/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +static System.Collections.Generic.ReadOnlyListExtensions.FindLastIndex<T>(this System.Collections.Generic.IReadOnlyList<T> list, System.Func<T, bool> predicate) -> int +System.Collections.Generic.ReadOnlyListExtensions \ No newline at end of file diff --git a/src/Microsoft.OData.Edm/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Edm/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 5f282702bb..9271da2b65 100644 --- a/src/Microsoft.OData.Edm/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Edm/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +static System.Collections.Generic.ReadOnlyListExtensions.FindLastIndex<T>(this System.Collections.Generic.IReadOnlyList<T> list, System.Func<T, bool> predicate) -> int +System.Collections.Generic.ReadOnlyListExtensions \ No newline at end of file diff --git a/src/Microsoft.OData.Edm/System/Collections/Generic/ReadOnlyListExtensions.cs b/src/Microsoft.OData.Edm/System/Collections/Generic/ReadOnlyListExtensions.cs new file mode 100644 index 0000000000..c1aafd4aeb --- /dev/null +++ b/src/Microsoft.OData.Edm/System/Collections/Generic/ReadOnlyListExtensions.cs @@ -0,0 +1,45 @@ +namespace System.Collections.Generic +{ + /// <summary> + /// Extensions methods <see cref="IReadOnlyList{T}"/> + /// </summary> + public static class ReadOnlyListExtensions + { + /// <summary> + /// Searches for an element that matches the conditions defined by the specified predicate, and returns the zero-based index of the last occurrence within the + /// entire <see cref="IReadOnlyList{T}"/> + /// </summary> + /// <typeparam name="T">The type of the elements in <paramref name="list"/></typeparam> + /// <param name="list">The <see cref="IReadOnlyList{T}"/> to find the index of the last element of</param> + /// <param name="predicate">The <see cref="Func{T, TResult}"/> delegate that defines the conditions of the element to search for.</param> + /// <returns> + /// The zero-based index of the last occurrence of an element that matches the conditions defined by <paramref name="predicate"/>, if found; otherwise, -1 + /// </returns> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="list"/> or <paramref name="predicate"/> is <see langword="null"/></exception> + /// <remarks> + /// Copied from <see href="https://github.com/dotnet/runtime/blob/87c25589bda5a79baf8d056501663b8525f366a8/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs#L560"/> + /// </remarks> + public static int FindLastIndex<T>(this IReadOnlyList<T> list, Func<T, bool> predicate) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + for (int i = list.Count - 1; i > -1; --i) + { + if (predicate(list[i])) + { + return i; + } + } + + return -1; + } + } +} diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/GenerateContextUrlFromSlimUriWithDerivedTypeCastAndKeySegment.xml b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/GenerateContextUrlFromSlimUriWithDerivedTypeCastAndKeySegment.xml new file mode 100644 index 0000000000..94745422cd --- /dev/null +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/GenerateContextUrlFromSlimUriWithDerivedTypeCastAndKeySegment.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8" ?> +<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" xmlns:ags="http://aggregator.microsoft.com/internal" xmlns:odata="http://schemas.microsoft.com/oDataCapabilities"> + <edmx:DataServices> + <Schema Namespace="ns" Alias="self" xmlns="http://docs.oasis-open.org/odata/ns/edm" xmlns:ags="http://aggregator.microsoft.com/internal" xmlns:odata="http://schemas.microsoft.com/oDataCapabilities"> + <EntityContainer Name="Container"> + <EntitySet Name="orders" EntityType="self.order" /> + <EntitySet Name="categories" EntityType="self.category" /> + </EntityContainer> + <EntityType Name="order"> + <Key> + <PropertyRef Name="id" /> + </Key> + <Property Name="id" Type="Edm.String" Nullable="false" /> + <NavigationProperty Name="products" Type="Collection(self.product)" ContainsTarget="true" Nullable="false" /> + </EntityType> + <EntityType Name="product"> + <Key> + <PropertyRef Name="id" /> + </Key> + <Property Name="id" Type="Edm.String" Nullable="false" /> + <Property Name="name" Type="Edm.String" Nullable="false" /> + </EntityType> + <EntityType Name="category"> + <Key> + <PropertyRef Name="id" /> + </Key> + <Property Name="id" Type="Edm.String" Nullable="false" /> + <Property Name="foo" Type="Edm.String" Nullable="false" /> + </EntityType> + <EntityType Name="derivedProduct" BaseType="self.product"> + <NavigationProperty Name="category" Type="self.category" ContainsTarget="true" /> + </EntityType> + </Schema> + </edmx:DataServices> +</edmx:Edmx> \ No newline at end of file diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightWriterTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightWriterTests.cs index d07e797efd..9dc9ccc998 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightWriterTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightWriterTests.cs @@ -8,11 +8,18 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using System.Xml; using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +#if NETCOREAPP3_1_OR_GREATER using Microsoft.OData.Json; +#endif using Microsoft.OData.JsonLight; +using Microsoft.OData.UriParser; using Microsoft.OData.Tests; using Microsoft.Test.OData.DependencyInjection; using Xunit; @@ -739,6 +746,181 @@ public async Task WriteEntityReferenceLinkAsync() result); } + /// <summary> + /// Gets the name of the caller method of this method + /// </summary> + /// <param name="caller">The string that the method name of the caller will be written into</param> + /// <returns>The name of the caller method of this method</returns> + public static string GetCurrentMethodName([System.Runtime.CompilerServices.CallerMemberName] string caller = null) + { + return caller; + } + + /// <summary> + /// A <see cref="IEdmNavigationSource"/> that pretends to be the "products" contained navigation collection for the purposes of computing a context URL + /// </summary> + private sealed class MockNavigationSource : IEdmNavigationSource, IEdmContainedEntitySet, IEdmUnknownEntitySet + { + public IEnumerable<IEdmNavigationPropertyBinding> NavigationPropertyBindings => throw new NotImplementedException(); + + public IEdmPathExpression Path => throw new NotImplementedException(); + + public IEdmType Type => new EdmEntityType("ns", "products"); + + public string Name => "products"; + + public IEdmNavigationSource ParentNavigationSource => throw new NotImplementedException(); + + public IEdmNavigationProperty NavigationProperty => throw new NotImplementedException(); + + public IEnumerable<IEdmNavigationPropertyBinding> FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + throw new NotImplementedException(); + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty) + { + throw new NotImplementedException(); + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + throw new NotImplementedException(); + } + } + +#if !NETCOREAPP1_1 + /// <summary> + /// Generates a context URL from a <see cref="ODataUriSlim"/> that ends with cast and key segments + /// </summary> + /// <returns><see cref="void"/></returns> + [Fact] + public static void GenerateContextUrlFromSlimUriWithDerivedTypeCastAndKeySegment() + { + var domain = new Uri("http://tempuri.org"); + var requestUrl = new Uri(domain, "/orders('1')/products/ns.derivedProduct('2')"); + + // load the CSDL from the embedded resources + var assembly = Assembly.GetExecutingAssembly(); + var currentMethod = GetCurrentMethodName(); + var csdlResourceName = assembly.GetManifestResourceNames().Where(name => name.EndsWith($"{currentMethod}.xml")).Single(); + + // parse the CSDL + IEdmModel model; + using (var csdlResourceStream = assembly.GetManifestResourceStream(csdlResourceName)) + { + using (var xmlReader = XmlReader.Create(csdlResourceStream)) + { + if (!CsdlReader.TryParse(xmlReader, out model, out var errors)) + { + Assert.True(false, string.Join(Environment.NewLine, errors)); + } + } + } + + var uriParser = new ODataUriParser(model, domain, requestUrl); + var slimUri = new ODataUriSlim(uriParser.ParseUri()); + var contextUrlInfo = ODataContextUrlInfo.Create(new MockNavigationSource(), "ns.product", true, slimUri, ODataVersion.V4); + Assert.Equal(@"orders('1')/products", contextUrlInfo.NavigationPath); + } + + /// <summary> + /// Writes a resource as the response to a request where the URL ends with a combined cast and key segment + /// </summary> + /// <returns><see cref="void"/></returns> + [Fact] + public static async Task WriteContextWithDerivedTypeCastAndKeySegmentAsync() + { + var domain = new Uri("http://tempuri.org"); + var requestUrl = new Uri(domain, "/orders('1')/products/ns.derivedProduct('2')"); + var serviceSideResponseResource = new ODataResource + { + TypeName = "ns.product", + Properties = new List<ODataProperty> + { + new ODataProperty + { + Name = "id", + Value = "1", + SerializationInfo = new ODataPropertySerializationInfo + { + PropertyKind = ODataPropertyKind.Key + }, + }, + new ODataProperty + { + Name = "name", + Value = "somename", + }, + }, + }; + var expectedResponsePayload = + "{" + + "\"@odata.context\":\"http://tempuri.org/$metadata#orders('1')/products/$entity\"," + + "\"id\":\"1\"," + + "\"name\":\"somename\"" + + "}"; + + // load the CSDL from the embedded resources + var assembly = Assembly.GetExecutingAssembly(); + var currentMethod = GetCurrentMethodName(); + var csdlResourceName = assembly.GetManifestResourceNames().Where(name => name.EndsWith($"{currentMethod}.xml")).Single(); + + // parse the CSDL + IEdmModel model; + using (var csdlResourceStream = assembly.GetManifestResourceStream(csdlResourceName)) + { + using (var xmlReader = XmlReader.Create(csdlResourceStream)) + { + if (!CsdlReader.TryParse(xmlReader, out model, out var errors)) + { + Assert.True(false, string.Join(Environment.NewLine, errors)); + } + } + } + + using (var memoryStream = new MemoryStream()) + { + // initialize the json response writer + var uriParser = new ODataUriParser(model, domain, requestUrl); + var odataMessageWriterSettings = new ODataMessageWriterSettings + { + EnableMessageStreamDisposal = false, + Version = ODataVersion.V4, + ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"), + ODataUri = uriParser.ParseUri(), + }; + var messageInfo = new ODataMessageInfo + { + MessageStream = memoryStream, + MediaType = new ODataMediaType("application", "json"), + Encoding = Encoding.Default, + IsResponse = true, + IsAsync = true, + Model = model, + }; + var jsonLightOutputContext = new ODataJsonLightOutputContext(messageInfo, odataMessageWriterSettings); + var jsonLightWriter = new ODataJsonLightWriter( + jsonLightOutputContext, + null, + null, + false); + + // write the response + await jsonLightWriter.WriteStartAsync(serviceSideResponseResource); + await jsonLightWriter.WriteEndAsync(); + + // confirm that the written response was the expected response + memoryStream.Position = 0; + using (var streamReader = new StreamReader(memoryStream)) + { + var actualResponsePayload = await streamReader.ReadToEndAsync(); + Assert.Equal(expectedResponsePayload, actualResponsePayload); + } + } + } +#endif + [Fact] public async Task WriteEntityReferenceLinkForCollectionNavigationPropertyAsync() { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/WriteContextWithDerivedTypeCastAndKeySegmentAsync.xml b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/WriteContextWithDerivedTypeCastAndKeySegmentAsync.xml new file mode 100644 index 0000000000..62f8053d86 --- /dev/null +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/WriteContextWithDerivedTypeCastAndKeySegmentAsync.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" ?> +<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" xmlns:ags="http://aggregator.microsoft.com/internal" xmlns:odata="http://schemas.microsoft.com/oDataCapabilities"> + <edmx:DataServices> + <Schema Namespace="ns" Alias="self" xmlns="http://docs.oasis-open.org/odata/ns/edm" xmlns:ags="http://aggregator.microsoft.com/internal" xmlns:odata="http://schemas.microsoft.com/oDataCapabilities"> + <EntityContainer Name="Container"> + <EntitySet Name="orders" EntityType="self.order" /> + </EntityContainer> + <EntityType Name="order"> + <Key> + <PropertyRef Name="id" /> + </Key> + <Property Name="id" Type="Edm.String" Nullable="false" /> + <NavigationProperty Name="products" Type="Collection(self.product)" ContainsTarget="true" Nullable="false" /> + </EntityType> + <EntityType Name="product"> + <Key> + <PropertyRef Name="id" /> + </Key> + <Property Name="id" Type="Edm.String" Nullable="false" /> + <Property Name="name" Type="Edm.String" Nullable="false" /> + </EntityType> + <EntityType Name="derivedProduct" BaseType="self.product"> + </EntityType> + </Schema> + </edmx:DataServices> +</edmx:Edmx> \ No newline at end of file diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Microsoft.OData.Core.Tests.csproj b/test/FunctionalTests/Microsoft.OData.Core.Tests/Microsoft.OData.Core.Tests.csproj index 3935c6030e..abe5dc87d9 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Microsoft.OData.Core.Tests.csproj +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Microsoft.OData.Core.Tests.csproj @@ -51,6 +51,11 @@ <Compile Remove="Properties\AssemblyInfo.cs" /> </ItemGroup> + <ItemGroup> + <None Remove="JsonLight\GenerateContextUrlFromSlimUriWithDerivedTypeCastAndKeySegment.xml" /> + <None Remove="JsonLight\WriteContextWithDerivedTypeCastAndKeySegmentAsync.xml" /> + </ItemGroup> + <ItemGroup> <Compile Include="..\Tests\TestUtils\Common\Microsoft.Test.OData.Utils\Common\ExceptionUtilities.cs" Link="ExceptionUtilities.cs" /> <Compile Include="..\Tests\TestUtils\Common\Microsoft.Test.OData.Utils\Metadata\EdmConstants.cs" Link="EdmConstants.cs" /> @@ -61,6 +66,11 @@ <Compile Include="..\Tests\TestUtils\Common\Microsoft.Test.OData.Utils\ODataLibTest\ModelBuilder.cs" Link="ModelBuilder.cs" /> </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="JsonLight\GenerateContextUrlFromSlimUriWithDerivedTypeCastAndKeySegment.xml" /> + <EmbeddedResource Include="JsonLight\WriteContextWithDerivedTypeCastAndKeySegmentAsync.xml" /> + </ItemGroup> + <ItemGroup> <ProjectReference Include="..\..\..\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj" /> <ProjectReference Include="..\..\..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj" /> 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 23d9f8aa68..4883294325 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/SemanticAst/ODataPathExtensionsTests.cs @@ -251,6 +251,53 @@ public void TestTrimEndingTypeCast() } } + /// <summary> + /// Test trimming the end of a path of it's key and type segments + /// </summary> + [Fact] + public void TrimEndingTypeAndKeySegments() + { + var testCases = new[] + { + new { + Url = "People", + Trimmed = "People" + }, + new { + Url = "People(1)", + Trimmed = "People" + }, + new { + Url = "People/Fully.Qualified.Namespace.Employee", + Trimmed = "People" + }, + new { + Url = "People(1)/Fully.Qualified.Namespace.Employee", + Trimmed = "People" + }, + new { + Url = "People/Fully.Qualified.Namespace.Employee/1", + Trimmed = "People" + }, + new { + Url = "People/Fully.Qualified.Namespace.Employee/1/MyAddress", + Trimmed = "People/Fully.Qualified.Namespace.Employee/1/MyAddress" + }, + new { + Url = "People(1)/Fully.Qualified.Namespace.Employee/MyAddress", + Trimmed = "People/1/Fully.Qualified.Namespace.Employee/MyAddress" + }, + }; + + foreach (var testCase in testCases) + { + ODataUriParser parser = new ODataUriParser(HardCodedTestModel.TestModel, this.testBaseUri, new Uri(this.testBaseUri, testCase.Url)); + ODataPath path = parser.ParsePath(); + var result = path.TrimEndingTypeAndKeySegments(); + Assert.Equal(testCase.Trimmed, result.ToResourcePathString(ODataUrlKeyDelimiter.Slash)); + } + } + [Fact] public void TestIsIndividualProperty() { diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/System/Collections/Generic/ReadOnlyListExtensionsTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/System/Collections/Generic/ReadOnlyListExtensionsTests.cs new file mode 100644 index 0000000000..58b9e43d82 --- /dev/null +++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/System/Collections/Generic/ReadOnlyListExtensionsTests.cs @@ -0,0 +1,24 @@ +namespace System.Collections.Generic +{ + using Xunit; + + /// <summary> + /// Extensions methods <see cref="IReadOnlyList{T}"/> + /// </summary> + public sealed class ReadOnlyListExtensionsTests + { + /// <summary> + /// Copied from <see href="https://github.com/dotnet/runtime/blob/87c25589bda5a79baf8d056501663b8525f366a8/src/libraries/System.Runtime/tests/System/ArrayTests.cs#L2040"/> + /// </summary> + [Fact] + public void FindLastIndex() + { + var intArray = new int[] { 40, 41, 42, 43, 44, 45, 46, 47, 48, 49 }; + Assert.Equal(9, intArray.FindLastIndex(i => i >= 43)); + Assert.Equal(-1, intArray.FindLastIndex(i => i == 99)); + + intArray = new int[0]; + Assert.Equal(-1, intArray.FindLastIndex(i => i == 43)); + } + } +} diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl index f91c6bb217..f3062d6cec 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl @@ -2914,6 +2914,16 @@ public sealed class Microsoft.OData.Edm.EdmUntypedStructuredType : Microsoft.ODa Microsoft.OData.Edm.EdmTypeKind TypeKind { public virtual get; } } +[ +ExtensionAttribute(), +] +public sealed class System.Collections.Generic.ReadOnlyListExtensions { + [ + ExtensionAttribute(), + ] + public static int FindLastIndex (IReadOnlyList`1 list, Func`2 predicate) +} + public enum Microsoft.OData.Edm.Csdl.CsdlTarget : int { EntityFramework = 0 OData = 1 @@ -6408,6 +6418,11 @@ public sealed class Microsoft.OData.UriParser.ODataPathExtensions { ] public static Microsoft.OData.UriParser.ODataPath TrimEndingKeySegment (Microsoft.OData.UriParser.ODataPath path) + [ + ExtensionAttribute(), + ] + public static Microsoft.OData.UriParser.ODataPath TrimEndingTypeAndKeySegments (Microsoft.OData.UriParser.ODataPath path) + [ ExtensionAttribute(), ] diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl index c30f3b01e8..787f26974f 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl @@ -2914,6 +2914,16 @@ public sealed class Microsoft.OData.Edm.EdmUntypedStructuredType : Microsoft.ODa Microsoft.OData.Edm.EdmTypeKind TypeKind { public virtual get; } } +[ +ExtensionAttribute(), +] +public sealed class System.Collections.Generic.ReadOnlyListExtensions { + [ + ExtensionAttribute(), + ] + public static int FindLastIndex (IReadOnlyList`1 list, Func`2 predicate) +} + public enum Microsoft.OData.Edm.Csdl.CsdlTarget : int { EntityFramework = 0 OData = 1 @@ -6408,6 +6418,11 @@ public sealed class Microsoft.OData.UriParser.ODataPathExtensions { ] public static Microsoft.OData.UriParser.ODataPath TrimEndingKeySegment (Microsoft.OData.UriParser.ODataPath path) + [ + ExtensionAttribute(), + ] + public static Microsoft.OData.UriParser.ODataPath TrimEndingTypeAndKeySegments (Microsoft.OData.UriParser.ODataPath path) + [ ExtensionAttribute(), ] diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl index f91c6bb217..f3062d6cec 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl @@ -2914,6 +2914,16 @@ public sealed class Microsoft.OData.Edm.EdmUntypedStructuredType : Microsoft.ODa Microsoft.OData.Edm.EdmTypeKind TypeKind { public virtual get; } } +[ +ExtensionAttribute(), +] +public sealed class System.Collections.Generic.ReadOnlyListExtensions { + [ + ExtensionAttribute(), + ] + public static int FindLastIndex (IReadOnlyList`1 list, Func`2 predicate) +} + public enum Microsoft.OData.Edm.Csdl.CsdlTarget : int { EntityFramework = 0 OData = 1 @@ -6408,6 +6418,11 @@ public sealed class Microsoft.OData.UriParser.ODataPathExtensions { ] public static Microsoft.OData.UriParser.ODataPath TrimEndingKeySegment (Microsoft.OData.UriParser.ODataPath path) + [ + ExtensionAttribute(), + ] + public static Microsoft.OData.UriParser.ODataPath TrimEndingTypeAndKeySegments (Microsoft.OData.UriParser.ODataPath path) + [ ExtensionAttribute(), ]