Skip to content

Commit dbcf686

Browse files
authored
Merge pull request #168 from microsoft/feature/enum-description
adds support for enum descriptions
2 parents fb9e753 + e65e32b commit dbcf686

File tree

13 files changed

+288
-16
lines changed

13 files changed

+288
-16
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System;
77
using System.Collections.Generic;
8+
using System.Linq;
89
using Microsoft.OpenApi.OData.Vocabulary;
910

1011
namespace Microsoft.OpenApi.OData.Common
@@ -106,5 +107,13 @@ internal static string CheckArgumentNullOrEmpty(string value, string parameterNa
106107

107108
return value;
108109
}
110+
111+
/// <summary>
112+
/// Lowers the first character of the string.
113+
/// </summary>
114+
/// <param name="input">The input string.</param>
115+
/// <returns>The changed string.</returns>
116+
internal static string ToFirstCharacterLowerCase(this string input)
117+
=> string.IsNullOrEmpty(input) ? input : $"{char.ToLowerInvariant(input.FirstOrDefault())}{input.Substring(1)}";
109118
}
110119
}

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Microsoft.OpenApi.Exceptions;
1616
using System.Linq;
1717
using Microsoft.OpenApi.Interfaces;
18+
using Microsoft.OpenApi.OData.OpenApiExtensions;
1819

1920
namespace Microsoft.OpenApi.OData.Generator
2021
{
@@ -130,7 +131,7 @@ internal static IEnumerable<IEdmStructuredType> GetAllCollectionEntityTypes(this
130131
Enumerable.Empty<IEdmStructuredType>())
131132
.Union(context.Model
132133
.SchemaElements
133-
.OfType<IEdmStructuredType>()
134+
.OfType<IEdmStructuredType>()
134135
.SelectMany(x => x.NavigationProperties())
135136
.Where(x => x.TargetMultiplicity() == EdmMultiplicity.Many)
136137
.Select(x => x.Type.ToStructuredType()))
@@ -209,7 +210,7 @@ public static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEdm
209210
Utils.CheckArgumentNull(context, nameof(context));
210211
Utils.CheckArgumentNull(enumType, nameof(enumType));
211212

212-
OpenApiSchema schema = new OpenApiSchema
213+
OpenApiSchema schema = new()
213214
{
214215
// An enumeration type is represented as a Schema Object of type string
215216
Type = "string",
@@ -221,16 +222,40 @@ public static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEdm
221222
// whose value is the value of the unqualified annotation Core.Description of the enumeration type.
222223
Description = context.Model.GetDescriptionAnnotation(enumType)
223224
};
225+
var extension = (context.Settings.OpenApiSpecVersion == OpenApiSpecVersion.OpenApi2_0 ||
226+
context.Settings.OpenApiSpecVersion == OpenApiSpecVersion.OpenApi3_0 ) &&
227+
context.Settings.AddEnumDescriptionExtension ?
228+
new OpenApiEnumValuesDescriptionExtension {
229+
EnumName = enumType.Name,
230+
} :
231+
null;
224232

225233
// Enum value is an array that contains a string with the member name for each enumeration member.
226234
foreach (IEdmEnumMember member in enumType.Members)
227235
{
228236
schema.Enum.Add(new OpenApiString(member.Name));
237+
AddEnumDescription(member, extension, context);
229238
}
230239

240+
if(extension?.ValuesDescriptions.Any() ?? false)
241+
schema.Extensions.Add(extension.Name, extension);
231242
schema.Title = enumType.Name;
232243
return schema;
233244
}
245+
private static void AddEnumDescription(IEdmEnumMember member, OpenApiEnumValuesDescriptionExtension target, ODataContext context)
246+
{
247+
if (target == null)
248+
return;
249+
250+
var enumDescription = context.Model.GetDescriptionAnnotation(member);
251+
if(!string.IsNullOrEmpty(enumDescription))
252+
target.ValuesDescriptions.Add(new EnumDescription
253+
{
254+
Name = member.Name,
255+
Value = member.Name,
256+
Description = enumDescription
257+
});
258+
}
234259

