Skip to content

Commit 08e5738

Browse files
[Feature] Adds support for RequiresExplicitBinding and ExplicitOperationBindings annotations for operations (#378)
* Add annotation constants * Use annotations to restrict generation of operations * Update unit and integration tests * Update release notes * Remove whitespace * Validate operation generation for separate model elements * Update unit and integration tests * Update integration test * Consider when entity type can't be annotated because it is a base type * Update src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs Co-authored-by: Eastman <[email protected]> --------- Co-authored-by: Eastman <[email protected]>
1 parent 5b50c6c commit 08e5738

File tree

6 files changed

+107
-8
lines changed

6 files changed

+107
-8
lines changed

src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Runtime;
1010
using Microsoft.OData.Edm;
1111
using Microsoft.OData.Edm.Csdl;
12+
using Microsoft.OData.Edm.Vocabularies;
1213
using Microsoft.OpenApi.Models;
1314
using Microsoft.OpenApi.OData.Edm;
1415
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
@@ -391,5 +392,29 @@ internal static string StripOrAliasNamespacePrefix(IEdmSchemaElement element, Op
391392

392393
return segmentName;
393394
}
395+
396+
/// <summary>
397+
/// Checks whether an operation is allowed on a model element.
398+
/// </summary>
399+
/// <param name="model">The Edm model.</param>
400+
/// <param name="edmOperation">The target operation.</param>
401+
/// <param name="annotatable">The model element.</param>
402+
/// <returns>true if the operation is allowed, otherwise false.</returns>
403+
internal static bool IsOperationAllowed(IEdmModel model, IEdmOperation edmOperation, IEdmVocabularyAnnotatable annotatable)
404+
{
405+
Utils.CheckArgumentNull(model, nameof(model));
406+
Utils.CheckArgumentNull(edmOperation, nameof(edmOperation));
407+
Utils.CheckArgumentNull(annotatable, nameof(annotatable));
408+
409+
var requiresExplicitBinding = model.FindVocabularyAnnotations(edmOperation).FirstOrDefault(x => x.Term.Name == CapabilitiesConstants.RequiresExplicitBindingName);
410+
411+
if (requiresExplicitBinding == null)
412+
{
413+
return true;
414+
}
415+
416+
var boundOperations = model.GetCollection(annotatable, CapabilitiesConstants.ExplicitOperationBindings)?.ToList();
417+
return boundOperations != null && boundOperations.Contains(edmOperation.FullName());
418+
}
394419
}
395420
}

src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class ODataPathProvider : IODataPathProvider
3434
private readonly IDictionary<IEdmEntityType, IList<ODataPath>> _dollarCountPaths =
3535
new Dictionary<IEdmEntityType, IList<ODataPath>>();
3636

37+
3738
/// <summary>
3839
/// Can filter the <see cref="IEdmElement"/> or not.
3940
/// </summary>
@@ -728,7 +729,7 @@ private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
728729
}
729730

730731
var firstEntityType = bindingType.AsEntity().EntityDefinition();
731-
732+
732733
bool filter(IEdmNavigationSource z) =>
733734
z.EntityType() != firstEntityType &&
734735
z.EntityType().FindAllBaseTypes().Contains(firstEntityType);
@@ -790,6 +791,20 @@ secondLastPathSegment is not ODataKeySegment &&
790791
{
791792
if (lastPathSegment is ODataTypeCastSegment && !convertSettings.AppendBoundOperationsOnDerivedTypeCastSegments) continue;
792793
if (lastPathSegment is ODataKeySegment segment && segment.IsAlternateKey) continue;
794+
795+
var annotatable = (lastPathSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable;
796+
annotatable ??= (lastPathSegment as ODataKeySegment)?.EntityType;
797+
798+
if (annotatable != null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable))
799+
{
800+
// Check whether the navigation source is allowed to have an operation on the entity type
801+
annotatable = (secondLastPathSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable;
802+
if (annotatable != null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable))
803+
{
804+
continue;
805+
}
806+
}
807+
793808
ODataPath newPath = subPath.Clone();
794809
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction, _model));
795810
AppendPath(newPath);
@@ -814,6 +829,11 @@ private void AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOpera
814829
{
815830
continue;
816831
}
832+
833+
if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, npSegment.NavigationProperty))
834+
{
835+
continue;
836+
}
817837

818838
bool isLastKeySegment = path.LastSegment is ODataKeySegment;
819839

