Skip to content

Commit 7439ab5

Browse files
Add the documentation links from Links annotations in CSDL to OpenAPI Operations (#260)
* Add configuration for showing links to external documentation for operations * Add Links to Core vocabularies * Add Core vocabulary constants * Add SetExternalDocs method to OperationHandler * Update LinksType vocabulary * Add description from CSDL for action and function operations * Add extension method for getting external docs * Select link to add to operation based on operation type and target type
1 parent 9865f1c commit 7439ab5

16 files changed

+343
-12
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
using System.Linq;
1010
using Microsoft.OData.Edm;
1111
using Microsoft.OData.Edm.Vocabularies;
12+
using Microsoft.OpenApi.Models;
1213
using Microsoft.OpenApi.OData.Common;
1314
using Microsoft.OpenApi.OData.Vocabulary;
1415
using Microsoft.OpenApi.OData.Vocabulary.Authorization;
16+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1517

1618
namespace Microsoft.OpenApi.OData.Edm
1719
{
@@ -256,6 +258,21 @@ public static IEnumerable<T> GetCollection<T>(this IEdmModel model, IEdmVocabula
256258
});
257259
}
258260

261+
/// <summary>
262+
/// Gets the links record value (a complex type) for the given <see cref="IEdmVocabularyAnnotatable"/>.
263+
/// </summary>
264+
/// <param name="model">The Edm model.</param>
265+
/// <param name="target">The Edm target.</param>
266+
/// <param name="linkRel">The link relation type for path operation.</param>
267+
/// <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)
269+
{
270+
Utils.CheckArgumentNull(model, nameof(model));
271+
Utils.CheckArgumentNull(target, nameof(target));
272+
273+
return model.GetCollection<Link>(target, CoreConstants.Links)?.FirstOrDefault(x => x.Rel == linkRel);
274+
}
275+
259276
/// <summary>
260277
/// Create the corresponding Authorization object.
261278
/// </summary>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ internal IEnumerable<DeprecatedRevisionsType> GetDeprecationInformations(IEdmVoc
177177
{
178178
return annotable == null ?
179179
Enumerable.Empty<DeprecatedRevisionsType>() :
180-
(Model?.GetCollection<DeprecatedRevisionsType>(annotable, "Org.OData.Core.V1.Revisions") ??
180+
(Model?.GetCollection<DeprecatedRevisionsType>(annotable, CoreConstants.Revisions) ??
181181
Enumerable.Empty<DeprecatedRevisionsType>());
182182
}
183183
}

src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.OpenApi.OData.Common;
99
using Microsoft.OpenApi.OData.Edm;
1010
using Microsoft.OpenApi.OData.Extensions;
11+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1112

