Skip to content

Commit ef8f508

Browse files
authored
Merge pull request #3240 from microsoft/andrueastman/bitwiseEnums
Generation/Handling of bitwise/flagged enums
2 parents 4eb8187 + 2706bec commit ef8f508

File tree

7 files changed

+237
-5
lines changed

7 files changed

+237
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Added support for external documentation links within descriptions in Python. [#2041](https://github.com/microsoft/kiota/issues/2041)
1414
- Added support for API manifests. [#3104](https://github.com/microsoft/kiota/issues/3104)
1515
- Added support for reserved path parameters. [#2320](https://github.com/microsoft/kiota/issues/2320)
16-
- Added support for csv values in enums using a mask.
16+
- Added support for csv enum values in enums using a mask in Go.
17+
- Added support for `x-ms-enum-flags` extension in Generator to enable generation of bitwise(flagged) enum values[#3237](https://github.com/microsoft/kiota/issues/3237).
1718

1819
### Changed
1920

src/Kiota.Builder/CodeDOM/CodeEnum.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Collections.Concurrent;
1+
using System.Collections.Concurrent;
32
using System.Collections.Generic;
43
using System.Linq;
54

@@ -12,6 +11,12 @@ public bool Flags
1211
{
1312
get; set;
1413
}
14+
15+
public EnumStyle Style
16+
{
17+
get; init;
18+
}
19+
1520
public CodeDocumentation Documentation { get; set; } = new();
1621
private readonly ConcurrentQueue<CodeEnumOption> OptionsInternal = new(); // this structure is used to maintain the order of the options
1722

@@ -37,3 +42,8 @@ public DeprecationInformation? Deprecation
3742
get; set;
3843
}
3944
}
45+
46+
public enum EnumStyle
47+
{
48+
Simple = 0 // Default
49+
}

src/Kiota.Builder/KiotaBuilder.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,10 @@ ex is SecurityException ||
471471
{
472472
OpenApiReservedParameterExtension.Name,
473473
static (i, _ ) => OpenApiReservedParameterExtension.Parse(i)
474+
},
475+
{
476+
OpenApiEnumFlagsExtension.Name,
477+
static (i, _ ) => OpenApiEnumFlagsExtension.Parse(i)
474478
}
475479
},
476480
RuleSet = ruleSet,
@@ -1753,9 +1757,17 @@ private CodeElement AddModelDeclarationIfDoesntExist(OpenApiUrlTreeNode currentN
17531757
if (schema.IsEnum())
17541758
{
17551759
var schemaDescription = schema.Description.CleanupDescription();
1760+
OpenApiEnumFlagsExtension? enumFlagsExtension = null;
1761+
if (schema.Extensions.TryGetValue(OpenApiEnumFlagsExtension.Name, out var rawExtension) &&
1762+
rawExtension is OpenApiEnumFlagsExtension flagsExtension)
1763+
{
1764+
enumFlagsExtension = flagsExtension;
1765+
}
17561766
var newEnum = new CodeEnum
17571767
{
1758-
Name = declarationName,//TODO set the flag property
1768+
Name = declarationName,
1769+
Flags = enumFlagsExtension?.IsFlags ?? false,
1770+
Style = Enum.TryParse<EnumStyle>(enumFlagsExtension?.Style ?? string.Empty, out var style) ? style : EnumStyle.Simple,
17591771
Documentation = new()
17601772
{
17611773
Description = !string.IsNullOrEmpty(schemaDescription) || !string.IsNullOrEmpty(schema.Reference?.Id) ?
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 Kiota.Builder.Extensions;
8+
using Microsoft.OpenApi;
9+
using Microsoft.OpenApi.Any;
10+
using Microsoft.OpenApi.Interfaces;
11+
using Microsoft.OpenApi.Writers;
12+
13+
namespace Kiota.Builder.OpenApiExtensions;
14+
15+
/// <summary>
16+
/// Extension element for OpenAPI to add deprecation information. x-ms-enum-flags
17+
/// </summary>
18+
public class OpenApiEnumFlagsExtension : IOpenApiExtension
19+
{
20+
/// <summary>
21+
/// Name of the extension as used in the description.
22+
/// </summary>
23+
public static string Name => "x-ms-enum-flags";
24+
/// <summary>
25+
/// Whether the enum is a flagged enum.
26+
/// </summary>
27+
public bool IsFlags
28+
{
29+
get; set;
30+
}
31+
/// <summary>
32+
/// The serialization style of the flagged enum.
33+
/// </summary>
34+
public string? Style
35+
{
36+
get; set;
37+
}
38+
/// <inheritdoc />
39+
public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
40+
{
41+
if (writer == null)
42+
throw new ArgumentNullException(nameof(writer));
43+
44+
writer.WriteStartObject();
45+
writer.WriteProperty(nameof(IsFlags).ToFirstCharacterLowerCase(), IsFlags);
46+
writer.WriteProperty(nameof(Style).ToFirstCharacterLowerCase(), Style);
47+
writer.WriteEndObject();
48+
}
49+
50+
public static OpenApiEnumFlagsExtension Parse(IOpenApiAny source)
51+
{
52+
if (source is not OpenApiObject rawObject) throw new ArgumentOutOfRangeException(nameof(source));
53+
var extension = new OpenApiEnumFlagsExtension();
54+
if (rawObject.TryGetValue("isFlags", out var flagsValue) && flagsValue is OpenApiBoolean isFlags)
55+
{
56+
extension.IsFlags = isFlags.Value;
57+
}
58+
if (rawObject.TryGetValue("style", out var styleValue) && styleValue is OpenApiString style)
59+
{
60+
extension.Style = style.Value;
61+
}
62+
return extension;
63+
}
64+
}

src/Kiota.Builder/Writers/CSharp/CodeEnumWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter write
2929
writer.WriteLine(x);
3030
writer.StartBlock($"namespace {codeNamespace.Name} {{");
3131
}
32+
conventions.WriteShortDescription(codeElement.Documentation.Description, writer);
3233
if (codeElement.Flags)
3334
writer.WriteLine("[Flags]");
34-
conventions.WriteShortDescription(codeElement.Documentation.Description, writer);
3535
conventions.WriteDeprecationAttribute(codeElement, writer);
3636
writer.StartBlock($"public enum {codeElement.Name.ToFirstCharacterUpperCase()} {{");
3737
var idx = 0;

tests/Kiota.Builder.Tests/KiotaBuilderTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ public async Task ParsesEnumDescriptions()
209209
Assert.NotNull(modelsNS);
210210
var enumDef = modelsNS.FindChildByName<CodeEnum>("StorageAccountType", false);
211211
Assert.NotNull(enumDef);
212+
Assert.False(enumDef.Flags);
213+
Assert.Equal(EnumStyle.Simple, enumDef.Style);
212214
var firstOption = enumDef.Options.First();
213215
Assert.Equal("+1", firstOption.SerializationName);
214216
Assert.Equal("plus_1", firstOption.Name);
@@ -223,6 +225,58 @@ public async Task ParsesEnumDescriptions()
223225
Assert.NotEmpty(thirdOption.Documentation.Description);
224226
Assert.Single(enumDef.Options.Where(static x => x.Name.Equals("Premium_LRS", StringComparison.OrdinalIgnoreCase)));
225227
}
228+
229+
[Fact]
230+
public async Task ParsesEnumFlagsInformation()
231+
{
232+
var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
233+
await using var fs = await GetDocumentStream(@"openapi: 3.0.1
234+
info:
235+
title: OData Service for namespace microsoft.graph
236+
description: This OData service is located at https://graph.microsoft.com/v1.0
237+
version: 1.0.1
238+
servers:
239+
- url: https://graph.microsoft.com/v1.0
240+
paths:
241+
/enumeration:
242+
get:
243+
responses:
244+
'200':
245+
content:
246+
application/json:
247+
schema:
248+
$ref: '#/components/schemas/StorageAccount'
249+
components:
250+
schemas:
251+
StorageAccount:
252+
type: object
253+
properties:
254+
accountType:
255+
$ref: '#/components/schemas/StorageAccountType'
256+
StorageAccountType:
257+
type: string
258+
enum:
259+
- Standard_LRS
260+
- Standard_ZRS
261+
- Standard_GRS
262+
- Standard_RAGRS
263+
- Premium_LRS
264+
- Premium_LRS
265+
x-ms-enum-flags:
266+
isFlags: true
267+
style: simple");
268+
var mockLogger = new Mock<ILogger<KiotaBuilder>>();
269+
var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient);
270+
var document = await builder.CreateOpenApiDocumentAsync(fs);
271+
var node = builder.CreateUriSpace(document);
272+
var codeModel = builder.CreateSourceModel(node);
273+
var modelsNS = codeModel.FindNamespaceByName("ApiSdk.models");
274+
Assert.NotNull(modelsNS);
275+
var enumDef = modelsNS.FindChildByName<CodeEnum>("StorageAccountType", false);
276+
Assert.NotNull(enumDef);
277+
Assert.True(enumDef.Flags);
278+
Assert.Equal(EnumStyle.Simple, enumDef.Style);
279+
}
226280
[Theory]
227281
[InlineData("description: 'Represents an Azure Active Directory user.'")]
228282
[InlineData("title: 'user'")]
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using Kiota.Builder.OpenApiExtensions;
4+
5+
using Microsoft.OpenApi;
6+
using Microsoft.OpenApi.Writers;
7+
8+
using Moq;
9+
10+
using Xunit;
11+
12+
namespace Kiota.Builder.Tests.OpenApiExtensions;
13+
14+
public class OpenApiEnumFlagsExtensionTests
15+
{
16+
[Fact]
17+
public void ExtensionNameMatchesExpected()
18+
{
19+
// Act
20+
string name = OpenApiEnumFlagsExtension.Name;
21+
string expectedName = "x-ms-enum-flags";
22+
23+
// Assert
24+
Assert.Equal(expectedName, name);
25+
}
26+
27+
[Fact]
28+
public void WritesDefaultValues()
29+
{
30+
// Arrange
31+
OpenApiEnumFlagsExtension extension = new();
32+
using TextWriter sWriter = new StringWriter();
33+
OpenApiJsonWriter writer = new(sWriter);
34+
35+
// Act
36+
extension.Write(writer, OpenApiSpecVersion.OpenApi3_0);
37+
string result = sWriter.ToString();
38+
39+
// Assert
40+
Assert.Contains("\"isFlags\": false", result);
41+
Assert.DoesNotContain("\"style\"", result);
42+
Assert.False(extension.IsFlags);
43+
Assert.Null(extension.Style);
44+
}
45+
46+
[Fact]
47+
public void WritesAllDefaultValues()
48+
{
49+
// Arrange
50+
OpenApiEnumFlagsExtension extension = new()
51+
{
52+
IsFlags = true
53+
};
54+
using TextWriter sWriter = new StringWriter();
55+
OpenApiJsonWriter writer = new(sWriter);
56+
57+
// Act
58+
extension.Write(writer, OpenApiSpecVersion.OpenApi3_0);
59+
string result = sWriter.ToString();
60+
61+
// Assert
62+
Assert.Contains("\"isFlags\": true", result);
63+
Assert.DoesNotContain("\"style\"", result);// writes form for unspecified style.
64+
Assert.True(extension.IsFlags);
65+
Assert.Null(extension.Style);
66+
}
67+
68+
[Fact]
69+
public void WritesAllValues()
70+
{
71+
// Arrange
72+
OpenApiEnumFlagsExtension extension = new()
73+
{
74+
IsFlags = true,
75+
Style = "form"
76+
};
77+
using TextWriter sWriter = new StringWriter();
78+
OpenApiJsonWriter writer = new(sWriter);
79+
80+
// Act
81+
extension.Write(writer, OpenApiSpecVersion.OpenApi3_0);
82+
string result = sWriter.ToString();
83+
84+
// Assert
85+
Assert.True(extension.IsFlags);
86+
Assert.NotNull(extension.Style);
87+
Assert.Contains("\"isFlags\": true", result);
88+
Assert.Contains("\"style\": \"form\"", result);
89+
}
90+
}
91+

0 commit comments

Comments
 (0)