@@ -866,6 +886,11 @@ private void AppendBoundOperationOnDerived(
866886
continue;
867887
}
868888

889+
if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, ns as IEdmVocabularyAnnotatable))
890+
{
891+
continue;
892+
}
893+
869894
if (isCollection)
870895
{
871896
if (ns is IEdmEntitySet)
@@ -934,6 +959,11 @@ private void AppendBoundOperationOnDerivedNavigationPropertyPath(
934959
continue;
935960
}
936961

962+
if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, npSegment.NavigationProperty))
963+
{
964+
continue;
965+
}
966+
937967
bool isLastKeySegment = path.LastSegment is ODataKeySegment;
938968

939969
if (isCollection)
@@ -958,7 +988,7 @@ private void AppendBoundOperationOnDerivedNavigationPropertyPath(
958988
}
959989

960990
if (HasUnsatisfiedDerivedTypeConstraint(
961-
npSegment.NavigationProperty as IEdmVocabularyAnnotatable,
991+
npSegment.NavigationProperty,
962992
baseType,
963993
convertSettings))
964994
{

src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<TargetFrameworks>netstandard2.0</TargetFrameworks>
1616
<PackageId>Microsoft.OpenApi.OData</PackageId>
1717
<SignAssembly>true</SignAssembly>
18-
<Version>1.4.0-preview6</Version>
18+
<Version>1.4.0-preview7</Version>
1919
<Description>This package contains the codes you need to convert OData CSDL to Open API Document of Model.</Description>
2020
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
2121
<PackageTags>Microsoft OpenApi OData EDM</PackageTags>
@@ -27,6 +27,7 @@
2727
- Use directly annotated CountRestriction annotations when creating $count segments for collection-valued navigation properties #328
2828
- Use MediaType annotation to set the content types of operations with Edm.Stream return types #342
2929
- Retrieves navigation properties from base types #371
30+
- Adds support for RequiresExplicitBinding and ExplicitOperationBindings annotations for operations #323 #232
3031
</PackageReleaseNotes>
3132
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
3233
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>

src/Microsoft.OpenApi.OData.Reader/Vocabulary/Capabilities/CapabilitiesConstants.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,15 @@ internal class CapabilitiesConstants
9494
/// Org.OData.Capabilities.V1.KeyAsSegmentSupported
9595
/// </summary>
9696
public const string KeyAsSegmentSupported = "Org.OData.Capabilities.V1.KeyAsSegmentSupported";
97+
98+
/// <summary>
99+
/// RequiresExplicitBinding
100+
/// </summary>
101+
public const string RequiresExplicitBindingName = "RequiresExplicitBinding";
102+
103+
/// <summary>
104+
/// Org.OData.Capabilities.V1.ExplicitOperationBindings
105+
/// </summary>
106+
public const string ExplicitOperationBindings = "Org.OData.Core.V1.ExplicitOperationBindings";
97107
}
98108
}

test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void GetPathsForGraphBetaModelReturnsAllPaths()
5252

5353
// Assert
5454
Assert.NotNull(paths);
55-
Assert.Equal(18409, paths.Count());
55+
Assert.Equal(18264, paths.Count());
5656
AssertGraphBetaModelPaths(paths);
5757
}
5858

@@ -70,6 +70,19 @@ private void AssertGraphBetaModelPaths(IEnumerable<ODataPath> paths)
7070

7171
// Test that navigation properties on base types are created
7272
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/print/printers({id})/jobs")));
73+
74+
// Test that RequiresExplicitBinding and ExplicitOperationBindings annotations work
75+
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.checkMemberGroups")));
76+
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.checkMemberObjects")));
77+
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.getMemberGroups")));
78+
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.getMemberObjects")));
79+
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.restore")));
80+
81+
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.checkMemberGroups")));
82+
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.checkMemberObjects")));
83+
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.getMemberGroups")));
84+
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.getMemberObjects")));
85+
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.restore")));
7386
}
7487

7588
[Fact]
@@ -90,7 +103,7 @@ public void GetPathsForGraphBetaModelWithDerivedTypesConstraintReturnsAllPaths()
90103

91104
// Assert
92105
Assert.NotNull(paths);
93-
Assert.Equal(19060, paths.Count());
106+
Assert.Equal(18915, paths.Count());
94107
}
95108

96109
[Theory]

