Skip to content

Commit 41929b5

Browse files
Use target path annotations to determine whether a path item should be generated (#533)
* Use target path annotations to determine whether a path item should be added * Reduce cognitive complexity * Minor updates for code quality * Add tests * Bump version * Add more tests
1 parent 18883fe commit 41929b5

40 files changed

+568
-382
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ internal static class EdmVocabularyAnnotationExtensions
6767
});
6868
}
6969

70+
public static bool? GetBoolean(this IEdmModel model, string targetPath, string qualifiedName)
71+
{
72+
Utils.CheckArgumentNull(model, nameof(model));
73+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
74+
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
75+
76+
IEdmTargetPath target = model.GetTargetPath(targetPath);
77+
if (target == null)
78+
return default;
79+
80+
return model.GetBoolean(target, qualifiedName);
81+
}
82+
7083
/// <summary>
7184
/// Gets the string term value for the given <see cref="IEdmVocabularyAnnotatable"/>.
7285
/// </summary>

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ public string GetPathItemName(OpenApiConvertSettings settings)
145145
Utils.CheckArgumentNull(settings, nameof(settings));
146146

147147
// From Open API spec, parameter name is case sensitive, so don't use the IgnoreCase HashSet.
148-
// HashSet<string> parameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
149148
HashSet<string> parameters = new();
150149
StringBuilder sb = new();
151150

@@ -238,7 +237,6 @@ internal IDictionary<ODataSegment, IDictionary<string, string>> CalculateParamet
238237
IDictionary<ODataSegment, IDictionary<string, string>> parameterMapping = new Dictionary<ODataSegment, IDictionary<string, string>>();
239238

240239
// From Open API spec, parameter name is case sensitive, so don't use the IgnoreCase HashSet.
241-
// HashSet<string> parameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
242240
HashSet<string> parameters = new HashSet<string>();
243241

244242
foreach (var segment in Segments)
@@ -259,6 +257,28 @@ internal IDictionary<ODataSegment, IDictionary<string, string>> CalculateParamet
259257
return parameterMapping;
260258
}
261259