235260
/// <summary>
236261
/// Create a <see cref="OpenApiSchema"/> for a <see cref="IEdmStructuredType"/>.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ public string PathPrefix
208208
/// </summary>
209209
public bool EnableDeprecationInformation { get; set; } = true;
210210

211+
/// <summary>
212+
/// Gets/sets a value indicating whether or not to add a "x-ms-enum" extension to the enum type schema for V2 and V3 descriptions.
213+
/// V3.1 will won't add the extension.
214+
/// https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-enum
215+
/// </summary>
216+
public bool AddEnumDescriptionExtension { get; set; } = false;
217+
211218
internal OpenApiConvertSettings Clone()
212219
{
213220
var newSettings = new OpenApiConvertSettings
@@ -243,6 +250,7 @@ internal OpenApiConvertSettings Clone()
243250
EnableODataTypeCast = this.EnableODataTypeCast,
244251
RequireDerivedTypesConstraintForODataTypeCastSegments = this.RequireDerivedTypesConstraintForODataTypeCastSegments,
245252
EnableDeprecationInformation = this.EnableDeprecationInformation,
253+
AddEnumDescriptionExtension = this.AddEnumDescriptionExtension,
246254
};
247255

248256
return newSettings;

src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiDeprecationExtension.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
// ------------------------------------------------------------
55

66
using System;
7-
using System.Linq;
87
using Microsoft.OpenApi.Interfaces;
8+
using Microsoft.OpenApi.OData.Common;
99
using Microsoft.OpenApi.Writers;
1010

1111
namespace Microsoft.OpenApi.OData.OpenApiExtensions;
@@ -46,17 +46,15 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
4646
writer.WriteStartObject();
4747

4848
if(RemovalDate.HasValue)
49-
writer.WriteProperty(ToFirstCharacterLowerCase(nameof(RemovalDate)), RemovalDate.Value);
49+
writer.WriteProperty(nameof(RemovalDate).ToFirstCharacterLowerCase(), RemovalDate.Value);
5050
if(Date.HasValue)
51-
writer.WriteProperty(ToFirstCharacterLowerCase(nameof(Date)), Date.Value);
51+
writer.WriteProperty(nameof(Date).ToFirstCharacterLowerCase(), Date.Value);
5252
if(!string.IsNullOrEmpty(Version))
53-
writer.WriteProperty(ToFirstCharacterLowerCase(nameof(Version)), Version);
53+
writer.WriteProperty(nameof(Version).ToFirstCharacterLowerCase(), Version);
5454
if(!string.IsNullOrEmpty(Description))
55-
writer.WriteProperty(ToFirstCharacterLowerCase(nameof(Description)), Description);
55+
writer.WriteProperty(nameof(Description).ToFirstCharacterLowerCase(), Description);
5656

