Skip to content

Commit f8cef27

Browse files
Fetch annotations for paths ending with navigation properties using target path (#509)
* Fetch annotations for paths ending with navigation properties using target path * Allow fetching annotations using either path or navigation property as target * Update tests * Temporarily remove typecasts from TargetPath used for fetching annotations until we have a fix in the Edm library * Update release notes * Update Microsoft.OData.Edm library which now allows type casts in annotation target paths * Update tests * Merge annotations if some are defined out of line using TargetPath and other inline on the schema element * Update release notes
1 parent 8adfed4 commit f8cef27

File tree

48 files changed

+12609
-1369
lines changed

Some content is hidden

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

48 files changed

+12609
-1369
lines changed

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

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using System.Linq;
1010
using Microsoft.OData.Edm;
1111
using Microsoft.OData.Edm.Vocabularies;
12-
using Microsoft.OpenApi.Models;
1312
using Microsoft.OpenApi.OData.Common;
1413
using Microsoft.OpenApi.OData.Vocabulary;
1514
using Microsoft.OpenApi.OData.Vocabulary.Authorization;
@@ -163,6 +162,28 @@ public static T GetRecord<T>(this IEdmModel model, IEdmVocabularyAnnotatable tar
163162
});
164163
}
165164

165+
/// <summary>
166+
/// Gets the record value (a complex type) for the given target path.
167+
/// </summary>
168+
/// <typeparam name="T">The CLR mapping type.</typeparam>
169+
/// <param name="model">The Edm model.</param>
170+
/// <param name="targetPath">The string representation of the Edm target path.</param>
171+
/// <param name="qualifiedName">The Term qualified name.</param>
172+
/// <returns></returns>
173+
public static T GetRecord<T>(this IEdmModel model, string targetPath, string qualifiedName)
174+
where T : IRecord, new()
175+
{
176+
Utils.CheckArgumentNull(model, nameof(model));
177+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
178+
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
179+
180+
IEdmTargetPath target = model.GetTargetPath(targetPath);
181+
if (target == null)
182+
return default;
183+
184+
return model.GetRecord<T>(target, qualifiedName);
185+
}
186+
166187
/// <summary>
167188
/// Gets the collection of string term value for the given <see cref="IEdmVocabularyAnnotatable"/>.
168189
/// </summary>
@@ -265,12 +286,31 @@ public static IEnumerable<T> GetCollection<T>(this IEdmModel model, IEdmVocabula
265286
/// <param name="target">The Edm target.</param>
266287
/// <param name="linkRel">The link relation type for path operation.</param>
267288
/// <returns>Null or the links record value (a complex type) for this annotation.</returns>
268-
public static Link GetLinkRecord(this IEdmModel model, IEdmVocabularyAnnotatable target, string linkRel)
289+
public static LinkType GetLinkRecord(this IEdmModel model, IEdmVocabularyAnnotatable target, string linkRel)
269290
{
270291
Utils.CheckArgumentNull(model, nameof(model));
271292
Utils.CheckArgumentNull(target, nameof(target));
272293

273-
return model.GetCollection<Link>(target, CoreConstants.Links)?.FirstOrDefault(x => x.Rel == linkRel);
294+
return model.GetCollection<LinkType>(target, CoreConstants.Links)?.FirstOrDefault(x => x.Rel == linkRel);
295+
}
296+
297+
/// <summary>
298+
/// Gets the links record value (a complex type) for the given target path.
299+
/// </summary>
300+
/// <param name="model">The Edm model.</param>
301+
/// <param name="targetPath">The string representation of the Edm target path.</param>
302+
/// <param name="linkRel">The link relation type for path operation.</param>
303+
/// <returns>Null or the links record value (a complex type) for this annotation.</returns>
304+
public static LinkType GetLinkRecord(this IEdmModel model, string targetPath, string linkRel)
305+
{
306+
Utils.CheckArgumentNull(model, nameof(model));
307+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
308+
309+
IEdmTargetPath target = model.GetTargetPath(targetPath);
310+
if (target == null)
311+
return null;
312+
313+
return model.GetLinkRecord(target, linkRel);
274314
}
275315

276316
/// <summary>
@@ -311,6 +351,18 @@ public static IEnumerable<Authorization> GetAuthorizations(this IEdmModel model,
311351
});
312352
}
313353

