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
+
+
+