5757
writer.WriteEndObject();
5858
}
59-
}
60-
private static string ToFirstCharacterLowerCase(string input)
61-
=> string.IsNullOrEmpty(input) ? input : $"{char.ToLowerInvariant(input.FirstOrDefault())}{input.Substring(1)}";
59+
}
6260
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// ------------------------------------------------------------
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using Microsoft.OpenApi.Interfaces;
10+
using Microsoft.OpenApi.OData.Common;
11+
using Microsoft.OpenApi.Writers;
12+
13+
namespace Microsoft.OpenApi.OData.OpenApiExtensions;
14+
15+
/// <summary>
16+
/// Extension element for OpenAPI to add enum values descriptions.
17+
/// Based of the AutoRest specification https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-enum
18+
/// </summary>
19+
internal class OpenApiEnumValuesDescriptionExtension : IOpenApiExtension
20+
{
21+
/// <summary>
22+
/// Name of the extension as used in the description.
23+
/// </summary>
24+
public string Name => "x-ms-enum";
25+
26+
/// <summary>
27+
/// The of the enum.
28+
/// </summary>
29+
public string EnumName { get; set; }
30+
31+
/// <summary>
32+
/// Descriptions for the enum symbols, where the value MUST match the enum symbols in the main description
33+
/// </summary>
34+
public List<EnumDescription> ValuesDescriptions { get; set; } = new();
35+
36+
/// <inheritdoc />
37+
public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
38+
{
39+
if(writer == null)
40+
throw new ArgumentNullException(nameof(writer));
41+
if((specVersion == OpenApiSpecVersion.OpenApi2_0 || specVersion == OpenApiSpecVersion.OpenApi3_0) &&
42+
!string.IsNullOrEmpty(EnumName) &&
43+
ValuesDescriptions.Any())
44+
{ // when we upgrade to 3.1, we don't need to write this extension as JSON schema will support writing enum values
45+
writer.WriteStartObject();
46+
writer.WriteProperty(nameof(Name).ToFirstCharacterLowerCase(), EnumName);
47+
writer.WriteProperty("modelAsString", false);
48+
writer.WriteRequiredCollection("values", ValuesDescriptions, (w, x) => {
49+
w.WriteStartObject();
50+
w.WriteProperty(nameof(x.Value).ToFirstCharacterLowerCase(), x.Value);
51+
w.WriteProperty(nameof(x.Description).ToFirstCharacterLowerCase(), x.Description);
52+
w.WriteProperty(nameof(x.Name).ToFirstCharacterLowerCase(), x.Name);
53+
w.WriteEndObject();
54+
});
55+
writer.WriteEndObject();
56+
}
57+
}
58+
}
59+
60+
internal class EnumDescription : IOpenApiElement
61+
{
62+
/// <summary>
63+
/// The description for the enum symbol
64+
/// </summary>
65+
public string Description { get; set; }
66+
/// <summary>
67+
/// The symbol for the enum symbol to use for code-generation
68+
/// </summary>
69+
public string Name { get; set; }
70+
/// <summary>
71+
/// The symbol as described in the main enum schema.
72+
/// </summary>
73+
public string Value { get; set; }
74+
}

src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ Microsoft.OpenApi.OData.OpenApiConvertSettings.Version.get -> System.Version
179179
Microsoft.OpenApi.OData.OpenApiConvertSettings.Version.set -> void
180180
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDeprecationInformation.get -> bool
181181
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDeprecationInformation.set -> void
182+
Microsoft.OpenApi.OData.OpenApiConvertSettings.AddEnumDescriptionExtension.get -> bool
183+
Microsoft.OpenApi.OData.OpenApiConvertSettings.AddEnumDescriptionExtension.set -> void
182184
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.GetPathItemName(Microsoft.OpenApi.OData.OpenApiConvertSettings settings, System.Collections.Generic.HashSet<string> parameters) -> string
183185
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.Identifier.get -> string
184186
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.EntityType.get -> Microsoft.OData.Edm.IEdmEntityType

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ public void TripServiceMetadataToOpenApiJsonWorks(OpenApiSpecVersion specVersion
210210
IEEE754Compatible = true,
211211
OpenApiSpecVersion = specVersion,
212212
AddSingleQuotesForStringParameters = true,
213+
AddEnumDescriptionExtension = true,
213214
};
214215
// Act
215216
string json = WriteEdmModelAsOpenApi(model, OpenApiFormat.Json, settings);
@@ -242,6 +243,7 @@ public void TripServiceMetadataToOpenApiYamlWorks(OpenApiSpecVersion specVersion
242243
IEEE754Compatible = true,
243244
OpenApiSpecVersion = specVersion,
244245
AddSingleQuotesForStringParameters = true,
246+
AddEnumDescriptionExtension = true,
245247
};
246248

