Skip to content

Commit 3b5767a

Browse files
irvinesundaybaywet
andauthored
Uses descriptions from CRUD restrictions annotations for OpenAPI operation descriptions (#178)
* Update retrieving descriptions for stream properties * Use restrictions annotations to retrieve operations descriptions * Update capturing of operation descriptions * Update tests; add further descriptions; remove invalid descriptions; fix bug * Update comment * Factor out common code into helper classes * Get navigation restrictions from nav. sources and in-lined nav. properties * Minor formatting changes * Remove description The description is basically just a description of the element and doesn't really add any more info. about the operation * Update src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Operation/SingletonPatchOperationHandler.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Operation/SingletonGetOperationHandler.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Operation/EntityUpdateOperationHandler.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetGetOperationHandler.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Operation/RefGetOperationHandler.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Minor refactoring * Get descriptions for complex properties Use CRUD restrictions annotations to get the descriptions * Update tests to validate retrieving descriptions for complex properties * Update src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Update src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs Co-authored-by: Vincent Biret <vibiret@microsoft.com> * Apply PR review suggestions Co-authored-by: Vincent Biret <vibiret@microsoft.com>
1 parent 64572d8 commit 3b5767a

File tree

59 files changed

+593
-341
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+593
-341
lines changed

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using Microsoft.OData.Edm;
1010
using Microsoft.OpenApi.Models;
11+
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
1112

1213
namespace Microsoft.OpenApi.OData.Common
1314
{
@@ -61,6 +62,28 @@ internal static OpenApiSchema GetDerivedTypesReferenceSchema(IEdmStructuredType
6162
};
6263

6364
return schema;
65+
}
66+
67+
/// <summary>
68+
/// Verifies whether the provided navigation restrictions allow for navigability of a navigation property.
69+
/// </summary>
70+
/// <param name="restrictionType">The <see cref="NavigationRestrictionsType"/>.</param>
71+
/// <param name="restrictionProperty">The <see cref="NavigationPropertyRestriction"/>.</param>
72+
/// <returns></returns>
73+
internal static bool NavigationRestrictionsAllowsNavigability(
74+
NavigationRestrictionsType restrictionType,
75+
NavigationPropertyRestriction restrictionProperty)
76+
{
77+
// Verify using individual navigation restriction first
78+
if (restrictionProperty?.Navigability != null && restrictionProperty.Navigability.Value == NavigationType.None)
79+
{
80+
return false;
81+
}
82+
83+
// if the individual has no navigability setting, use the global navigability setting
84+
// Default navigability for all navigation properties of the annotation target.
85+
// Individual navigation properties can override this value via `RestrictedProperties/Navigability`.
86+
return restrictionProperty?.Navigability != null || restrictionType == null || restrictionType.IsNavigable;
6487
}
6588
}
6689
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Collections.Generic;
88
using System.Linq;
9+
using Microsoft.OpenApi.OData.Edm;
910
using Microsoft.OpenApi.OData.Vocabulary;
1011

