From f9a9405c15890db7f1ee31b5c548c701686e1abc Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 17 Nov 2023 09:39:39 -0800 Subject: [PATCH] =?UTF-8?q?updated=20odata.context=20computation=20to=20re?= =?UTF-8?q?move=20trailing=20cast=20segments=20th=E2=80=A6=20(#2681)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updated odata.context computation to remove trailing cast segments that are following by key segments as well as the case where the standard key syntax is used within a cast segment * Removed use of Predicate delegate * Update ReadOnlyCollectionExtensions.cs * fixed public api tests * actually fix public api tests? * add unit tests for the new trim extension * added comment * adding back the odataurislim variant for computing context url * moving findlastindex from spatial to edm * adding test for findlastindex * fix publicapi tests * public api tests * asdf --- .../ODataContextUrlInfo.cs | 6 +- .../PublicAPI/net45/PublicAPI.Unshipped.txt | 1 + .../netcoreapp3.1/PublicAPI.Unshipped.txt | 1 + .../netstandard1.1/PublicAPI.Unshipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 1 + .../SemanticAst/ODataPathExtensions.cs | 16 ++ .../PublicAPI/net45/PublicAPI.Unshipped.txt | 3 +- .../netstandard1.1/PublicAPI.Unshipped.txt | 3 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 3 +- .../Generic/ReadOnlyListExtensions.cs | 45 +++++ ...limUriWithDerivedTypeCastAndKeySegment.xml | 35 ++++ .../JsonLight/ODataJsonLightWriterTests.cs | 182 ++++++++++++++++++ ...tWithDerivedTypeCastAndKeySegmentAsync.xml | 26 +++ .../Microsoft.OData.Core.Tests.csproj | 10 + .../SemanticAst/ODataPathExtensionsTests.cs | 47 +++++ .../Generic/ReadOnlyListExtensionsTests.cs | 24 +++ .../Microsoft.OData.PublicApi.net45.bsl | 15 ++ ...crosoft.OData.PublicApi.netstandard1.1.bsl | 15 ++ ...crosoft.OData.PublicApi.netstandard2.0.bsl | 15 ++ 19 files changed, 442 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.OData.Edm/System/Collections/Generic/ReadOnlyListExtensions.cs create mode 100644 test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/GenerateContextUrlFromSlimUriWithDerivedTypeCastAndKeySegment.xml create mode 100644 test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/WriteContextWithDerivedTypeCastAndKeySegmentAsync.xml create mode 100644 test/FunctionalTests/Microsoft.OData.Edm.Tests/System/Collections/Generic/ReadOnlyListExtensionsTests.cs 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; } + /// + /// Creates a that is with the type segments and key segments removed from the end + /// + /// The to trim the ending of + /// A without type-cast and key segments at the end + /// Thrown if is + 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)); + } + /// /// Creates a new ODataPath with the specified segment added. /// 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(this System.Collections.Generic.IReadOnlyList list, System.Func 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(this System.Collections.Generic.IReadOnlyList list, System.Func 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(this System.Collections.Generic.IReadOnlyList list, System.Func 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 +{ + /// + /// Extensions methods + /// + public static class ReadOnlyListExtensions + { + /// + /// 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 + /// + /// The type of the elements in + /// The to find the index of the last element of + /// The delegate that defines the conditions of the element to search for. + /// + /// The zero-based index of the last occurrence of an element that matches the conditions defined by , if found; otherwise, -1 + /// + /// Thrown if or is + /// + /// Copied from + /// + public static int FindLastIndex(this IReadOnlyList list, Func 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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); } + /// + /// Gets the name of the caller method of this method + /// + /// The string that the method name of the caller will be written into + /// The name of the caller method of this method + public static string GetCurrentMethodName([System.Runtime.CompilerServices.CallerMemberName] string caller = null) + { + return caller; + } + + /// + /// A that pretends to be the "products" contained navigation collection for the purposes of computing a context URL + /// + private sealed class MockNavigationSource : IEdmNavigationSource, IEdmContainedEntitySet, IEdmUnknownEntitySet + { + public IEnumerable 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 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 + /// + /// Generates a context URL from a that ends with cast and key segments + /// + /// + [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); + } + + /// + /// Writes a resource as the response to a request where the URL ends with a combined cast and key segment + /// + /// + [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 + { + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + @@ -61,6 +66,11 @@ + + + + + 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() } } + /// + /// Test trimming the end of a path of it's key and type segments + /// + [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; + + /// + /// Extensions methods + /// + public sealed class ReadOnlyListExtensionsTests + { + /// + /// Copied from + /// + [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(), ]