diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs b/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs index 1a3eefdb..1998f6bd 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs @@ -9,6 +9,7 @@ using System.Runtime; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Vocabularies; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -391,5 +392,29 @@ internal static string StripOrAliasNamespacePrefix(IEdmSchemaElement element, Op return segmentName; } + + /// + /// Checks whether an operation is allowed on a model element. + /// + /// The Edm model. + /// The target operation. + /// The model element. + /// true if the operation is allowed, otherwise false. + internal static bool IsOperationAllowed(IEdmModel model, IEdmOperation edmOperation, IEdmVocabularyAnnotatable annotatable) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(edmOperation, nameof(edmOperation)); + Utils.CheckArgumentNull(annotatable, nameof(annotatable)); + + var requiresExplicitBinding = model.FindVocabularyAnnotations(edmOperation).FirstOrDefault(x => x.Term.Name == CapabilitiesConstants.RequiresExplicitBindingName); + + if (requiresExplicitBinding == null) + { + return true; + } + + var boundOperations = model.GetCollection(annotatable, CapabilitiesConstants.ExplicitOperationBindings)?.ToList(); + return boundOperations != null && boundOperations.Contains(edmOperation.FullName()); + } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs index caa5ec73..c516f055 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs @@ -34,6 +34,7 @@ public class ODataPathProvider : IODataPathProvider private readonly IDictionary> _dollarCountPaths = new Dictionary>(); + /// /// Can filter the or not. /// @@ -728,7 +729,7 @@ private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings) } var firstEntityType = bindingType.AsEntity().EntityDefinition(); - + bool filter(IEdmNavigationSource z) => z.EntityType() != firstEntityType && z.EntityType().FindAllBaseTypes().Contains(firstEntityType); @@ -790,6 +791,20 @@ secondLastPathSegment is not ODataKeySegment && { if (lastPathSegment is ODataTypeCastSegment && !convertSettings.AppendBoundOperationsOnDerivedTypeCastSegments) continue; if (lastPathSegment is ODataKeySegment segment && segment.IsAlternateKey) continue; + + var annotatable = (lastPathSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable; + annotatable ??= (lastPathSegment as ODataKeySegment)?.EntityType; + + if (annotatable != null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable)) + { + // Check whether the navigation source is allowed to have an operation on the entity type + annotatable = (secondLastPathSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable; + if (annotatable != null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable)) + { + continue; + } + } + ODataPath newPath = subPath.Clone(); newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction, _model)); AppendPath(newPath); @@ -814,6 +829,11 @@ private void AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOpera { continue; } + + if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, npSegment.NavigationProperty)) + { + continue; + } bool isLastKeySegment = path.LastSegment is ODataKeySegment; @@ -866,6 +886,11 @@ private void AppendBoundOperationOnDerived( continue; } + if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, ns as IEdmVocabularyAnnotatable)) + { + continue; + } + if (isCollection) { if (ns is IEdmEntitySet) @@ -934,6 +959,11 @@ private void AppendBoundOperationOnDerivedNavigationPropertyPath( continue; } + if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, npSegment.NavigationProperty)) + { + continue; + } + bool isLastKeySegment = path.LastSegment is ODataKeySegment; if (isCollection) @@ -958,7 +988,7 @@ private void AppendBoundOperationOnDerivedNavigationPropertyPath( } if (HasUnsatisfiedDerivedTypeConstraint( - npSegment.NavigationProperty as IEdmVocabularyAnnotatable, + npSegment.NavigationProperty, baseType, convertSettings)) { 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 015998a7..9a717845 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj +++ b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj @@ -15,7 +15,7 @@ netstandard2.0 Microsoft.OpenApi.OData true - 1.4.0-preview6 + 1.4.0-preview7 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 @@ -27,6 +27,7 @@ - Use directly annotated CountRestriction annotations when creating $count segments for collection-valued navigation properties #328 - Use MediaType annotation to set the content types of operations with Edm.Stream return types #342 - Retrieves navigation properties from base types #371 +- Adds support for RequiresExplicitBinding and ExplicitOperationBindings annotations for operations #323 #232 Microsoft.OpenApi.OData.Reader ..\..\tool\Microsoft.OpenApi.OData.snk diff --git a/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/CapabilitiesConstants.cs b/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/CapabilitiesConstants.cs index 11d5d493..81d38a20 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/CapabilitiesConstants.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/CapabilitiesConstants.cs @@ -94,5 +94,15 @@ internal class CapabilitiesConstants /// Org.OData.Capabilities.V1.KeyAsSegmentSupported /// public const string KeyAsSegmentSupported = "Org.OData.Capabilities.V1.KeyAsSegmentSupported"; + + /// + /// RequiresExplicitBinding + /// + public const string RequiresExplicitBindingName = "RequiresExplicitBinding"; + + /// + /// Org.OData.Capabilities.V1.ExplicitOperationBindings + /// + public const string ExplicitOperationBindings = "Org.OData.Core.V1.ExplicitOperationBindings"; } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs index f4c585d5..bc775f59 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs @@ -52,7 +52,7 @@ public void GetPathsForGraphBetaModelReturnsAllPaths() // Assert Assert.NotNull(paths); - Assert.Equal(18409, paths.Count()); + Assert.Equal(18264, paths.Count()); AssertGraphBetaModelPaths(paths); } @@ -70,6 +70,19 @@ private void AssertGraphBetaModelPaths(IEnumerable paths) // Test that navigation properties on base types are created Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/print/printers({id})/jobs"))); + + // Test that RequiresExplicitBinding and ExplicitOperationBindings annotations work + Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.checkMemberGroups"))); + Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.checkMemberObjects"))); + Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.getMemberGroups"))); + Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.getMemberObjects"))); + Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.restore"))); + + Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.checkMemberGroups"))); + Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.checkMemberObjects"))); + Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.getMemberGroups"))); + Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.getMemberObjects"))); + Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.restore"))); } [Fact] @@ -90,7 +103,7 @@ public void GetPathsForGraphBetaModelWithDerivedTypesConstraintReturnsAllPaths() // Assert Assert.NotNull(paths); - Assert.Equal(19060, paths.Count()); + Assert.Equal(18915, paths.Count()); } [Theory] diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml index 84ecab83..1bace6d6 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml @@ -53506,6 +53506,11 @@ microsoft.graph.group microsoft.graph.application + + + + microsoft.graph.restore + @@ -103443,6 +103448,7 @@ + @@ -104558,6 +104564,7 @@ + @@ -104577,11 +104584,13 @@ + + @@ -104601,6 +104610,7 @@ + @@ -108921,7 +108931,7 @@ - + @@ -109111,7 +109121,7 @@ - + @@ -112362,7 +112372,17 @@ - + + + + + microsoft.graph.checkMemberGroups + microsoft.graph.checkMemberObjects + microsoft.graph.getMemberGroups + microsoft.graph.getMemberObjects + + +