260+
/// <summary>
261+
/// Get string representation of the Edm Target Path for annotations
262+
/// </summary>
263+
/// <param name="model">The Edm model.</param>
264+
/// <returns>The string representation of the Edm target path.</returns>
265+
internal string GetTargetPath(IEdmModel model)
266+
{
267+
Utils.CheckArgumentNull(model, nameof(model));
268+
269+
var targetPath = new StringBuilder(model.EntityContainer.FullName());
270+
271+
bool skipLastSegment = LastSegment is ODataRefSegment
272+
|| LastSegment is ODataDollarCountSegment
273+
|| LastSegment is ODataStreamContentSegment;
274+
foreach (var segment in Segments.Where(segment => segment is not ODataKeySegment
275+
&& !(skipLastSegment && segment == LastSegment)))
276+
{
277+
targetPath.Append($"/{segment.Identifier}");
278+
}
279+
return targetPath.ToString();
280+
}
281+
262282
/// <summary>
263283
/// Output the path string.
264284
/// </summary>

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

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,9 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource
249249
// for entity set, create a path with key and a $count path
250250
if (entitySet != null)
251251
{
252-
count = _model.GetRecord<CountRestrictionsType>(entitySet, CapabilitiesConstants.CountRestrictions);
252+
string targetPath = path.GetTargetPath(_model);
253+
count = _model.GetRecord<CountRestrictionsType>(targetPath, CapabilitiesConstants.CountRestrictions)
254+
?? _model.GetRecord<CountRestrictionsType>(entitySet, CapabilitiesConstants.CountRestrictions);
253255
if(count?.Countable ?? true) // ~/entitySet/$count
254256
CreateCountPath(path, convertSettings);
255257

@@ -310,16 +312,22 @@ private void RetrieveComplexPropertyPaths(IEdmEntityType entityType, ODataPath c
310312
.Where(x => x.Type.IsComplex() ||
311313
x.Type.IsCollection() && x.Type.Definition.AsElementType() is IEdmComplexType))
312314
{
313-
if (!ShouldCreateComplexPropertyPaths(sp, convertSettings)) continue;
314-
315315
currentPath.Push(new ODataComplexPropertySegment(sp));
316+
var targetPath = currentPath.GetTargetPath(_model);
317+
if (!ShouldCreateComplexPropertyPaths(sp, targetPath, convertSettings))
318+
{
319+
currentPath.Pop();
320+
continue;
321+
}
316322
AppendPath(currentPath.Clone());
317323

318324
if (sp.Type.IsCollection())
319325
{
320326
CreateTypeCastPaths(currentPath, convertSettings, sp.Type.Definition.AsElementType() as IEdmComplexType, sp, true);
321-
var count = _model.GetRecord<CountRestrictionsType>(sp, CapabilitiesConstants.CountRestrictions);
322-
if (count?.IsCountable ?? true)
327+
var isCountable = _model.GetRecord<CountRestrictionsType>(targetPath, CapabilitiesConstants.CountRestrictions)?.IsCountable
328+
?? _model.GetRecord<CountRestrictionsType>(sp, CapabilitiesConstants.CountRestrictions)?.IsCountable
329+
?? true;
330+
if (isCountable)
323331
CreateCountPath(currentPath, convertSettings);
324332
}
325333
else
@@ -363,7 +371,9 @@ private bool RetrieveComplexTypeNavigationPropertyPaths(IEdmComplexType complexT
363371

364372
foreach (var np in navigationProperties)
365373
{
366-
var count = _model.GetRecord<CountRestrictionsType>(np, CapabilitiesConstants.CountRestrictions);
374+
var targetPath = currentPath.GetTargetPath(_model);
375+
var count = _model.GetRecord<CountRestrictionsType>(targetPath, CapabilitiesConstants.CountRestrictions)
376+
?? _model.GetRecord<CountRestrictionsType>(np, CapabilitiesConstants.CountRestrictions);
367377
RetrieveNavigationPropertyPaths(np, count, currentPath, convertSettings);
368378
}
369379

@@ -407,19 +417,26 @@ private void TraverseComplexProperty(IEdmStructuralProperty structuralProperty,
407417
/// Evaluates whether or not to create paths for complex properties.
408418
/// </summary>
409419
/// <param name="complexProperty">The target complex property.</param>
420+
/// <param name="targetPath">The annotation target path for the complex property.</param>
410421
/// <param name="convertSettings">The settings for the current conversion.</param>
411422
/// <returns>true or false.</returns>
412-
private bool ShouldCreateComplexPropertyPaths(IEdmStructuralProperty complexProperty, OpenApiConvertSettings convertSettings)
423+
private bool ShouldCreateComplexPropertyPaths(IEdmStructuralProperty complexProperty, string targetPath, OpenApiConvertSettings convertSettings)
413424
{
414425
Utils.CheckArgumentNull(complexProperty, nameof(complexProperty));
415426
Utils.CheckArgumentNull(convertSettings, nameof(convertSettings));
416427

417428
if (!convertSettings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths)
418429
return true;
419430

420-
bool isReadable = _model.GetRecord<ReadRestrictionsType>(complexProperty, CapabilitiesConstants.ReadRestrictions)?.Readable ?? false;
421-
bool isUpdatable = _model.GetRecord<UpdateRestrictionsType>(complexProperty, CapabilitiesConstants.UpdateRestrictions)?.Updatable ?? false;
422-
bool isInsertable = _model.GetRecord<InsertRestrictionsType>(complexProperty, CapabilitiesConstants.InsertRestrictions)?.Insertable ?? false;
431+
bool isReadable = _model.GetRecord<ReadRestrictionsType>(targetPath, CapabilitiesConstants.ReadRestrictions)?.Readable
432+
?? _model.GetRecord<ReadRestrictionsType>(complexProperty, CapabilitiesConstants.ReadRestrictions)?.Readable
433+
?? false;
434+
bool isUpdatable = _model.GetRecord<UpdateRestrictionsType>(targetPath, CapabilitiesConstants.UpdateRestrictions)?.Updatable
435+
??_model.GetRecord<UpdateRestrictionsType>(complexProperty, CapabilitiesConstants.UpdateRestrictions)?.Updatable
436+
?? false;
437+
bool isInsertable = _model.GetRecord<InsertRestrictionsType>(targetPath, CapabilitiesConstants.InsertRestrictions)?.Insertable
438+
?? _model.GetRecord<InsertRestrictionsType>(complexProperty, CapabilitiesConstants.InsertRestrictions)?.Insertable
439+
?? false;
423440

424441
return isReadable || isUpdatable || isInsertable;
425442
}
@@ -525,9 +542,13 @@ private void RetrieveNavigationPropertyPaths(
525542
AppendPath(currentPath.Clone());
526543
visitedNavigationProperties.Push(navPropFullyQualifiedName);
527544

545+
// For fetching annotations
546+
var targetPath = currentPath.GetTargetPath(_model);
547+
528548
// Check whether a collection-valued navigation property should be indexed by key value(s).
529549
// Find indexability annotation annotated directly via NavigationPropertyRestriction.
530-
bool? annotatedIndexability = _model.GetBoolean(navigationProperty, CapabilitiesConstants.IndexableByKey);
550+
bool? annotatedIndexability = _model.GetBoolean(targetPath, CapabilitiesConstants.IndexableByKey)
551+
?? _model.GetBoolean(navigationProperty, CapabilitiesConstants.IndexableByKey);
531552
bool indexableByKey = true;
532553

533554
if (restriction?.IndexableByKey != null)
@@ -550,7 +571,8 @@ private void RetrieveNavigationPropertyPaths(
550571
if (count == null)
551572
{
552573
// First, get the directly annotated restriction annotation of the navigation property
553-
count = _model.GetRecord<CountRestrictionsType>(navigationProperty, CapabilitiesConstants.CountRestrictions);
574+
count = _model.GetRecord<CountRestrictionsType>(targetPath, CapabilitiesConstants.CountRestrictions)
575+
?? _model.GetRecord<CountRestrictionsType>(navigationProperty, CapabilitiesConstants.CountRestrictions);
554576
createCountPath = count?.Countable;
555577
}
556578

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,6 @@ public static OpenApiParameter CreateSearch(this ODataContext context, IEdmVocab
455455

456456
return null;
457457
}
458-
459458
/// <summary>
460459
/// Create the $search parameter for Edm target path.
461460
/// </summary>
@@ -558,6 +557,18 @@ public static OpenApiParameter CreateFilter(this ODataContext context, string ta
558557
return context.CreateFilter(target);
559558
}
560559

560+
public static OpenApiParameter CreateOrderBy(this ODataContext context, string targetPath, IEdmEntityType entityType)
561+
{
562+
Utils.CheckArgumentNull(context, nameof(context));
563+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
564+
565+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
566+
if (target == null)
567+
return null;
568+
569+
return context.CreateOrderBy(target, entityType);
570+
}
571+
561572
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmEntitySet entitySet)
562573
{
563574
Utils.CheckArgumentNull(context, nameof(context));
@@ -592,7 +603,17 @@ public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmNavi
592603
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmEntityType entityType)
593604
{// patchwork to avoid breaking changes
594605
return context.CreateOrderBy(target, entityType as IEdmStructuredType);
606+
}
607+
608+
public static OpenApiParameter CreateOrderBy(this ODataContext context, string targetPath, IEdmStructuredType structuredType)
609+
{
610+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
611+
if (target == null)
612+
return null;
613+
614+
return context.CreateOrderBy(target, structuredType);
595615
}
616+
596617
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmStructuredType structuredType)
597618
{
598619
Utils.CheckArgumentNull(context, nameof(context));
@@ -653,6 +674,18 @@ public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmVoca
653674
};
654675
}
655676

677+
public static OpenApiParameter CreateSelect(this ODataContext context, string targetPath, IEdmEntityType entityType)
678+
{
679+
Utils.CheckArgumentNull(context, nameof(context));
680+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
681+
682+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
683+
if (target == null)
684+
return null;
685+
686+
return context.CreateSelect(target, entityType);
687+
}
688+
656689
public static OpenApiParameter CreateSelect(this ODataContext context, IEdmEntitySet entitySet)
657690
{
658691
Utils.CheckArgumentNull(context, nameof(context));
@@ -688,6 +721,16 @@ public static OpenApiParameter CreateSelect(this ODataContext context, IEdmVocab
688721
{ // patchwork to avoid breaking changes
689722
return context.CreateSelect(target, entityType as IEdmStructuredType);
690723
}
724+
725+
public static OpenApiParameter CreateSelect(this ODataContext context, string targetPath, IEdmStructuredType structuredType)
726+
{
727+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
728+
if (target == null)
729+
return null;
730+
731+
return context.CreateSelect(target, structuredType);
732+
}
733+
691734
public static OpenApiParameter CreateSelect(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmStructuredType structuredType)
692735
{
693736
Utils.CheckArgumentNull(context, nameof(context));
@@ -736,6 +779,19 @@ public static OpenApiParameter CreateSelect(this ODataContext context, IEdmVocab
736779
Explode = false
737780
};
738781
}
782+
783+
public static OpenApiParameter CreateExpand(this ODataContext context, string targetPath, IEdmEntityType entityType)
784+
{
785+
Utils.CheckArgumentNull(context, nameof(context));
786+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
787+
788+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
789+
if (target == null)
790+
return null;
791+
792+
return context.CreateExpand(target, entityType);
793+
}
794+
739795
public static OpenApiParameter CreateExpand(this ODataContext context, IEdmEntitySet entitySet)
740796
{
741797
Utils.CheckArgumentNull(context, nameof(context));
@@ -771,6 +827,16 @@ public static OpenApiParameter CreateExpand(this ODataContext context, IEdmVocab
771827
{ // patchwork to avoid breaking changes
772828
return context.CreateExpand(target, entityType as IEdmStructuredType);
773829
}
830+
831+
public static OpenApiParameter CreateExpand(this ODataContext context, string targetPath, IEdmStructuredType structuredType)
832+
{
833+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
834+
if (target == null)
835+
return null;
836+
837+
return context.CreateExpand(target, structuredType);
838+
}
839+
774840
public static OpenApiParameter CreateExpand(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmStructuredType structuredType)
775841
{
776842
Utils.CheckArgumentNull(context, nameof(context));

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
<TargetFrameworks>netstandard2.0</TargetFrameworks>
1616
<PackageId>Microsoft.OpenApi.OData</PackageId>
1717
<SignAssembly>true</SignAssembly>
18-
<Version>1.6.4</Version>
18+
<Version>1.6.5</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>
2222
<RepositoryUrl>https://github.com/Microsoft/OpenAPI.NET.OData</RepositoryUrl>
2323
<PackageReleaseNotes>
24-
- Add support for Edm.Untyped #511
24+
- Use annotations with path as target to dermine whether to add an operation #535
2525
</PackageReleaseNotes>
2626
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
2727
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>

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

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,8 @@ protected override void Initialize(ODataContext context, ODataPath path)
2828

2929
_readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(TargetPath, CapabilitiesConstants.ReadRestrictions);
3030
var complexPropertyReadRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
31-
32-
if (_readRestrictions == null)
33-
{
34-
_readRestrictions = complexPropertyReadRestrictions;
35-
}
36-
else
37-
{
38-
_readRestrictions.MergePropertiesIfNull(complexPropertyReadRestrictions);
39-
}
31+
_readRestrictions?.MergePropertiesIfNull(complexPropertyReadRestrictions);
32+
_readRestrictions ??= complexPropertyReadRestrictions;
4033
}
4134

4235
/// <inheritdoc/>
@@ -108,22 +101,25 @@ protected override void SetParameters(OpenApiOperation operation)
108101
// of just providing a comma-separated list of properties can be expressed via an array-valued
109102
// parameter with an enum constraint
110103
// $order
111-
parameter = Context.CreateOrderBy(ComplexPropertySegment.Property, ComplexPropertySegment.ComplexType);
104+
parameter = Context.CreateOrderBy(TargetPath, ComplexPropertySegment.ComplexType)
105+
?? Context.CreateOrderBy(ComplexPropertySegment.Property, ComplexPropertySegment.ComplexType);
112106
if (parameter != null)
113107
{
114108
operation.Parameters.Add(parameter);
115109
}
116110
}
117111

118112
// $select
119-
parameter = Context.CreateSelect(ComplexPropertySegment.Property, ComplexPropertySegment.ComplexType);
113+
parameter = Context.CreateSelect(TargetPath, ComplexPropertySegment.ComplexType)
114+
?? Context.CreateSelect(ComplexPropertySegment.Property, ComplexPropertySegment.ComplexType);
120115
if (parameter != null)
121116
{
122117
operation.Parameters.Add(parameter);
123118
}
124119

125120
// $expand
126-
parameter = Context.CreateExpand(ComplexPropertySegment.Property, ComplexPropertySegment.ComplexType);
121+
parameter = Context.CreateExpand(TargetPath, ComplexPropertySegment.ComplexType)
122+
?? Context.CreateExpand(ComplexPropertySegment.Property, ComplexPropertySegment.ComplexType);
127123
if (parameter != null)
128124
{
129125
operation.Parameters.Add(parameter);

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

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,8 @@ protected override void Initialize(ODataContext context, ODataPath path)
2828

2929
_insertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(TargetPath, CapabilitiesConstants.InsertRestrictions);
3030
var complexPropertyInsertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
31-
32-
if (_insertRestrictions == null)
33-
{
34-
_insertRestrictions = complexPropertyInsertRestrictions;
35-
}
36-
else
37-
{
38-
_insertRestrictions.MergePropertiesIfNull(complexPropertyInsertRestrictions);
39-
}
31+
_insertRestrictions?.MergePropertiesIfNull(complexPropertyInsertRestrictions);
32+
_insertRestrictions ??= complexPropertyInsertRestrictions;
4033
}
4134
/// <inheritdoc />
4235
public override OperationType OperationType => OperationType.Post;

0 commit comments

Comments
 (0)