1213
namespace Microsoft.OpenApi.OData
1314
{
@@ -179,6 +180,11 @@ public string PathPrefix
179180
/// </summary>
180181
public bool ShowMsDosGroupPath { get; set; } = true;
181182

183+
/// <summary>
184+
/// Gets/sets links to external documentation for operations
185+
/// </summary>
186+
public bool ShowExternalDocs { get; set; } = true;
187+
182188
/// <summary>
183189
/// Gets/sets a the path provider.
184190
/// </summary>
@@ -259,6 +265,20 @@ public string PathPrefix
259265
/// </summary>
260266
public bool UseSuccessStatusCodeRange { get; set; } = false;
261267

268+
/// <summary>
269+
/// Get/Sets a dictionary containing a mapping of HTTP methods to custom link relation types
270+
/// </summary>
271+
public Dictionary<LinkRelKey, string> CustomHttpMethodLinkRelMapping { get; set; } = new()
272+
{
273+
{ LinkRelKey.List, "https://graph.microsoft.com/rels/docs/list" },
274+
{ LinkRelKey.ReadByKey, "https://graph.microsoft.com/rels/docs/get" },
275+
{ LinkRelKey.Create, "https://graph.microsoft.com/rels/docs/create" },
276+
{ LinkRelKey.Update, "https://graph.microsoft.com/rels/docs/update" },
277+
{ LinkRelKey.Delete, "https://graph.microsoft.com/rels/docs/delete" },
278+
{ LinkRelKey.Action, "https://graph.microsoft.com/rels/docs/action" },
279+
{ LinkRelKey.Function, "https://graph.microsoft.com/rels/docs/function" }
280+
};
281+
262282
internal OpenApiConvertSettings Clone()
263283
{
264284
var newSettings = new OpenApiConvertSettings
@@ -288,6 +308,7 @@ internal OpenApiConvertSettings Clone()
288308
RequireDerivedTypesConstraintForBoundOperations = this.RequireDerivedTypesConstraintForBoundOperations,
289309
ShowSchemaExamples = this.ShowSchemaExamples,
290310
ShowRootPath = this.ShowRootPath,
311+
ShowExternalDocs = this.ShowExternalDocs,
291312
PathProvider = this.PathProvider,
292313
EnableDollarCountPath = this.EnableDollarCountPath,
293314
AddSingleQuotesForStringParameters = this.AddSingleQuotesForStringParameters,
@@ -300,6 +321,7 @@ internal OpenApiConvertSettings Clone()
300321
RequireRestrictionAnnotationsToGenerateComplexPropertyPaths = this.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths,
301322
ExpandDerivedTypesNavigationProperties = this.ExpandDerivedTypesNavigationProperties,
302323
CustomXMLAttributesMapping = this.CustomXMLAttributesMapping,
324+
CustomHttpMethodLinkRelMapping = this.CustomHttpMethodLinkRelMapping,
303325
AppendBoundOperationsOnDerivedTypeCastSegments = this.AppendBoundOperationsOnDerivedTypeCastSegments,
304326
UseSuccessStatusCodeRange = this.UseSuccessStatusCodeRange
305327
};

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using Microsoft.OpenApi.Any;
88
using Microsoft.OpenApi.Models;
99
using Microsoft.OpenApi.OData.Common;
10+
using Microsoft.OpenApi.OData.Edm;
1011
using Microsoft.OpenApi.OData.Generator;
12+
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
1113

1214
namespace Microsoft.OpenApi.OData.Operation
1315
{
@@ -19,6 +21,19 @@ internal class EdmActionOperationHandler : EdmOperationOperationHandler
1921
/// <inheritdoc/>
2022
public override OperationType OperationType => OperationType.Post;
2123

24+
/// <inheritdoc/>
25+
protected override void SetBasicInfo(OpenApiOperation operation)
26+
{
27+
base.SetBasicInfo(operation);
28+
29+
// Description
30+
var insertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(EdmOperation, CapabilitiesConstants.InsertRestrictions);
31+
if (!string.IsNullOrWhiteSpace(insertRestrictions?.LongDescription))
32+
{
33+
operation.Description = insertRestrictions.LongDescription;
34+
}
35+
}
36+
2237
/// <inheritdoc/>
2338
protected override void SetRequestBody(OpenApiOperation operation)
2439
{

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using Microsoft.OpenApi.Any;
88
using Microsoft.OpenApi.Models;
99
using Microsoft.OpenApi.OData.Common;
10+
using Microsoft.OpenApi.OData.Edm;
11+
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
1012

1113
namespace Microsoft.OpenApi.OData.Operation
1214
{
@@ -23,6 +25,19 @@ internal class EdmFunctionOperationHandler : EdmOperationOperationHandler
2325
/// </summary>
2426
public IEdmFunction Function => EdmOperation as IEdmFunction;
2527

28+
/// <inheritdoc/>
29+
protected override void SetBasicInfo(OpenApiOperation operation)
30+
{
31+
base.SetBasicInfo(operation);
32+
33+
// Description
34+
var readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(EdmOperation, CapabilitiesConstants.ReadRestrictions);
35+
if (!string.IsNullOrWhiteSpace(readRestrictions?.LongDescription))
36+
{
37+
operation.Description = readRestrictions.LongDescription;
38+
}
39+
}
40+
2641
/// <inheritdoc/>
2742
protected override void SetExtensions(OpenApiOperation operation)
2843
{

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.OpenApi.OData.Edm;
1414
using Microsoft.OpenApi.OData.Generator;
1515
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
16+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1617

1718
namespace Microsoft.OpenApi.OData.Operation
1819
{
@@ -152,5 +153,18 @@ internal static string PathAsString(IEnumerable<string> path)
152153
{
153154
return String.Join("/", path);
154155
}
156+
157+
/// <inheritdoc/>
158+
protected override void SetExternalDocs(OpenApiOperation operation)
159+
{
160+
if (Context.Settings.ShowExternalDocs && Context.Model.GetLinkRecord(EdmOperationImport, CustomLinkRel) is Link externalDocs)
161+
{
162+
operation.ExternalDocs = new OpenApiExternalDocs()
163+
{
164+
Description = CoreConstants.ExternalDocsDescription,
165+
Url = externalDocs.Href
166+
};
167+
}
168+
}
155169
}
156170
}

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.OpenApi.OData.Edm;
1313
using Microsoft.OpenApi.OData.Generator;
1414
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
15+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1516

1617
namespace Microsoft.OpenApi.OData.Operation
1718
{
@@ -42,9 +43,7 @@ internal abstract class EdmOperationOperationHandler : OperationHandler
4243

4344
/// <inheritdoc/>
4445
protected override void Initialize(ODataContext context, ODataPath path)
45-
{
46-
base.Initialize(context, path);
47-
46+
{
4847
// It's bound operation, the first segment must be the navigaiton source.
4948
ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment;
5049
NavigationSource = navigationSourceSegment.NavigationSource;
@@ -53,6 +52,8 @@ protected override void Initialize(ODataContext context, ODataPath path)
5352
EdmOperation = OperationSegment.Operation;
5453

5554
HasTypeCast = path.Segments.Any(s => s is ODataTypeCastSegment);
55+
56+
base.Initialize(context, path);
5657
}
5758

5859
/// <inheritdoc/>
@@ -198,5 +199,30 @@ protected override void AppendCustomParameters(OpenApiOperation operation)
198199
AppendCustomParameters(operation, restriction.CustomQueryOptions, ParameterLocation.Query);
199200
}
200201
}
202+
203+
/// <inheritdoc/>
204+
protected override void SetCustomLinkRelType()
205+
{
206+
if (Context.Settings.CustomHttpMethodLinkRelMapping != null && EdmOperation != null)
207+
{
208+
LinkRelKey key = EdmOperation.IsAction() ? LinkRelKey.Action : LinkRelKey.Function;
209+
Context.Settings.CustomHttpMethodLinkRelMapping.TryGetValue(key, out string linkRelValue);
210+
CustomLinkRel = linkRelValue;
211+
}
212+
}
213+
214+
215+
/// <inheritdoc/>
216+
protected override void SetExternalDocs(OpenApiOperation operation)
217+
{
218+
if (Context.Settings.ShowExternalDocs && Context.Model.GetLinkRecord(EdmOperation, CustomLinkRel) is Link externalDocs)
219+
{
220+
operation.ExternalDocs = new OpenApiExternalDocs()
221+
{
222+
Description = CoreConstants.ExternalDocsDescription,
223+
Url = externalDocs.Href
224+
};
225+
}
226+
}
201227
}
202228
}

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

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

1213
namespace Microsoft.OpenApi.OData.Operation
1314
{
@@ -56,5 +57,18 @@ protected override void SetExtensions(OpenApiOperation operation)
5657

5758
base.SetExtensions(operation);
5859
}
60+
61+
/// <inheritdoc/>
62+
protected override void SetExternalDocs(OpenApiOperation operation)
63+
{
64+
if (Context.Settings.ShowExternalDocs && Context.Model.GetLinkRecord(EntitySet, CustomLinkRel) is Link externalDocs)
65+
{
66+
operation.ExternalDocs = new OpenApiExternalDocs()
67+
{
68+
Description = CoreConstants.ExternalDocsDescription,
69+
Url = externalDocs.Href
70+
};
71+
}
72+
}
5973
}
6074
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.OpenApi.OData.Common;
1111
using Microsoft.OpenApi.OData.Edm;
1212
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
13+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1314
using System.Collections.Generic;
1415
using System.Linq;
1516

@@ -24,7 +25,7 @@ internal abstract class MediaEntityOperationalHandler : NavigationPropertyOperat
2425
/// Gets/Sets the NavigationSource segment
2526
/// </summary>
2627
protected ODataNavigationSourceSegment NavigationSourceSegment { get; private set; }
27-
28+
2829
/// <summary>
2930
/// Gets/Sets flag indicating whether path is navigation property path
3031
/// </summary>
@@ -204,5 +205,18 @@ private IEdmNavigationProperty GetNavigationProperty(IEdmEntityType entityType,
204205
{
205206
return entityType.DeclaredNavigationProperties().FirstOrDefault(x => x.Name.Equals(identifier));
206207
}
208+
209+
protected override void SetExternalDocs(OpenApiOperation operation)
210+
{
211+
if (Context.Settings.ShowExternalDocs && IsNavigationPropertyPath &&
212+
Context.Model.GetLinkRecord(NavigationProperty, CustomLinkRel) is Link externalDocs)
213+
{
214+
operation.ExternalDocs = new OpenApiExternalDocs()
215+
{
216+
Description = CoreConstants.ExternalDocsDescription,
217+
Url = externalDocs.Href
218+
};
219+
}
220+
}
207221
}
208222
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.OpenApi.OData.Edm;
1313
using Microsoft.OpenApi.OData.Vocabulary;
1414
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
15+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1516

1617
namespace Microsoft.OpenApi.OData.Operation
1718
{
@@ -156,6 +157,19 @@ protected string GetOperationId(string prefix = null)
156157
return string.Join(".", items);
157158
}
158159

160+
/// <inheritdoc/>
161+
protected override void SetExternalDocs(OpenApiOperation operation)
162+
{
163+
if (Context.Settings.ShowExternalDocs && Context.Model.GetLinkRecord(NavigationProperty, CustomLinkRel) is Link externalDocs)
164+
{
165+
operation.ExternalDocs = new OpenApiExternalDocs()
166+
{
167+
Description = CoreConstants.ExternalDocsDescription,
168+
Url = externalDocs.Href
169+
};
170+
}
171+
}
172+
159173
/// <summary>
160174
/// Retrieves the CRUD restrictions annotations for the navigation property
161175
/// in context, given a capability annotation term.

0 commit comments

Comments
 (0)