From 05b15f46dbd831ee3ae46e0f312c6d2f6a72c28d Mon Sep 17 00:00:00 2001 From: Andrew Omondi Date: Tue, 28 Jan 2025 10:22:45 +0300 Subject: [PATCH 1/2] Adds support for retrieving collection of enum values from `UpdateMethod` property of `UpdateRestrictions` annotation (#567) * Add method for retrieving collection of enum values * Update the UpdateMethod to an IList * Add support for generating both PUT and PATCH operations * Update unit tests to validate support for both PUT and PATCH * Update release note * Small typo fix * Another very small typo fix * Update function to account for flagged enums * Use flags attribute on enum type; revert property name --- .../Edm/RecordExpressionExtensions.cs | 24 ++++++++++++---- .../PathItem/ComplexPropertyItemHandler.cs | 7 ++++- .../PathItem/EntityPathItemHandler.cs | 7 ++++- .../NavigationPropertyPathItemHandler.cs | 28 +++++++++++-------- .../Capabilities/UpdateRestrictionsType.cs | 22 ++++++++++----- .../NavigationPropertyPathItemHandlerTests.cs | 14 +++++----- 6 files changed, 69 insertions(+), 33 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs index 01cf7aeab..5d8dc6d4a 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs @@ -94,18 +94,30 @@ public static string GetString(this IEdmRecordExpression record, string property /// The property name. /// The Enum value or null. public static T? GetEnum(this IEdmRecordExpression record, string propertyName) - where T : struct + where T : struct, Enum { Utils.CheckArgumentNull(record, nameof(record)); Utils.CheckArgumentNull(propertyName, nameof(propertyName)); - return (record.Properties?.FirstOrDefault(e => propertyName.Equals(e.Name, StringComparison.Ordinal)) is IEdmPropertyConstructor property && + if (record.Properties?.FirstOrDefault(e => propertyName.Equals(e.Name, StringComparison.Ordinal)) + is IEdmPropertyConstructor property && property.Value is IEdmEnumMemberExpression value && value.EnumMembers != null && - value.EnumMembers.Any() && - Enum.TryParse(value.EnumMembers.First().Name, out T result)) ? - result : - null; + value.EnumMembers.Any()) + { + long combinedValue = 0; + foreach (var enumMember in value.EnumMembers) + { + if (Enum.TryParse(enumMember.Name, out T enumValue)) + { + combinedValue |= Convert.ToInt64(enumValue); + } + } + + return (T)Enum.ToObject(typeof(T), combinedValue); + } + + return null; } /// diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs index 866dcef3d..b08a22892 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs @@ -70,7 +70,12 @@ public void AddUpdateOperation(OpenApiPathItem item) if ((Context.Settings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths && isUpdatable) || !Context.Settings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths) { - if (updateRestrictions != null && updateRestrictions.IsUpdateMethodPut) + if (updateRestrictions?.IsUpdateMethodPutAndPatch == true) + { + AddOperation(item, OperationType.Put); + AddOperation(item, OperationType.Patch); + } + else if (updateRestrictions?.IsUpdateMethodPut == true) { AddOperation(item, OperationType.Put); } diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs index 7c66d7d9c..d7f688ae4 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs @@ -40,7 +40,12 @@ protected override void SetOperations(OpenApiPathItem item) updateRestrictions ??= entityUpdateRestrictions; if (updateRestrictions?.IsUpdatable ?? true) { - if (updateRestrictions != null && updateRestrictions.IsUpdateMethodPut) + if (updateRestrictions?.IsUpdateMethodPutAndPatch == true) + { + AddOperation(item, OperationType.Put); + AddOperation(item, OperationType.Patch); + } + else if (updateRestrictions?.IsUpdateMethodPut == true) { AddOperation(item, OperationType.Put); } diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs index 57b361197..e66675f75 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs @@ -243,17 +243,23 @@ private void AddDeleteOperation(OpenApiPathItem item, NavigationPropertyRestrict private void AddUpdateOperation(OpenApiPathItem item, UpdateRestrictionsType updateRestrictionsType) { - if (updateRestrictionsType == null || updateRestrictionsType.IsUpdatable) - { - if (updateRestrictionsType != null && updateRestrictionsType.IsUpdateMethodPut) - { - AddOperation(item, OperationType.Put); - } - else - { - AddOperation(item, OperationType.Patch); - } - } + if (updateRestrictionsType?.IsUpdatable ?? true) + { + if (updateRestrictionsType?.IsUpdateMethodPutAndPatch == true) + { + AddOperation(item, OperationType.Put); + AddOperation(item, OperationType.Patch); + } + else if (updateRestrictionsType?.IsUpdateMethodPut == true) + { + AddOperation(item, OperationType.Put); + } + else + { + AddOperation(item, OperationType.Patch); + } + } + } /// diff --git a/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/UpdateRestrictionsType.cs b/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/UpdateRestrictionsType.cs index 27e3e8f59..ff073e399 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/UpdateRestrictionsType.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/UpdateRestrictionsType.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.OData.Edm.Vocabularies; @@ -14,17 +15,18 @@ namespace Microsoft.OpenApi.OData.Vocabulary.Capabilities /// /// Enumerates HTTP methods that can be used to update entities /// + [Flags] internal enum HttpMethod { /// /// The HTTP PATCH Method /// - PATCH, + PATCH = 1, /// /// The HTTP PUT Method /// - PUT + PUT = 2 } /// /// Complex Type: Org.OData.Capabilities.V1.UpdateRestrictionsType @@ -46,10 +48,10 @@ internal class UpdateRestrictionsType : IRecord /// /// Gets the value indicating Entities can be inserted, updated, and deleted via a PATCH request with a delta payload. /// - public bool? DeltaUpdateSupported { get; private set; } - + public bool? DeltaUpdateSupported { get; private set; } + /// - /// Gets the value indicating the HTTP Method (PUT or PATCH) for updating an entity. + /// Gets the values indicating the HTTP Method (PUT and/or PATCH) for updating an entity. /// If null, PATCH should be supported and PUT MAY be supported. /// public HttpMethod? UpdateMethod { get; private set; } @@ -124,10 +126,16 @@ public bool IsNonUpdatableNavigationProperty(string navigationPropertyPath) } /// - /// Tests whether the update method for the entity has been explicitly specified as PUT + /// Tests whether the update method for the target has been explicitly specified as PUT /// public bool IsUpdateMethodPut => UpdateMethod.HasValue && UpdateMethod.Value == HttpMethod.PUT; + /// + /// Tests whether the update method for the target has been explicitly specified as PATCH and PUT + /// + public bool IsUpdateMethodPutAndPatch => UpdateMethod.HasValue && + (UpdateMethod.Value & (HttpMethod.PUT | HttpMethod.PATCH)) == (HttpMethod.PUT | HttpMethod.PATCH); + /// /// Lists the media types acceptable for the request content /// @@ -157,7 +165,7 @@ public void Initialize(IEdmRecordExpression record) // DeltaUpdateSupported DeltaUpdateSupported = record.GetBoolean("DeltaUpdateSupported"); - // UpdateMethod + // UpdateMethod UpdateMethod = record.GetEnum("UpdateMethod"); // FilterSegmentSupported diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs index 66aedac9f..61f383455 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs @@ -452,7 +452,7 @@ public void CreatePathItemForNavigationPropertyAndUpdateMethodUpdateRestrictions - Org.OData.Capabilities.V1.HttpMethod/PUT + Org.OData.Capabilities.V1.HttpMethod/PUT Org.OData.Capabilities.V1.HttpMethod/PATCH @@ -488,21 +488,21 @@ public void CreatePathItemForNavigationPropertyAndUpdateMethodUpdateRestrictions if (isContainment) { expected = updatable - ? (new[] { OperationType.Get, OperationType.Put, OperationType.Delete }) - : (new[] { OperationType.Get, OperationType.Delete }); + ? ([OperationType.Get, OperationType.Put, OperationType.Patch, OperationType.Delete]) + : ([OperationType.Get, OperationType.Delete]); } else { expected = updatable - ? (new[] { OperationType.Get, OperationType.Put }) - : (new[] { OperationType.Get }); + ? ([OperationType.Get, OperationType.Put, OperationType.Patch,]) + : ([OperationType.Get]); } } else { expected = isContainment - ? (new[] { OperationType.Get, OperationType.Patch, OperationType.Delete }) - : (new[] { OperationType.Get }); + ? ([OperationType.Get, OperationType.Patch, OperationType.Delete]) + : ([OperationType.Get]); } From f69fea0e056e6de5a76b128e31ab580ec13356fa Mon Sep 17 00:00:00 2001 From: Andrew Omondi Date: Tue, 28 Jan 2025 10:26:53 +0300 Subject: [PATCH 2/2] Sync 567 changes to v1 of the library --- .../Microsoft.OpenAPI.OData.Reader.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj index 7ab181e85..441a9d880 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj +++ b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj @@ -15,13 +15,13 @@ netstandard2.0 Microsoft.OpenApi.OData true - 1.7.3 + 1.7.4 This package contains the codes you need to convert OData CSDL to Open API Document of Model. © Microsoft Corporation. All rights reserved. Microsoft OpenApi OData EDM https://github.com/Microsoft/OpenAPI.NET.OData - - Fix regression in unique operation id generation at #462. + - Sync changes from https://github.com/microsoft/OpenAPI.NET.OData/pull/567 to V1 of the library Microsoft.OpenApi.OData.Reader ..\..\tool\Microsoft.OpenApi.OData.snk