diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs
index 3c34a0be..4f821154 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Microsoft.OpenApi.OData.Vocabulary;
namespace Microsoft.OpenApi.OData.Common
@@ -106,5 +107,13 @@ internal static string CheckArgumentNullOrEmpty(string value, string parameterNa
return value;
}
+
+ ///
+ /// Lowers the first character of the string.
+ ///
+ /// The input string.
+ /// The changed string.
+ internal static string ToFirstCharacterLowerCase(this string input)
+ => string.IsNullOrEmpty(input) ? input : $"{char.ToLowerInvariant(input.FirstOrDefault())}{input.Substring(1)}";
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs
index fb7815bb..e551c27d 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs
@@ -15,6 +15,7 @@
using Microsoft.OpenApi.Exceptions;
using System.Linq;
using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.OData.OpenApiExtensions;
namespace Microsoft.OpenApi.OData.Generator
{
@@ -130,7 +131,7 @@ internal static IEnumerable GetAllCollectionEntityTypes(this
Enumerable.Empty())
.Union(context.Model
.SchemaElements
- .OfType()
+ .OfType()
.SelectMany(x => x.NavigationProperties())
.Where(x => x.TargetMultiplicity() == EdmMultiplicity.Many)
.Select(x => x.Type.ToStructuredType()))
@@ -209,7 +210,7 @@ public static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEdm
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(enumType, nameof(enumType));
- OpenApiSchema schema = new OpenApiSchema
+ OpenApiSchema schema = new()
{
// An enumeration type is represented as a Schema Object of type string
Type = "string",
@@ -221,16 +222,40 @@ public static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEdm
// whose value is the value of the unqualified annotation Core.Description of the enumeration type.
Description = context.Model.GetDescriptionAnnotation(enumType)
};
+ var extension = (context.Settings.OpenApiSpecVersion == OpenApiSpecVersion.OpenApi2_0 ||
+ context.Settings.OpenApiSpecVersion == OpenApiSpecVersion.OpenApi3_0 ) &&
+ context.Settings.AddEnumDescriptionExtension ?
+ new OpenApiEnumValuesDescriptionExtension {
+ EnumName = enumType.Name,
+ } :
+ null;
// Enum value is an array that contains a string with the member name for each enumeration member.
foreach (IEdmEnumMember member in enumType.Members)
{
schema.Enum.Add(new OpenApiString(member.Name));
+ AddEnumDescription(member, extension, context);
}
+ if(extension?.ValuesDescriptions.Any() ?? false)
+ schema.Extensions.Add(extension.Name, extension);
schema.Title = enumType.Name;
return schema;
}
+ private static void AddEnumDescription(IEdmEnumMember member, OpenApiEnumValuesDescriptionExtension target, ODataContext context)
+ {
+ if (target == null)
+ return;
+
+ var enumDescription = context.Model.GetDescriptionAnnotation(member);
+ if(!string.IsNullOrEmpty(enumDescription))
+ target.ValuesDescriptions.Add(new EnumDescription
+ {
+ Name = member.Name,
+ Value = member.Name,
+ Description = enumDescription
+ });
+ }
///
/// Create a for a .
diff --git a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
index a32ce8f2..5770b484 100644
--- a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
@@ -208,6 +208,13 @@ public string PathPrefix
///
public bool EnableDeprecationInformation { get; set; } = true;
+ ///
+ /// 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.
+ /// V3.1 will won't add the extension.
+ /// https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-enum
+ ///
+ public bool AddEnumDescriptionExtension { get; set; } = false;
+
internal OpenApiConvertSettings Clone()
{
var newSettings = new OpenApiConvertSettings
@@ -243,6 +250,7 @@ internal OpenApiConvertSettings Clone()
EnableODataTypeCast = this.EnableODataTypeCast,
RequireDerivedTypesConstraintForODataTypeCastSegments = this.RequireDerivedTypesConstraintForODataTypeCastSegments,
EnableDeprecationInformation = this.EnableDeprecationInformation,
+ AddEnumDescriptionExtension = this.AddEnumDescriptionExtension,
};
return newSettings;
diff --git a/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiDeprecationExtension.cs b/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiDeprecationExtension.cs
index 69c0069d..a062443d 100644
--- a/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiDeprecationExtension.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiDeprecationExtension.cs
@@ -4,8 +4,8 @@
// ------------------------------------------------------------
using System;
-using System.Linq;
using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.OData.Common;
using Microsoft.OpenApi.Writers;
namespace Microsoft.OpenApi.OData.OpenApiExtensions;
@@ -46,17 +46,15 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
writer.WriteStartObject();
if(RemovalDate.HasValue)
- writer.WriteProperty(ToFirstCharacterLowerCase(nameof(RemovalDate)), RemovalDate.Value);
+ writer.WriteProperty(nameof(RemovalDate).ToFirstCharacterLowerCase(), RemovalDate.Value);
if(Date.HasValue)
- writer.WriteProperty(ToFirstCharacterLowerCase(nameof(Date)), Date.Value);
+ writer.WriteProperty(nameof(Date).ToFirstCharacterLowerCase(), Date.Value);
if(!string.IsNullOrEmpty(Version))
- writer.WriteProperty(ToFirstCharacterLowerCase(nameof(Version)), Version);
+ writer.WriteProperty(nameof(Version).ToFirstCharacterLowerCase(), Version);
if(!string.IsNullOrEmpty(Description))
- writer.WriteProperty(ToFirstCharacterLowerCase(nameof(Description)), Description);
+ writer.WriteProperty(nameof(Description).ToFirstCharacterLowerCase(), Description);
writer.WriteEndObject();
}
- }
- private static string ToFirstCharacterLowerCase(string input)
- => string.IsNullOrEmpty(input) ? input : $"{char.ToLowerInvariant(input.FirstOrDefault())}{input.Substring(1)}";
+ }
}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs b/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs
new file mode 100644
index 00000000..13028562
--- /dev/null
+++ b/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs
@@ -0,0 +1,74 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.OData.Common;
+using Microsoft.OpenApi.Writers;
+
+namespace Microsoft.OpenApi.OData.OpenApiExtensions;
+
+///
+/// Extension element for OpenAPI to add enum values descriptions.
+/// Based of the AutoRest specification https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-enum
+///
+internal class OpenApiEnumValuesDescriptionExtension : IOpenApiExtension
+{
+ ///
+ /// Name of the extension as used in the description.
+ ///
+ public string Name => "x-ms-enum";
+
+ ///
+ /// The of the enum.
+ ///
+ public string EnumName { get; set; }
+
+ ///
+ /// Descriptions for the enum symbols, where the value MUST match the enum symbols in the main description
+ ///
+ public List ValuesDescriptions { get; set; } = new();
+
+ ///
+ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
+ {
+ if(writer == null)
+ throw new ArgumentNullException(nameof(writer));
+ if((specVersion == OpenApiSpecVersion.OpenApi2_0 || specVersion == OpenApiSpecVersion.OpenApi3_0) &&
+ !string.IsNullOrEmpty(EnumName) &&
+ ValuesDescriptions.Any())
+ { // when we upgrade to 3.1, we don't need to write this extension as JSON schema will support writing enum values
+ writer.WriteStartObject();
+ writer.WriteProperty(nameof(Name).ToFirstCharacterLowerCase(), EnumName);
+ writer.WriteProperty("modelAsString", false);
+ writer.WriteRequiredCollection("values", ValuesDescriptions, (w, x) => {
+ w.WriteStartObject();
+ w.WriteProperty(nameof(x.Value).ToFirstCharacterLowerCase(), x.Value);
+ w.WriteProperty(nameof(x.Description).ToFirstCharacterLowerCase(), x.Description);
+ w.WriteProperty(nameof(x.Name).ToFirstCharacterLowerCase(), x.Name);
+ w.WriteEndObject();
+ });
+ writer.WriteEndObject();
+ }
+ }
+}
+
+internal class EnumDescription : IOpenApiElement
+{
+ ///
+ /// The description for the enum symbol
+ ///
+ public string Description { get; set; }
+ ///
+ /// The symbol for the enum symbol to use for code-generation
+ ///
+ public string Name { get; set; }
+ ///
+ /// The symbol as described in the main enum schema.
+ ///
+ public string Value { get; set; }
+}
diff --git a/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt
index b2576112..62ac6f8b 100644
--- a/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt
@@ -179,6 +179,8 @@ Microsoft.OpenApi.OData.OpenApiConvertSettings.Version.get -> System.Version
Microsoft.OpenApi.OData.OpenApiConvertSettings.Version.set -> void
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDeprecationInformation.get -> bool
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDeprecationInformation.set -> void
+Microsoft.OpenApi.OData.OpenApiConvertSettings.AddEnumDescriptionExtension.get -> bool
+Microsoft.OpenApi.OData.OpenApiConvertSettings.AddEnumDescriptionExtension.set -> void
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.GetPathItemName(Microsoft.OpenApi.OData.OpenApiConvertSettings settings, System.Collections.Generic.HashSet parameters) -> string
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.Identifier.get -> string
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.EntityType.get -> Microsoft.OData.Edm.IEdmEntityType
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/EdmModelOpenApiExtensionsTest.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/EdmModelOpenApiExtensionsTest.cs
index 0a71c2ec..6749c98e 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/EdmModelOpenApiExtensionsTest.cs
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/EdmModelOpenApiExtensionsTest.cs
@@ -210,6 +210,7 @@ public void TripServiceMetadataToOpenApiJsonWorks(OpenApiSpecVersion specVersion
IEEE754Compatible = true,
OpenApiSpecVersion = specVersion,
AddSingleQuotesForStringParameters = true,
+ AddEnumDescriptionExtension = true,
};
// Act
string json = WriteEdmModelAsOpenApi(model, OpenApiFormat.Json, settings);
@@ -242,6 +243,7 @@ public void TripServiceMetadataToOpenApiYamlWorks(OpenApiSpecVersion specVersion
IEEE754Compatible = true,
OpenApiSpecVersion = specVersion,
AddSingleQuotesForStringParameters = true,
+ AddEnumDescriptionExtension = true,
};
// Act
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/OpenApiExtensions/OpenApiEnumValuesDescriptionExtensionTexts.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/OpenApiExtensions/OpenApiEnumValuesDescriptionExtensionTexts.cs
new file mode 100644
index 00000000..1c1feeb9
--- /dev/null
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/OpenApiExtensions/OpenApiEnumValuesDescriptionExtensionTexts.cs
@@ -0,0 +1,75 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.OpenApi.Writers;
+using Xunit;
+
+namespace Microsoft.OpenApi.OData.OpenApiExtensions.Tests;
+
+public class OpenApiEnumValuesDescriptionExtensionTexts
+{
+ [Fact]
+ public void ExtensionNameMatchesExpected()
+ {
+ // Arrange
+ OpenApiEnumValuesDescriptionExtension extension = new();
+
+ // Act
+ string name = extension.Name;
+ string expectedName = "x-ms-enum";
+
+ // Assert
+ Assert.Equal(expectedName, name);
+ }
+
+ [Fact]
+ public void WritesNothingWhenNoValues()
+ {
+ // Arrange
+ OpenApiEnumValuesDescriptionExtension extension = new();
+ using TextWriter sWriter = new StringWriter();
+ OpenApiJsonWriter writer = new(sWriter);
+
+ // Act
+ extension.Write(writer, OpenApiSpecVersion.OpenApi3_0);
+ string result = sWriter.ToString();
+
+ // Assert
+ Assert.Null(extension.EnumName);
+ Assert.Empty(extension.ValuesDescriptions);
+ Assert.Empty(result);
+ }
+ [Fact]
+ public void WritesEnumDescription()
+ {
+ // Arrange
+ OpenApiEnumValuesDescriptionExtension extension = new();
+ extension.EnumName = "TestEnum";
+ extension.ValuesDescriptions = new()
+ {
+ new() {
+ Description = "TestDescription",
+ Value = "TestValue",
+ Name = "TestName"
+ }
+ };
+ using TextWriter sWriter = new StringWriter();
+ OpenApiJsonWriter writer = new(sWriter);
+
+ // Act
+ extension.Write(writer, OpenApiSpecVersion.OpenApi3_0);
+ string result = sWriter.ToString();
+
+ // Assert
+ Assert.Contains("values", result);
+ Assert.Contains("modelAsString\": false", result);
+ Assert.Contains("name\": \"TestEnum", result);
+ Assert.Contains("description\": \"TestDescription", result);
+ Assert.Contains("value\": \"TestValue", result);
+ Assert.Contains("name\": \"TestName", result);
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml
index 2b185b00..ab636673 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml
@@ -144,10 +144,17 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json
index 6a8e934a..fce7d605 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json
@@ -28511,12 +28511,34 @@
},
"Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender": {
"title": "PersonGender",
+ "description": "Gender of the person.",
"enum": [
"Male",
"Female",
"Unknow"
],
- "type": "string"
+ "type": "string",
+ "x-ms-enum": {
+ "name": "PersonGender",
+ "modelAsString": false,
+ "values": [
+ {
+ "value": "Male",
+ "description": "The Male gender.",
+ "name": "Male"
+ },
+ {
+ "value": "Female",
+ "description": "The Female gender.",
+ "name": "Female"
+ },
+ {
+ "value": "Unknow",
+ "description": "Unknown gender or prefers not to say.",
+ "name": "Unknow"
+ }
+ ]
+ }
},
"Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature": {
"title": "Feature",
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml
index 80aa6474..0bdaee62 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml
@@ -20199,11 +20199,25 @@ definitions:
$ref: '#/definitions/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person'
Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender:
title: PersonGender
+ description: Gender of the person.
enum:
- Male
- Female
- Unknow
type: string
+ x-ms-enum:
+ name: PersonGender
+ modelAsString: false
+ values:
+ - value: Male
+ description: The Male gender.
+ name: Male
+ - value: Female
+ description: The Female gender.
+ name: Female
+ - value: Unknow
+ description: Unknown gender or prefers not to say.
+ name: Unknow
Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature:
title: Feature
enum:
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json
index caa35c91..127be552 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json
@@ -31962,7 +31962,29 @@
"Female",
"Unknow"
],
- "type": "string"
+ "type": "string",
+ "description": "Gender of the person.",
+ "x-ms-enum": {
+ "name": "PersonGender",
+ "modelAsString": false,
+ "values": [
+ {
+ "value": "Male",
+ "description": "The Male gender.",
+ "name": "Male"
+ },
+ {
+ "value": "Female",
+ "description": "The Female gender.",
+ "name": "Female"
+ },
+ {
+ "value": "Unknow",
+ "description": "Unknown gender or prefers not to say.",
+ "name": "Unknow"
+ }
+ ]
+ }
},
"Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature": {
"title": "Feature",
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml
index 8267bb53..396afddf 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml
@@ -22279,6 +22279,20 @@ components:
- Female
- Unknow
type: string
+ description: Gender of the person.
+ x-ms-enum:
+ name: PersonGender
+ modelAsString: false
+ values:
+ - value: Male
+ description: The Male gender.
+ name: Male
+ - value: Female
+ description: The Female gender.
+ name: Female
+ - value: Unknow
+ description: Unknown gender or prefers not to say.
+ name: Unknow
Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature:
title: Feature
enum: