Skip to content

Commit 9d44019

Browse files
irvinesundayIrvine Sunday
andauthored
Fixes OpenAPI Links and makes them optional (#72)
* Adds Links to EntitySet type response objects * Adds links to the test OpenAPI docs. * Refactor to use Utils class for nullability checks * Modify link generator to handle all instances of IEdmEntityType * Update arguments * Add new Link properties * Update test files with links properties * Rename parameter * Fix OpenAPI Link generation * Reorder Parameters generation before Responses This is important so that the parameters info can be used for Links generation * Update test files to validate Link fixes * Fix links and add optional setting * Update test for Links * Revert project PlatformTarget * Add comment * Refactor to remove unnecessary Link creation of collection of entities * Revert platform target to default - AnyCPU * Add helpful comment * Grammar nit fix Co-authored-by: Irvine Sunday <[email protected]> Co-authored-by: Irvine Sunday <[email protected]>
1 parent c58a613 commit 9d44019

14 files changed

+285
-606
lines changed

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

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.OpenApi.Models;
1010
using Microsoft.OpenApi.Any;
1111
using Microsoft.OpenApi.OData.Edm;
12+
using System.Linq;
1213

1314
namespace Microsoft.OpenApi.OData.Generator
1415
{
@@ -22,32 +23,78 @@ internal static class OpenApiLinkGenerator
2223
/// </summary>
2324
/// <param name="context">The OData context.</param>
2425
/// <param name="entityType">The Entity type.</param>
25-
/// <param name ="sourceElementName">The name of the source of the <see cref="IEdmEntityType" />.</param>
26+
/// <param name ="entityName">The name of the source of the <see cref="IEdmEntityType"/> object.</param>
27+
/// <param name="entityKind">"The kind of the source of the <see cref="IEdmEntityType"/> object.</param>
28+
/// <param name="parameters">"The list of parameters of the incoming operation.</param>
29+
/// <param name="navPropOperationId">Optional parameter: The operation id of the source of the NavigationProperty object.</param>
2630
/// <returns>The created dictionary of <see cref="OpenApiLink"/> object.</returns>
27-
public static IDictionary<string, OpenApiLink> CreateLinks(this ODataContext context, IEdmEntityType entityType, string sourceElementName)
31+
public static IDictionary<string, OpenApiLink> CreateLinks(this ODataContext context,
32+
IEdmEntityType entityType, string entityName, string entityKind,
33+
IList<OpenApiParameter> parameters, string navPropOperationId = null)
2834
{
35+
IDictionary<string, OpenApiLink> links = new Dictionary<string, OpenApiLink>();
36+
2937
Utils.CheckArgumentNull(context, nameof(context));
3038
Utils.CheckArgumentNull(entityType, nameof(entityType));
31-
Utils.CheckArgumentNullOrEmpty(sourceElementName, nameof(sourceElementName));
39+
Utils.CheckArgumentNullOrEmpty(entityName, nameof(entityName));
40+
Utils.CheckArgumentNullOrEmpty(entityKind, nameof(entityKind));
41+
Utils.CheckArgumentNull(parameters, nameof(parameters));
3242

33-
IDictionary<string, OpenApiLink> links = new Dictionary<string, OpenApiLink>();
34-
foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties())
43+
List<string> pathKeyNames = new List<string>();
44+
45+
// Fetch defined Id(s) from incoming parameters (if any)
46+
foreach (var parameter in parameters)
47+
{
48+
if (!string.IsNullOrEmpty(parameter.Description) &&
49+
parameter.Description.ToLower().Contains("key"))
50+
{
51+
pathKeyNames.Add(parameter.Name);
52+
}
53+
}
54+
55+
foreach (IEdmNavigationProperty navProp in entityType.NavigationProperties())
3556
{
57+
string navPropName = navProp.Name;
58+
string operationId;
59+
string operationPrefix;
60+
61+
switch (entityKind)
62+
{
63+
case "Navigation": // just for contained navigations
64+
operationPrefix = navPropOperationId;
65+
break;
66+
default: // EntitySet, Entity, Singleton
67+
operationPrefix = entityName;
68+
break;
69+
}
70+
71+
if (navProp.TargetMultiplicity() == EdmMultiplicity.Many)
72+
{
73+
operationId = operationPrefix + ".List" + Utils.UpperFirstChar(navPropName);
74+
}
75+
else
76+
{
77+
operationId = operationPrefix + ".Get" + Utils.UpperFirstChar(navPropName);
78+
}
79+
3680
OpenApiLink link = new OpenApiLink
3781
{
38-
OperationId = sourceElementName + "." + entityType.Name + ".Get" + Utils.UpperFirstChar(entityType.Name),
82+
OperationId = operationId,
3983
Parameters = new Dictionary<string, RuntimeExpressionAnyWrapper>()
4084
};
4185

42-
foreach (IEdmStructuralProperty key in entityType.Key())
86+
if (pathKeyNames.Any())
4387
{
44-
link.Parameters[key.Name] = new RuntimeExpressionAnyWrapper
88+
foreach (var pathKeyName in pathKeyNames)
4589
{
46-
Any = new OpenApiString("$request.path." + key.Name)
47-
};
90+
link.Parameters[pathKeyName] = new RuntimeExpressionAnyWrapper
91+
{
92+
Any = new OpenApiString("$request.path." + pathKeyName)
93+
};
94+
}
4895
}
4996

50-
links[np.Name] = link;
97+
links[navProp.Name] = link;
5198
}
5299

53100
return links;

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ public class OpenApiConvertSettings
120120
/// </summary>
121121
public string PathPrefix { get; set; } = string.Empty;
122122

123+
/// <summary>
124+
/// Gets/Sets a value indicating whether or not to show the OpenAPI links in the responses.
125+
/// </summary>
126+
public bool ShowLinks { get; set; } = false;
127+
123128
internal OpenApiConvertSettings Clone()
124129
{
125130
var newSettings = new OpenApiConvertSettings
@@ -144,7 +149,8 @@ internal OpenApiConvertSettings Clone()
144149
EnableDiscriminatorValue = this.EnableDiscriminatorValue,
145150
EnableDerivedTypesReferencesForResponses = this.EnableDerivedTypesReferencesForResponses,
146151
EnableDerivedTypesReferencesForRequestBody = this.EnableDerivedTypesReferencesForRequestBody,
147-
PathPrefix = this.PathPrefix
152+
PathPrefix = this.PathPrefix,
153+
ShowLinks = this.ShowLinks
148154
};
149155

150156
return newSettings;

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,19 @@ protected override void SetParameters(OpenApiOperation operation)
6464
protected override void SetResponses(OpenApiOperation operation)
6565
{
6666
OpenApiSchema schema = null;
67+
IDictionary<string, OpenApiLink> links = null;
6768

6869
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
6970
{
7071
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
7172
}
7273

74+
if (Context.Settings.ShowLinks)
75+
{
76+
links = Context.CreateLinks(entityType: EntitySet.EntityType(), entityName: EntitySet.Name,
77+
entityKind: EntitySet.ContainerElementKind.ToString(), parameters: operation.Parameters);
78+
}
79+
7380
if (schema == null)
7481
{
7582
schema = new OpenApiSchema
@@ -99,7 +106,7 @@ protected override void SetResponses(OpenApiOperation operation)
99106
}
100107
}
101108
},
102-
Links = Context.CreateLinks(EntitySet.EntityType(), EntitySet.Name)
109+
Links = links
103110
}
104111
}
105112
};

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,7 @@ protected override void SetResponses(OpenApiOperation operation)
192192
}
193193
}
194194
}
195-
},
196-
Links = Context.CreateLinks(EntitySet.EntityType(), EntitySet.Name)
195+
}
197196
}
198197
}
199198
};

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,23 @@ protected override void SetResponses(OpenApiOperation operation)
131131
}
132132
}
133133
}
134-
},
135-
Links = Context.CreateLinks(NavigationProperty.ToEntityType(), NavigationProperty.Name)
134+
}
136135
}
137136
}
138137
};
139138
}
140139
else
141140
{
141+
IDictionary<string, OpenApiLink> links = null;
142+
if (Context.Settings.ShowLinks)
143+
{
144+
string operationId = GetOperationId();
145+
146+
links = Context.CreateLinks(entityType: NavigationProperty.ToEntityType(), entityName: NavigationProperty.Name,
147+
entityKind: NavigationProperty.PropertyKind.ToString(), parameters: operation.Parameters,
148+
navPropOperationId: operationId);
149+
}
150+
142151
operation.Responses = new OpenApiResponses
143152
{
144153
{
@@ -155,7 +164,8 @@ protected override void SetResponses(OpenApiOperation operation)
155164
Schema = schema
156165
}
157166
}
158-
}
167+
},
168+
Links = links
159169
}
160170
}
161171
};

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ protected override void SetExtensions(OpenApiOperation operation)
137137
base.SetExtensions(operation);
138138
}
139139

140-
protected string GetOperationId(string prefix)
140+
protected string GetOperationId(string prefix = null)
141141
{
142142
IList<string> items = new List<string>
143143
{
@@ -152,7 +152,15 @@ protected string GetOperationId(string prefix)
152152
{
153153
if (segment == lastpath)
154154
{
155-
items.Add(prefix + Utils.UpperFirstChar(npSegment.NavigationProperty.Name));
155+
if (prefix != null)
156+
{
157+
items.Add(prefix + Utils.UpperFirstChar(npSegment.NavigationProperty.Name));
158+
}
159+
else
160+
{
161+
items.Add(Utils.UpperFirstChar(npSegment.NavigationProperty.Name));
162+
}
163+
156164
break;
157165
}
158166
else

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,18 @@ public virtual OpenApiOperation CreateOperation(ODataContext context, ODataPath
4444
// Security
4545
SetSecurity(operation);
4646

47+
/* Parameters
48+
These need to be set before Responses, as the Parameters
49+
will be used in the Responses when creating Links.
50+
*/
51+
SetParameters(operation);
52+
4753
// Responses
4854
SetResponses(operation);
4955

5056
// RequestBody
5157
SetRequestBody(operation);
5258

53-
// Parameters
54-
SetParameters(operation);
55-
5659
// Tags
5760
SetTags(operation);
5861

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,23 @@ protected override void SetResponses(OpenApiOperation operation)
116116
}
117117
}
118118
}
119-
},
120-
Links = Context.CreateLinks(NavigationProperty.ToEntityType(), NavigationProperty.Name)
119+
}
121120
}
122121
}
123122
};
124123
}
125124
else
126125
{
126+
IDictionary<string, OpenApiLink> links = null;
127+
if (Context.Settings.ShowLinks)
128+
{
129+
string operationId = GetOperationId();
130+
131+
links = Context.CreateLinks(entityType: NavigationProperty.ToEntityType(), entityName: NavigationProperty.Name,
132+
entityKind: NavigationProperty.PropertyKind.ToString(), parameters: operation.Parameters,
133+
navPropOperationId: operationId);
134+
}
135+
127136
operation.Responses = new OpenApiResponses
128137
{
129138
{
@@ -140,7 +149,8 @@ protected override void SetResponses(OpenApiOperation operation)
140149
Schema = schema
141150
}
142151
}
143-
}
152+
},
153+
Links = links
144154
}
145155
}
146156
};

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,19 @@ protected override void SetParameters(OpenApiOperation operation)
6464
protected override void SetResponses(OpenApiOperation operation)
6565
{
6666
OpenApiSchema schema = null;
67+
IDictionary<string, OpenApiLink> links = null;
6768

6869
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
6970
{
7071
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(Singleton.EntityType(), Context.Model);
7172
}
7273

74+
if (Context.Settings.ShowLinks)
75+
{
76+
links = Context.CreateLinks(entityType: Singleton.EntityType(), entityName: Singleton.Name,
77+
entityKind: Singleton.ContainerElementKind.ToString(), parameters: operation.Parameters);
78+
}
79+
7380
if (schema == null)
7481
{
7582
schema = new OpenApiSchema
@@ -99,7 +106,7 @@ protected override void SetResponses(OpenApiOperation operation)
99106
}
100107
}
101108
},
102-
Links = Context.CreateLinks(Singleton.EntityType(), Singleton.Name)
109+
Links = links
103110
}
104111
}
105112
};