354+
public static string GetDescriptionAnnotation(this IEdmModel model, string targetPath)
355+
{
356+
Utils.CheckArgumentNull(model, nameof(model));
357+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
358+
359+
IEdmTargetPath target = model.GetTargetPath(targetPath);
360+
if (target == null)
361+
return null;
362+
363+
return model.GetDescriptionAnnotation(target);
364+
}
365+
314366
private static T GetOrAddCached<T>(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName, Func<T> createFunc)
315367
{
316368
if (model == null || target == null)

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,24 @@ public static OpenApiParameter CreateTop(this ODataContext context, IEdmVocabula
372372
return null;
373373
}
374374

375+
/// <summary>
376+
/// Create the $top parameter for Edm target path.
377+
/// </summary>
378+
/// <param name="context">The OData context.</param>
379+
/// <param name="targetPath">The string representation of the Edm target path.</param>
380+
/// <returns></returns>
381+
public static OpenApiParameter CreateTop(this ODataContext context, string targetPath)
382+
{
383+
Utils.CheckArgumentNull(context, nameof(context));
384+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
385+
386+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
387+
if (target == null)
388+
return null;
389+
390+
return context.CreateTop(target);
391+
}
392+
375393
/// <summary>
376394
/// Create the $skip parameter.
377395
/// </summary>
@@ -396,6 +414,24 @@ public static OpenApiParameter CreateSkip(this ODataContext context, IEdmVocabul
396414
return null;
397415
}
398416

417+
/// <summary>
418+
/// Create the $skip parameter for Edm target path.
419+
/// </summary>
420+
/// <param name="context">The OData context.</param>
421+
/// <param name="targetPath">The string representation of the Edm target path.</param>
422+
/// <returns></returns>
423+
public static OpenApiParameter CreateSkip(this ODataContext context, string targetPath)
424+
{
425+
Utils.CheckArgumentNull(context, nameof(context));
426+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
427+
428+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
429+
if (target == null)
430+
return null;
431+
432+
return context.CreateSkip(target);
433+
}
434+
399435
/// <summary>
400436
/// Create the $search parameter.
401437
/// </summary>
@@ -420,6 +456,24 @@ public static OpenApiParameter CreateSearch(this ODataContext context, IEdmVocab
420456
return null;
421457
}
422458

459+
/// <summary>
460+
/// Create the $search parameter for Edm target path.
461+
/// </summary>
462+
/// <param name="context">The OData context.</param>
463+
/// <param name="targetPath">The string representation of the Edm target path.</param>
464+
/// <returns></returns>
465+
public static OpenApiParameter CreateSearch(this ODataContext context, string targetPath)
466+
{
467+
Utils.CheckArgumentNull(context, nameof(context));
468+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
469+
470+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
471+
if (target == null)
472+
return null;
473+
474+
return context.CreateSearch(target);
475+
}
476+
423477
/// <summary>
424478
/// Create the $count parameter.
425479
/// </summary>
@@ -444,6 +498,24 @@ public static OpenApiParameter CreateCount(this ODataContext context, IEdmVocabu
444498
return null;
445499
}
446500

501+
/// <summary>
502+
/// Create the $count parameter for Edm target path.
503+
/// </summary>
504+
/// <param name="context">The OData context.</param>
505+
/// <param name="targetPath">The string representation of the Edm target path.</param>
506+
/// <returns></returns>
507+
public static OpenApiParameter CreateCount(this ODataContext context, string targetPath)
508+
{
509+
Utils.CheckArgumentNull(context, nameof(context));
510+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
511+
512+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
513+
if (target == null)
514+
return null;
515+
516+
return context.CreateCount(target);
517+
}
518+
447519
/// <summary>
448520
/// Create the $filter parameter.
449521
/// </summary>
@@ -468,6 +540,24 @@ public static OpenApiParameter CreateFilter(this ODataContext context, IEdmVocab
468540
return null;
469541
}
470542

543+
/// <summary>
544+
/// Create the $filter parameter for Edm target path.
545+
/// </summary>
546+
/// <param name="context">The OData context.</param>
547+
/// <param name="targetPath">The string representation of the Edm target path.</param>
548+
/// <returns></returns>
549+
public static OpenApiParameter CreateFilter(this ODataContext context, string targetPath)
550+
{
551+
Utils.CheckArgumentNull(context, nameof(context));
552+
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
553+
554+
IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
555+
if (target == null)
556+
return null;
557+
558+
return context.CreateFilter(target);
559+
}
560+
471561
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmEntitySet entitySet)
472562
{
473563
Utils.CheckArgumentNull(context, nameof(context));

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
<TargetFrameworks>netstandard2.0</TargetFrameworks>
1616
<PackageId>Microsoft.OpenApi.OData</PackageId>
1717
<SignAssembly>true</SignAssembly>
18-
<Version>1.6.1</Version>
18+
<Version>1.6.2</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-
- Generates unique DELETE operation ids of $ref paths for indexed collection navigation properties #513
25-
</PackageReleaseNotes>
24+
- Adds support for fetching annotations using Edm target path preferentially #514
25+
</PackageReleaseNotes>
2626
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
2727
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
2828
<OutputPath Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">..\..\bin\Debug\</OutputPath>

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.OpenApi.Models;
88
using Microsoft.OpenApi.OData.Common;
99
using Microsoft.OpenApi.OData.Edm;
10+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1011

1112
namespace Microsoft.OpenApi.OData.Operation;
1213

@@ -17,6 +18,7 @@ internal abstract class ComplexPropertyBaseOperationHandler : OperationHandler
1718
/// <inheritdoc/>
1819
protected override void Initialize(ODataContext context, ODataPath path)
1920
{
21+
base.Initialize(context, path);
2022
ComplexPropertySegment = path.LastSegment as ODataComplexPropertySegment ?? throw Error.ArgumentNull(nameof(path));
2123
}
2224

@@ -40,4 +42,23 @@ protected override void SetTags(OpenApiOperation operation)
4042

4143
base.SetTags(operation);
4244
}
45+
46+
/// <inheritdoc/>
47+
protected override void SetExternalDocs(OpenApiOperation operation)
48+
{
49+
if (Context.Settings.ShowExternalDocs)
50+
{
51+
var externalDocs = Context.Model.GetLinkRecord(TargetPath, CustomLinkRel) ??
52+
Context.Model.GetLinkRecord(ComplexPropertySegment.Property, CustomLinkRel);
53+
54+
if (externalDocs != null)
55+
{
56+
operation.ExternalDocs = new OpenApiExternalDocs()
57+
{
58+
Description = CoreConstants.ExternalDocsDescription,
59+
Url = externalDocs.Href
60+
};
61+
}
62+
}
63+
}
4364
}

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ protected override void Initialize(ODataContext context, ODataPath path)
2626
{
2727
base.Initialize(context, path);
2828

29-
_readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
29+
_readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(TargetPath, CapabilitiesConstants.ReadRestrictions);
30+
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+
}
3040
}
3141