247249
// Act
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// ------------------------------------------------------------
5+
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using Microsoft.OpenApi.Writers;
9+
using Xunit;
10+
11+
namespace Microsoft.OpenApi.OData.OpenApiExtensions.Tests;
12+
13+
public class OpenApiEnumValuesDescriptionExtensionTexts
14+
{
15+
[Fact]
16+
public void ExtensionNameMatchesExpected()
17+
{
18+
// Arrange
19+
OpenApiEnumValuesDescriptionExtension extension = new();
20+
21+
// Act
22+
string name = extension.Name;
23+
string expectedName = "x-ms-enum";
24+
25+
// Assert
26+
Assert.Equal(expectedName, name);
27+
}
28+
29+
[Fact]
30+
public void WritesNothingWhenNoValues()
31+
{
32+
// Arrange
33+
OpenApiEnumValuesDescriptionExtension extension = new();
34+
using TextWriter sWriter = new StringWriter();
35+
OpenApiJsonWriter writer = new(sWriter);
36+
37+
// Act
38+
extension.Write(writer, OpenApiSpecVersion.OpenApi3_0);
39+
string result = sWriter.ToString();
40+
41+
// Assert
42+
Assert.Null(extension.EnumName);
43+
Assert.Empty(extension.ValuesDescriptions);
44+
Assert.Empty(result);
45+
}
46+
[Fact]
47+
public void WritesEnumDescription()
48+
{
49+
// Arrange
50+
OpenApiEnumValuesDescriptionExtension extension = new();
51+
extension.EnumName = "TestEnum";
52+
extension.ValuesDescriptions = new()
53+
{
54+
new() {
55+
Description = "TestDescription",
56+
Value = "TestValue",
57+
Name = "TestName"
58+
}
59+
};
60+
using TextWriter sWriter = new StringWriter();
61+
OpenApiJsonWriter writer = new(sWriter);
62+
63+
// Act
64+
extension.Write(writer, OpenApiSpecVersion.OpenApi3_0);
65+
string result = sWriter.ToString();
66+
67+
// Assert
68+
Assert.Contains("values", result);
69+
Assert.Contains("modelAsString\": false", result);
70+
Assert.Contains("name\": \"TestEnum", result);
71+
Assert.Contains("description\": \"TestDescription", result);
72+
Assert.Contains("value\": \"TestValue", result);
73+
Assert.Contains("name\": \"TestName", result);
74+
}
75+
}

test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,17 @@
144144
<NavigationProperty Name="DirectReports" Type="Collection(Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person)" />
145145
</EntityType>
146146
<EnumType Name="PersonGender">
147-
<Member Name="Male" Value="0" />
148-
<Member Name="Female" Value="1" />
149-
<Member Name="Unknow" Value="2" />
150-
</EnumType>
147+
<Annotation Term="Org.OData.Core.V1.Description" String="Gender of the person." />
148+
<Member Name="Male" Value="0">
149+
<Annotation Term="Org.OData.Core.V1.Description" String="The Male gender." />
150+
</Member>
151+
<Member Name="Female" Value="1">
152+
<Annotation Term="Org.OData.Core.V1.Description" String="The Female gender." />
153+
</Member>
154+
<Member Name="Unknow" Value="2">
155+
<Annotation Term="Org.OData.Core.V1.Description" String="Unknown gender or prefers not to say." />
156+
</Member>
157+
</EnumType>
151158
<EnumType Name="Feature">
152159
<Member Name="Feature1" Value="0" />
153160
<Member Name="Feature2" Value="1" />

test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28511,12 +28511,34 @@
2851128511
},
2851228512
"Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender": {
2851328513
"title": "PersonGender",
28514+
"description": "Gender of the person.",
2851428515
"enum": [
2851528516
"Male",
2851628517
"Female",
2851728518
"Unknow"
2851828519
],
28519-
"type": "string"
28520+
"type": "string",
28521+
"x-ms-enum": {
28522+
"name": "PersonGender",
28523+
"modelAsString": false,
28524+
"values": [
28525+
{
28526+
"value": "Male",
28527+
"description": "The Male gender.",
28528+
"name": "Male"
28529+
},
28530+
{
28531+
"value": "Female",
28532+
"description": "The Female gender.",
28533+
"name": "Female"
28534+
},
28535+
{
28536+
"value": "Unknow",
28537+
"description": "Unknown gender or prefers not to say.",
28538+
"name": "Unknow"
28539+
}
28540+
]
28541+
}
2852028542
},
2852128543
"Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature": {
2852228544
"title": "Feature",

0 commit comments

Comments
 (0)