test/Microsoft.OpenAPI.OData.Reader.Tests/EdmModelOpenApiExtensionsTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ public void MultipleSchemasEdmModelToOpenApiJsonWorks(OpenApiSpecVersion specVer
140140
IEdmModel model = EdmModelHelper.MultipleSchemasEdmModel;
141141
var openApiConvertSettings = new OpenApiConvertSettings();
142142
openApiConvertSettings.OpenApiSpecVersion = specVersion;
143+
openApiConvertSettings.ShowLinks = true; // test Links
143144

144145
// Act
145146
string json = WriteEdmModelAsOpenApi(model, OpenApiFormat.Json, openApiConvertSettings);
@@ -165,6 +166,7 @@ public void MultipleSchemasEdmModelToOpenApiYamlWorks(OpenApiSpecVersion specVer
165166
IEdmModel model = EdmModelHelper.MultipleSchemasEdmModel;
166167
var openApiConvertSettings = new OpenApiConvertSettings();
167168
openApiConvertSettings.OpenApiSpecVersion = specVersion;
169+
openApiConvertSettings.ShowLinks = true; // test Links
168170

169171
// Act
170172
string yaml = WriteEdmModelAsOpenApi(model, OpenApiFormat.Yaml, openApiConvertSettings);

0 commit comments

Comments
 (0)