3242
/// <inheritdoc/>
@@ -54,41 +64,40 @@ protected override void SetParameters(OpenApiOperation operation)
5464
OpenApiParameter parameter;
5565
if(ComplexPropertySegment.Property.Type.IsCollection())
5666
{
57-
5867
// The parameters array contains Parameter Objects for all system query options allowed for this collection,
5968
// and it does not list system query options not allowed for this collection, see terms
6069
// Capabilities.TopSupported, Capabilities.SkipSupported, Capabilities.SearchRestrictions,
6170
// Capabilities.FilterRestrictions, and Capabilities.CountRestrictions
6271
// $top
63-
parameter = Context.CreateTop(ComplexPropertySegment.Property);
72+
parameter = Context.CreateTop(TargetPath) ?? Context.CreateTop(ComplexPropertySegment.Property);
6473
if (parameter != null)
6574
{
6675
operation.Parameters.Add(parameter);
6776
}
6877

6978
// $skip
70-
parameter = Context.CreateSkip(ComplexPropertySegment.Property);
79+
parameter = Context.CreateSkip(TargetPath) ?? Context.CreateSkip(ComplexPropertySegment.Property);
7180
if (parameter != null)
7281
{
7382
operation.Parameters.Add(parameter);
7483
}
7584

7685
// $search
77-
parameter = Context.CreateSearch(ComplexPropertySegment.Property);
86+
parameter = Context.CreateSearch(TargetPath) ?? Context.CreateSearch(ComplexPropertySegment.Property);
7887
if (parameter != null)
7988
{
8089
operation.Parameters.Add(parameter);
8190
}
8291

8392
// $filter
84-
parameter = Context.CreateFilter(ComplexPropertySegment.Property);
93+
parameter = Context.CreateFilter(TargetPath) ?? Context.CreateFilter(ComplexPropertySegment.Property);
8594
if (parameter != null)
8695
{
8796
operation.Parameters.Add(parameter);
8897
}
8998

9099
// $count
91-
parameter = Context.CreateCount(ComplexPropertySegment.Property);
100+
parameter = Context.CreateCount(TargetPath) ?? Context.CreateCount(ComplexPropertySegment.Property);
92101
if (parameter != null)
93102
{
94103
operation.Parameters.Add(parameter);

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ protected override void Initialize(ODataContext context, ODataPath path)
2626
throw new InvalidOperationException("OData conventions do not support POSTing to a complex property that is not a collection.");
2727
}
2828

29-
_insertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
29+
_insertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(TargetPath, CapabilitiesConstants.InsertRestrictions);
30+
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+
}
3040
}
3141
/// <inheritdoc />
3242
public override OperationType OperationType => OperationType.Post;

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,17 @@ protected override void Initialize(ODataContext context, ODataPath path)
2323
{
2424
base.Initialize(context, path);
2525

26-
_updateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
26+
_updateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(TargetPath, CapabilitiesConstants.UpdateRestrictions);
27+
var complexPropertyUpdateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
28+
29+
if (_updateRestrictions == null)
30+
{
31+
_updateRestrictions = complexPropertyUpdateRestrictions;
32+
}
33+
else
34+
{
35+
_updateRestrictions.MergePropertiesIfNull(complexPropertyUpdateRestrictions);
36+
}
2737
}
2838

2939
/// <inheritdoc/>

0 commit comments

Comments
 (0)