1112
namespace Microsoft.OpenApi.OData.Common
@@ -115,5 +116,19 @@ internal static string CheckArgumentNullOrEmpty(string value, string parameterNa
115116
/// <returns>The changed string.</returns>
116117
internal static string ToFirstCharacterLowerCase(this string input)
117118
=> string.IsNullOrEmpty(input) ? input : $"{char.ToLowerInvariant(input.FirstOrDefault())}{input.Substring(1)}";
119+
120+
/// <summary>
121+
/// Gets the navigation path.
122+
/// </summary>
123+
/// <param name="path">The <see cref="ODataPath"/>.</param>
124+
/// <param name="navigationPropertyName">Optional: The navigation property name.</param>
125+
internal static string NavigationPropertyPath(this ODataPath path, string navigationPropertyName = null)
126+
{
127+
string value = string.Join("/",
128+
path.Segments.Where(s => !(s is ODataKeySegment || s is ODataNavigationSourceSegment
129+
|| s is ODataStreamContentSegment || s is ODataStreamPropertySegment)).Select(e => e.Identifier));
130+
131+
return navigationPropertyName == null ? value : $"{value}/{navigationPropertyName}";
132+
}
118133
}
119134
}

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

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -360,36 +360,52 @@ private void RetrieveNavigationPropertyPaths(
360360

361361
if (visitedNavigationProperties == null)
362362
{
363-
visitedNavigationProperties = new ();
363+
visitedNavigationProperties = new();
364364
}
365-
366-
var navPropFullyQualifiedName = $"{navigationProperty.DeclaringType.FullTypeName()}/{navigationProperty.Name}";
367365

368-
// Check whether the navigation property has already been navigated in the path
366+
string navPropFullyQualifiedName = $"{navigationProperty.DeclaringType.FullTypeName()}/{navigationProperty.Name}";
367+
368+
// Check whether the navigation property has already been navigated in the path.
369369
if (visitedNavigationProperties.Contains(navPropFullyQualifiedName))
370370
{
371371
return;
372372
}
373373

374+
// Get the annotatable navigation source for this navigation property.
375+
IEdmVocabularyAnnotatable annotatableNavigationSource = (currentPath.FirstSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable;
376+
NavigationRestrictionsType navSourceRestrictionType = null;
377+
NavigationRestrictionsType navPropRestrictionType = null;
378+
379+
// Get the NavigationRestrictions referenced by this navigation property: Can be defined in the navigation source or in-lined in the navigation property.
380+
if (annotatableNavigationSource != null)
381+
{
382+
navSourceRestrictionType = _model.GetRecord<NavigationRestrictionsType>(annotatableNavigationSource, CapabilitiesConstants.NavigationRestrictions);
383+
navPropRestrictionType = _model.GetRecord<NavigationRestrictionsType>(navigationProperty, CapabilitiesConstants.NavigationRestrictions);
384+
}
385+
386+
NavigationPropertyRestriction restriction = navSourceRestrictionType?.RestrictedProperties?
387+
.FirstOrDefault(r => r.NavigationProperty == currentPath.NavigationPropertyPath(navigationProperty.Name))
388+
?? navPropRestrictionType?.RestrictedProperties?.FirstOrDefault();
389+
374390
// Check whether the navigation property should be part of the path
375-
NavigationRestrictionsType navigation = _model.GetRecord<NavigationRestrictionsType>(navigationProperty, CapabilitiesConstants.NavigationRestrictions);
376-
if (navigation != null && !navigation.IsNavigable)
391+
if (!EdmModelHelper.NavigationRestrictionsAllowsNavigability(navSourceRestrictionType, restriction) ||
392+
!EdmModelHelper.NavigationRestrictionsAllowsNavigability(navPropRestrictionType, restriction))
377393
{
378394
return;
379395
}
380396

381-
// Whether to expand the navigation property
397+
// Whether to expand the navigation property.
382398
bool shouldExpand = navigationProperty.ContainsTarget;
383399

384-
// append a navigation property.
400+
// Append a navigation property.
385401
currentPath.Push(new ODataNavigationPropertySegment(navigationProperty));
386402
AppendPath(currentPath.Clone());
387403
visitedNavigationProperties.Push(navPropFullyQualifiedName);
388404

389405
// Check whether a collection-valued navigation property should be indexed by key value(s).
390-
NavigationPropertyRestriction restriction = navigation?.RestrictedProperties?.FirstOrDefault();
406+
bool indexableByKey = restriction?.IndexableByKey ?? true;
391407

392-
if (restriction == null || restriction.IndexableByKey == true)
408+
if (indexableByKey)
393409
{
394410
IEdmEntityType navEntityType = navigationProperty.ToEntityType();
395411
var targetsMany = navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many;
@@ -409,7 +425,8 @@ private void RetrieveNavigationPropertyPaths(
409425
// ~/entityset/{key}/single-valued-Nav/subtype
410426
CreateTypeCastPaths(currentPath, convertSettings, navigationProperty.DeclaringType, navigationProperty, targetsMany);
411427

412-
if (navigation?.Referenceable == true)
428+
if ((navSourceRestrictionType?.Referenceable ?? false) ||
429+
(navPropRestrictionType?.Referenceable ?? false))
413430
{
414431
// Referenceable navigation properties
415432
// Single-Valued: ~/entityset/{key}/single-valued-Nav/$ref

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyGetOperationHandler.cs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ internal class ComplexPropertyGetOperationHandler : ComplexPropertyBaseOperation
1919
{
2020
/// <inheritdoc />
2121
public override OperationType OperationType => OperationType.Get;
22+
23+
/// <summary>
24+
/// Gets/Sets the <see cref="ReadRestrictionsType"/>
25+
/// </summary>
26+
private ReadRestrictionsType ReadRestrictions { get; set; }
27+
28+
protected override void Initialize(ODataContext context, ODataPath path)
29+
{
30+
base.Initialize(context, path);
31+
32+
ReadRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
33+
}
34+
2235
/// <inheritdoc/>
2336
protected override void SetBasicInfo(OpenApiOperation operation)
2437
{
@@ -33,6 +46,9 @@ protected override void SetBasicInfo(OpenApiOperation operation)
3346
operation.OperationId = ComplexPropertySegment.Property.Name + "." + typeName + listOrGet + Utils.UpperFirstChar(typeName);
3447
}
3548

49+
// Description
50+
operation.Description = ReadRestrictions?.Description ?? Context.Model.GetDescriptionAnnotation(ComplexPropertySegment.Property);
51+
3652
base.SetBasicInfo(operation);
3753
}
3854
/// <inheritdoc/>
@@ -192,31 +208,29 @@ private void SetSingleResponse(OpenApiOperation operation)
192208
}
193209
protected override void SetSecurity(OpenApiOperation operation)
194210
{
195-
ReadRestrictionsType read = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
196-
if (read == null || read.Permissions == null)
211+
if (ReadRestrictions?.Permissions == null)
197212
{
198213
return;
199214
}
200215

201-
operation.Security = Context.CreateSecurityRequirements(read.Permissions).ToList();
216+
operation.Security = Context.CreateSecurityRequirements(ReadRestrictions.Permissions).ToList();
202217
}
203218

204219
protected override void AppendCustomParameters(OpenApiOperation operation)
205-
{
206-
ReadRestrictionsType read = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
207-
if (read == null)
220+
{
221+
if (ReadRestrictions == null)
208222
{
209223
return;
210224
}
211225

212-
if (read.CustomHeaders != null)
226+
if (ReadRestrictions.CustomHeaders != null)
213227
{
214-
AppendCustomParameters(operation, read.CustomHeaders, ParameterLocation.Header);
228+
AppendCustomParameters(operation, ReadRestrictions.CustomHeaders, ParameterLocation.Header);
215229
}
216230

217-
if (read.CustomQueryOptions != null)
231+
if (ReadRestrictions.CustomQueryOptions != null)
218232
{
219-
AppendCustomParameters(operation, read.CustomQueryOptions, ParameterLocation.Query);
233+
AppendCustomParameters(operation, ReadRestrictions.CustomQueryOptions, ParameterLocation.Query);
220234
}
221235
}
222236
}

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyPostOperationHandler.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,17 @@ protected override void Initialize(ODataContext context, ODataPath path)
2525
{
2626
throw new InvalidOperationException("OData conventions do not support POSTing to a complex property that is not a collection.");
2727
}
28+
29+
InsertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
2830
}
2931
/// <inheritdoc />
3032
public override OperationType OperationType => OperationType.Post;
3133

34+
/// <summary>
35+
/// Gets/Sets the <see cref="InsertRestrictionsType"/>
36+
/// </summary>
37+
private InsertRestrictionsType InsertRestrictions { get; set; }
38+
3239
/// <inheritdoc/>
3340
protected override void SetBasicInfo(OpenApiOperation operation)
3441
{
@@ -42,6 +49,9 @@ protected override void SetBasicInfo(OpenApiOperation operation)
4249
operation.OperationId = ComplexPropertySegment.Property.Name + "." + typeName + ".Set" + Utils.UpperFirstChar(typeName);
4350
}
4451

52+
// Description
53+
operation.Description = InsertRestrictions?.Description;
54+
4555
base.SetBasicInfo(operation);
4656
}
4757

@@ -103,31 +113,29 @@ protected override void SetResponses(OpenApiOperation operation)
103113

104114
protected override void SetSecurity(OpenApiOperation operation)
105115
{
106-
InsertRestrictionsType insert = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
107-
if (insert == null || insert.Permissions == null)
116+
if (InsertRestrictions?.Permissions == null)
108117
{
109118
return;
110119
}
111120

112-
operation.Security = Context.CreateSecurityRequirements(insert.Permissions).ToList();
121+
operation.Security = Context.CreateSecurityRequirements(InsertRestrictions.Permissions).ToList();
113122
}
114123

115124
protected override void AppendCustomParameters(OpenApiOperation operation)
116125
{
117-
InsertRestrictionsType insert = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
118-
if (insert == null)
126+
if (InsertRestrictions == null)
119127
{
120128
return;
121129
}
122130

123-
if (insert.CustomQueryOptions != null)
131+
if (InsertRestrictions.CustomQueryOptions != null)
124132
{
125-
AppendCustomParameters(operation, insert.CustomQueryOptions, ParameterLocation.Query);
133+
AppendCustomParameters(operation, InsertRestrictions.CustomQueryOptions, ParameterLocation.Query);
126134
}
127135

128-
if (insert.CustomHeaders != null)
136+
if (InsertRestrictions.CustomHeaders != null)
129137
{
130-
AppendCustomParameters(operation, insert.CustomHeaders, ParameterLocation.Header);
138+
AppendCustomParameters(operation, InsertRestrictions.CustomHeaders, ParameterLocation.Header);
131139
}
132140
}
133141
}

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyUpdateOperationHandler.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,26 @@ namespace Microsoft.OpenApi.OData.Operation;
1616

1717
internal abstract class ComplexPropertyUpdateOperationHandler : ComplexPropertyBaseOperationHandler
1818
{
19+
/// <summary>
20+
/// Gets/Sets the <see cref="UpdateRestrictionsType"/>
21+
/// </summary>
22+
private UpdateRestrictionsType UpdateRestrictions { get; set; }
23+
24+
protected override void Initialize(ODataContext context, ODataPath path)
25+
{
26+
base.Initialize(context, path);
27+
28+
UpdateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
29+
}
30+
1931
/// <inheritdoc/>
2032
protected override void SetBasicInfo(OpenApiOperation operation)
2133
{
2234
// Summary
2335
operation.Summary = $"Update property {ComplexPropertySegment.Property.Name} value.";
2436

2537
// Description
26-
operation.Description = Context.Model.GetDescriptionAnnotation(ComplexPropertySegment.Property);
38+
operation.Description = UpdateRestrictions?.Description;
2739

2840
// OperationId
2941
if (Context.Settings.EnableOperationId)
@@ -87,31 +99,29 @@ protected override void SetResponses(OpenApiOperation operation)
8799
}
88100
protected override void SetSecurity(OpenApiOperation operation)
89101
{
90-
UpdateRestrictionsType update = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
91-
if (update == null || update.Permissions == null)
102+
if (UpdateRestrictions?.Permissions == null)
92103
{
93104
return;
94105
}
95106

96-
operation.Security = Context.CreateSecurityRequirements(update.Permissions).ToList();
107+
operation.Security = Context.CreateSecurityRequirements(UpdateRestrictions.Permissions).ToList();
97108
}
98109

99110
protected override void AppendCustomParameters(OpenApiOperation operation)
100111
{
101-
UpdateRestrictionsType update = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
102-
if (update == null)
112+
if (UpdateRestrictions == null)
103113
{
104114
return;
105115
}
106116

107-
if (update.CustomHeaders != null)
117+
if (UpdateRestrictions.CustomHeaders != null)
108118
{
109-
AppendCustomParameters(operation, update.CustomHeaders, ParameterLocation.Header);
119+
AppendCustomParameters(operation, UpdateRestrictions.CustomHeaders, ParameterLocation.Header);
110120
}
111121

112-
if (update.CustomQueryOptions != null)
122+
if (UpdateRestrictions.CustomQueryOptions != null)
113123
{
114-
AppendCustomParameters(operation, update.CustomQueryOptions, ParameterLocation.Query);
124+
AppendCustomParameters(operation, UpdateRestrictions.CustomQueryOptions, ParameterLocation.Query);
115125
}
116126
}
117127
}

0 commit comments

Comments
 (0)