test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53506,6 +53506,11 @@
5350653506
<String>microsoft.graph.group</String>
5350753507
<String>microsoft.graph.application</String>
5350853508
</Collection>
53509+
</Annotation>
53510+
<Annotation Term="Org.OData.Core.V1.ExplicitOperationBindings">
53511+
<Collection>
53512+
<String>microsoft.graph.restore</String>
53513+
</Collection>
5350953514
</Annotation>
5351053515
</NavigationProperty>
5351153516
<NavigationProperty Name="federationConfigurations" Type="Collection(graph.identityProviderBase)" ContainsTarget="true">
@@ -103443,6 +103448,7 @@
103443103448
</Annotation>
103444103449
</Action>
103445103450
<Action Name="restore" IsBound="true">
103451+
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
103446103452
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
103447103453
<ReturnType Type="graph.directoryObject" />
103448103454
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
@@ -104558,6 +104564,7 @@
104558104564
<ReturnType Type="graph.exactMatchSession" />
104559104565
</Action>
104560104566
<Action Name="checkMemberGroups" IsBound="true">
104567+
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
104561104568
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
104562104569
<Parameter Name="groupIds" Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
104563104570
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
@@ -104577,11 +104584,13 @@
104577104584
</Annotation>
104578104585
</Action>
104579104586
<Action Name="checkMemberObjects" IsBound="true">
104587+
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
104580104588
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
104581104589
<Parameter Name="ids" Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
104582104590
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
104583104591
</Action>
104584104592
<Action Name="getMemberGroups" IsBound="true">
104593+
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
104585104594
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
104586104595
<Parameter Name="securityEnabledOnly" Type="Edm.Boolean" />
104587104596
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
@@ -104601,6 +104610,7 @@
104601104610
</Annotation>
104602104611
</Action>
104603104612
<Action Name="getMemberObjects" IsBound="true">
104613+
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
104604104614
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
104605104615
<Parameter Name="securityEnabledOnly" Type="Edm.Boolean" />
104606104616
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
@@ -108921,7 +108931,7 @@
108921108931
</Action>
108922108932
<Action Name="stop" IsBound="true">
108923108933
<Parameter Name="bindingParameter" Type="graph.accessReviewInstance" />
108924-
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
108934+
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
108925108935
<Record>
108926108936
<PropertyValue Property="Description" String="Stop accessReviewInstance" />
108927108937
<PropertyValue Property="LongDescription" String="Stop a currently active accessReviewInstance. After the access review instance stops, the instance status will be `Completed`, the reviewers can no longer give input, and the access review decisions can be applied. Stopping an instance will not effect future instances. To prevent a recurring access review from starting future instances, update the schedule definition to change its scheduled end date." />
@@ -109111,7 +109121,7 @@
109111109121
</Annotation>
109112109122
</Action>
109113109123
<Action Name="sendReminder" IsBound="true">
109114-
<Parameter Name="bindingParameter" Type="graph.accessReviewInstance" />
109124+
<Parameter Name="bindingParameter" Type="graph.accessReviewInstance" />
109115109125
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
109116109126
<Record>
109117109127
<PropertyValue Property="Description" String="accessReviewInstance: sendReminder" />
@@ -112362,7 +112372,17 @@
112362112372
</Term>
112363112373
<Term Name="teamCreationMode" Type="Edm.String" AppliesTo="microsoft.graph.team">
112364112374
<Annotation Term="Org.OData.Core.V1.LongDescription" String="Indicates that the team is in migration state and is currently being used for migration purposes. It accepts one value: migration. Note: In the future, Microsoft may require you or your customers to pay additional fees based on the amount of data imported." />
112365-
</Term>
112375+
</Term>
112376+
<Annotations Target="microsoft.graph.GraphService/directoryObjects">
112377+
<Annotation Term="Org.OData.Core.V1.ExplicitOperationBindings">
112378+
<Collection>
112379+
<String>microsoft.graph.checkMemberGroups</String>
112380+
<String>microsoft.graph.checkMemberObjects</String>
112381+
<String>microsoft.graph.getMemberGroups</String>
112382+
<String>microsoft.graph.getMemberObjects</String>
112383+
</Collection>
112384+
</Annotation>
112385+
</Annotations>
112366112386
<Annotations Target="microsoft.graph.user/drives">
112367112387
<Annotation Term="Org.OData.Capabilities.V1.CountRestrictions">
112368112388
<Record>

0 commit comments

Comments
 (0)