From 6c42ae8934d00bce2e5c8ee40f0ca6d7b7408e17 Mon Sep 17 00:00:00 2001 From: X39 Date: Fri, 28 Nov 2025 02:57:56 +0100 Subject: [PATCH 1/3] fix: Handle nullable types in OpenAPI schemas and add corresponding unit tests. This commit provides a basic fix for #6776, unblocking nullable reference types for ASP.Net Core. --- .../Extensions/OpenApiSchemaExtensions.cs | 4 + .../Writers/CSharp/YamlToCSharpTests.cs | 251 ++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs index 8d8f93bf25..3f803399e4 100644 --- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs @@ -33,6 +33,8 @@ public static IEnumerable GetSchemaNames(this IOpenApiSchema schema, boo return schema switch { OpenApiSchemaReference reference => reference.Reference?.Id, + // Quickfix for https://github.com/microsoft/kiota/issues/6776 ToDo: Properly handle nullable types in accordance with OpenAPI 3. + OpenApiSchema {OneOf: [OpenApiSchema{Type: JsonSchemaType.Null}, OpenApiSchemaReference reference]} => reference.Reference?.Id, OpenApiSchema s when s.GetMergedSchemaOriginalReferenceId() is string originalReferenceId => originalReferenceId, _ => null, }; @@ -59,6 +61,8 @@ public static bool IsReferencedSchema(this IOpenApiSchema schema) return schema switch { OpenApiSchemaReference => true, + // Quickfix for https://github.com/microsoft/kiota/issues/6776 ToDo: Properly handle nullable types in accordance with OpenAPI 3. + OpenApiSchema {OneOf: [OpenApiSchema{Type: JsonSchemaType.Null}, OpenApiSchemaReference reference]} => true, _ => false, }; } diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs new file mode 100644 index 0000000000..b0d861df07 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs @@ -0,0 +1,251 @@ +#nullable enable +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Configuration; +using Kiota.Builder.Writers.CSharp; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi; +using Moq; +using Xunit; + +namespace Kiota.Builder.Tests.Writers.CSharp; + +public sealed class YamlToCSharpTests : IDisposable +{ + public void Dispose() + { + _httpClient.Dispose(); + } + + private readonly HttpClient _httpClient = new(); + + public const string NullableInOpenApi3 = """ + { + "openapi": "3.1.1", + "info": { + "title": "WebApplication2 | v1", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:5088/" + } + ], + "paths": { + "/Sample": { + "get": { + "tags": [ + "Sample" + ], + "operationId": "GetWeatherForecast", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Sample" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Sample" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Sample" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Nested": { + "required": [ + "value" + ], + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "Sample": { + "required": [ + "immediate", + "nestedNonNullable", + "nestedNullable" + ], + "type": "object", + "properties": { + "immediate": { + "type": "string" + }, + "nestedNonNullable": { + "$ref": "#/components/schemas/Nested" + }, + "nestedNullable": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Nested" + } + ] + } + } + } + } + }, + "tags": [ + { + "name": "Sample" + } + ] + } + """; + + public const string NullableInOpenApi3_Models_Sample = """ + // + #pragma warning disable CS0618 + using Microsoft.Kiota.Abstractions.Extensions; + using Microsoft.Kiota.Abstractions.Serialization; + using System.Collections.Generic; + using System.IO; + using System; + namespace ApiSdk.Models + { + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class Sample : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The immediate property + #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + #nullable enable + public string? Immediate { get; set; } + #nullable restore + #else + public string Immediate { get; set; } + #endif + /// The nestedNonNullable property + #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + #nullable enable + public global::ApiSdk.Models.Nested? NestedNonNullable { get; set; } + #nullable restore + #else + public global::ApiSdk.Models.Nested NestedNonNullable { get; set; } + #endif + /// The nestedNullable property + #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + #nullable enable + public global::ApiSdk.Models.Nested? NestedNullable { get; set; } + #nullable restore + #else + public global::ApiSdk.Models.Nested NestedNullable { get; set; } + #endif + /// + /// Instantiates a new and sets the default values. + /// + public Sample() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::ApiSdk.Models.Sample CreateFromDiscriminatorValue(IParseNode parseNode) + { + if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode)); + return new global::ApiSdk.Models.Sample(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "immediate", n => { Immediate = n.GetStringValue(); } }, + { "nestedNonNullable", n => { NestedNonNullable = n.GetObjectValue(global::ApiSdk.Models.Nested.CreateFromDiscriminatorValue); } }, + { "nestedNullable", n => { NestedNullable = n.GetObjectValue(global::ApiSdk.Models.Nested.CreateFromDiscriminatorValue); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("immediate", Immediate); + writer.WriteObjectValue("nestedNonNullable", NestedNonNullable); + writer.WriteObjectValue("nestedNullable", NestedNullable); + writer.WriteAdditionalData(AdditionalData); + } + } + } + #pragma warning restore CS0618 + + """; + + + [Theory] + [InlineData(NullableInOpenApi3, new[] {"Models/Sample.cs", NullableInOpenApi3_Models_Sample})] + public async Task CreateOpenApiDocumentWithResultAsync_ReturnsDiagnostics(string input, + string[] expectedData) + { + if (expectedData.Length % 2 != 0) + Assert.Fail("Invalid test data"); + var expectedList = expectedData.Chunk(2).Select(e => (fileName: e[0], expected: e[1])).ToList(); + + string? tempInputFile = null; + string? tempOutputDirectory = null; + try + { + tempInputFile = Path.GetTempFileName(); + tempOutputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempOutputDirectory); + await File.WriteAllTextAsync(tempInputFile, input); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, + new GenerationConfiguration + { + ClientClassName = "Graph", + OpenAPIFilePath = tempInputFile, + OutputPath = tempOutputDirectory, + Language = GenerationLanguage.CSharp + }, _httpClient); + var result = await builder.GenerateClientAsync(default); + Assert.True(result); + foreach (var (fileName, expected) in expectedList) + { + var contents = await File.ReadAllTextAsync(Path.Combine(tempOutputDirectory, fileName)); + Assert.Equal(expected, contents); + } + } + finally + { + if (tempInputFile is not null && File.Exists(tempInputFile)) + File.Delete(tempInputFile); + if (tempOutputDirectory is not null && Directory.Exists(tempOutputDirectory)) + Directory.Delete(tempOutputDirectory, true); + } + } +} From 89e9c09a4f2d33690a9e4737b75a8bb29d8d3add Mon Sep 17 00:00:00 2001 From: X39 Date: Sat, 29 Nov 2025 15:06:42 +0100 Subject: [PATCH 2/3] fix: nullable enum support in OpenAPI schemas --- .../Extensions/OpenApiSchemaExtensions.cs | 2 +- .../Writers/CSharp/YamlToCSharpTests.cs | 176 +++++++++++++++++- 2 files changed, 174 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs index 3f803399e4..72408a9758 100644 --- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs @@ -322,7 +322,7 @@ public static bool IsSemanticallyMeaningful(this IOpenApiSchema schema, bool ign return schema.HasAnyProperty() || (!ignoreEnums && schema.Enum is { Count: > 0 }) || (!ignoreArrays && schema.Items != null) || - (!ignoreType && schema.Type is not null && + (!ignoreType && schema.Type is not null and not JsonSchemaType.Null && ((ignoreNullableObjects && !schema.IsObjectType()) || !ignoreNullableObjects)) || !string.IsNullOrEmpty(schema.Format) || diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs index b0d861df07..56a84f434a 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs @@ -23,7 +23,7 @@ public void Dispose() private readonly HttpClient _httpClient = new(); - public const string NullableInOpenApi3 = """ + public const string NullableObjectInOpenApi3 = """ { "openapi": "3.1.1", "info": { @@ -116,7 +116,7 @@ public void Dispose() } """; - public const string NullableInOpenApi3_Models_Sample = """ + public const string NullableObjectInOpenApi3_Models_Sample = """ // #pragma warning disable CS0618 using Microsoft.Kiota.Abstractions.Extensions; @@ -205,9 +205,179 @@ public virtual void Serialize(ISerializationWriter writer) """; + public const string NullableEnumInOpenApi3 = """ + { + "openapi": "3.1.1", + "info": { + "title": "WebApplication2 | v1", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:5088/" + } + ], + "paths": { + "/Sample": { + "get": { + "tags": [ + "Sample" + ], + "operationId": "GetWeatherForecast", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SampleEnum" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SampleEnum" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SampleEnum" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ESample": { + "$comment": "Applied FixEnumsSchemaTransformer", + "enum": [ + "A", + "B" + ], + "type": "integer", + "x-ms-enum": { + "name": "ESample", + "modelAsString": false, + "values": [ + { + "name": "A", + "value": 0, + "description": "" + }, + { + "name": "B", + "value": 1, + "description": "" + } + ] + } + }, + "SampleEnum": { + "required": [ + "notNullable", + "nullable" + ], + "type": "object", + "properties": { + "notNullable": { + "$ref": "#/components/schemas/ESample" + }, + "nullable": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ESample" + } + ] + } + } + } + } + }, + "tags": [ + { + "name": "Sample" + } + ] + } + """; + + public const string NullableEnumInOpenApi3_Models_SampleEnum = """ + // + #pragma warning disable CS0618 + using Microsoft.Kiota.Abstractions.Extensions; + using Microsoft.Kiota.Abstractions.Serialization; + using System.Collections.Generic; + using System.IO; + using System; + namespace ApiSdk.Models + { + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class SampleEnum : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The notNullable property + public global::ApiSdk.Models.ESample? NotNullable { get; set; } + /// The nullable property + public global::ApiSdk.Models.ESample? Nullable { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public SampleEnum() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::ApiSdk.Models.SampleEnum CreateFromDiscriminatorValue(IParseNode parseNode) + { + if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode)); + return new global::ApiSdk.Models.SampleEnum(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "notNullable", n => { NotNullable = n.GetEnumValue(); } }, + { "nullable", n => { Nullable = n.GetEnumValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer)); + writer.WriteEnumValue("notNullable", NotNullable); + writer.WriteEnumValue("nullable", Nullable); + writer.WriteAdditionalData(AdditionalData); + } + } + } + #pragma warning restore CS0618 + + """; + [Theory] - [InlineData(NullableInOpenApi3, new[] {"Models/Sample.cs", NullableInOpenApi3_Models_Sample})] + [InlineData(NullableObjectInOpenApi3, new[] {"Models/Sample.cs", NullableObjectInOpenApi3_Models_Sample})] + [InlineData(NullableEnumInOpenApi3, new[] {"Models/SampleEnum.cs", NullableEnumInOpenApi3_Models_SampleEnum})] public async Task CreateOpenApiDocumentWithResultAsync_ReturnsDiagnostics(string input, string[] expectedData) { From 592710aa6905adea20c73192cf204a3b6f83f2fa Mon Sep 17 00:00:00 2001 From: X39 Date: Sat, 29 Nov 2025 16:20:56 +0100 Subject: [PATCH 3/3] fix: support integer-based enums for C#. --- src/Kiota.Builder/CodeDOM/CodeEnum.cs | 6 ++ src/Kiota.Builder/KiotaBuilder.cs | 1 + .../Writers/CSharp/CodeMethodWriter.cs | 73 ++++++++++++++++--- .../Writers/CSharp/YamlToCSharpTests.cs | 14 ++-- 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeEnum.cs b/src/Kiota.Builder/CodeDOM/CodeEnum.cs index c4c48550e5..5352b2a186 100644 --- a/src/Kiota.Builder/CodeDOM/CodeEnum.cs +++ b/src/Kiota.Builder/CodeDOM/CodeEnum.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi; namespace Kiota.Builder.CodeDOM; #pragma warning disable CA1711 @@ -42,4 +43,9 @@ public CodeConstant? CodeEnumObject { get; set; } + + public JsonSchemaType? BackingType + { + get; set; + } } diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 94a77921f2..28fa2eb7a5 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -2021,6 +2021,7 @@ private CodeElement AddModelDeclarationIfDoesntExist(OpenApiUrlTreeNode currentN currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel), }, Deprecation = schema.GetDeprecationInformation(), + BackingType = schema.Type, }; SetEnumOptions(schema, newEnum); return currentNamespace.AddEnum(newEnum).First(); diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index f80d43ceb3..0e6befab48 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -4,6 +4,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; using Kiota.Builder.OrderComparers; +using Microsoft.OpenApi; namespace Kiota.Builder.Writers.CSharp; @@ -141,10 +142,21 @@ private void WriteFactoryMethodBodyForUnionModel(CodeMethod codeElement, CodeCla } else if (propertyType.TypeDefinition is CodeClass && propertyType.IsCollection || propertyType.TypeDefinition is null || propertyType.TypeDefinition is CodeEnum) { + var readerReference = parseNodeParameter.Name.ToFirstCharacterLowerCase(); + var selfReference = $"{ResultVarName}.{property.Name.ToFirstCharacterUpperCase()}"; + var deserializationMethodName = GetDeserializationMethodName(propertyType, codeElement); var typeName = conventions.GetTypeString(propertyType, codeElement, true, (propertyType.TypeDefinition is CodeEnum || conventions.IsPrimitiveType(propertyType.Name)) && propertyType.CollectionKind is not CodeTypeBase.CodeTypeCollectionKind.None); var valueVarName = $"{property.Name.ToFirstCharacterLowerCase()}Value"; - writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({parseNodeParameter.Name.ToFirstCharacterLowerCase()}.{GetDeserializationMethodName(propertyType, codeElement)} is {typeName} {valueVarName})"); - writer.WriteBlock(lines: $"{ResultVarName}.{property.Name.ToFirstCharacterUpperCase()} = {valueVarName};"); + if (propertyType.TypeDefinition is CodeType { TypeDefinition: CodeEnum {BackingType: JsonSchemaType.Integer}}) + { + writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({readerReference}.{deserializationMethodName} is int {valueVarName})"); + writer.WriteBlock(lines: $"{selfReference} = ({typeName}){valueVarName};"); + } + else + { + writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({readerReference}.{deserializationMethodName} is {typeName} {valueVarName})"); + writer.WriteBlock(lines: $"{selfReference} = {valueVarName};"); + } } if (!includeElse) includeElse = true; @@ -162,10 +174,21 @@ private void WriteFactoryMethodBodyForIntersectionModel(CodeMethod codeElement, { if (property.Type is CodeType propertyType) { + var readerReference = parseNodeParameter.Name.ToFirstCharacterLowerCase(); + var selfReference = $"{ResultVarName}.{property.Name.ToFirstCharacterUpperCase()}"; + var deserializationMethodName = GetDeserializationMethodName(propertyType, codeElement); var typeName = conventions.GetTypeString(propertyType, codeElement, true, propertyType.TypeDefinition is CodeEnum && propertyType.CollectionKind is not CodeTypeBase.CodeTypeCollectionKind.None); var valueVarName = $"{property.Name.ToFirstCharacterLowerCase()}Value"; - writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({parseNodeParameter.Name.ToFirstCharacterLowerCase()}.{GetDeserializationMethodName(propertyType, codeElement)} is {typeName} {valueVarName})"); - writer.WriteBlock(lines: $"{ResultVarName}.{property.Name.ToFirstCharacterUpperCase()} = {valueVarName};"); + if (propertyType.TypeDefinition is CodeType { TypeDefinition: CodeEnum {BackingType: JsonSchemaType.Integer}}) + { + writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({readerReference}.{deserializationMethodName} is int {valueVarName})"); + writer.WriteBlock(lines: $"{selfReference} = ({typeName}){valueVarName};"); + } + else + { + writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({readerReference}.{deserializationMethodName} is {typeName} {valueVarName})"); + writer.WriteBlock(lines: $"{selfReference} = {valueVarName};"); + } } if (!includeElse) includeElse = true; @@ -349,7 +372,13 @@ private void WriteDeserializerBodyForInheritedModel(bool shouldHide, CodeMethod .Where(static x => !x.ExistsInBaseType) .OrderBy(static x => x.Name, StringComparer.Ordinal)) { - writer.WriteLine($"{{ \"{otherProp.WireName}\", n => {{ {otherProp.Name.ToFirstCharacterUpperCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeElement)}; }} }},"); + if (otherProp is {Type: CodeType { TypeDefinition: CodeEnum {BackingType: JsonSchemaType.Integer}} propertyType}) + { + var typeName = conventions.GetTypeString(propertyType, codeElement, true, (propertyType.TypeDefinition is CodeEnum || conventions.IsPrimitiveType(propertyType.Name)) && propertyType.CollectionKind is not CodeTypeBase.CodeTypeCollectionKind.None); + writer.WriteLine($"{{ \"{otherProp.WireName}\", n => {{ {otherProp.Name.ToFirstCharacterUpperCase()} = ({typeName}?) n.{GetDeserializationMethodName(otherProp.Type, codeElement)}; }} }},"); + } + else + writer.WriteLine($"{{ \"{otherProp.WireName}\", n => {{ {otherProp.Name.ToFirstCharacterUpperCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeElement)}; }} }},"); } writer.CloseBlock("};"); } @@ -370,7 +399,10 @@ private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod me return $"GetCollectionOfObjectValues<{propertyType}>({propertyType}.CreateFromDiscriminatorValue){collectionMethod}"; } else if (currentType.TypeDefinition is CodeEnum enumType) - return $"GetEnumValue<{enumType.GetFullName()}>()"; + if (enumType.BackingType is JsonSchemaType.Integer) + return $"GetIntValue()"; + else + return $"GetEnumValue<{enumType.GetFullName()}>()"; } return propertyType switch { @@ -482,7 +514,10 @@ private void WriteSerializerBodyForInheritedModel(bool shouldHide, CodeMethod me .OrderBy(static x => x.Name)) { var serializationMethodName = GetSerializationMethodName(otherProp.Type, method); - writer.WriteLine($"writer.{serializationMethodName}(\"{otherProp.WireName}\", {otherProp.Name.ToFirstCharacterUpperCase()});"); + if (otherProp.Type is CodeType{TypeDefinition: CodeEnum {BackingType: JsonSchemaType.Integer}}) + writer.WriteLine($"writer.{serializationMethodName}(\"{otherProp.WireName}\", (int?){otherProp.Name.ToFirstCharacterUpperCase()});"); + else + writer.WriteLine($"writer.{serializationMethodName}(\"{otherProp.WireName}\", {otherProp.Name.ToFirstCharacterUpperCase()});"); } } private void WriteSerializerBodyForUnionModel(CodeMethod method, CodeClass parentClass, LanguageWriter writer) @@ -494,8 +529,12 @@ private void WriteSerializerBodyForUnionModel(CodeMethod method, CodeClass paren .OrderBy(static x => x, CodePropertyTypeForwardComparer) .ThenBy(static x => x.Name)) { + var serializationMethodName = GetSerializationMethodName(otherProp.Type, method); writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({otherProp.Name.ToFirstCharacterUpperCase()} != null)"); - writer.WriteBlock(lines: $"writer.{GetSerializationMethodName(otherProp.Type, method)}(null, {otherProp.Name.ToFirstCharacterUpperCase()});"); + if (otherProp.Type is CodeType{TypeDefinition: CodeEnum {BackingType: JsonSchemaType.Integer}}) + writer.WriteBlock(lines: $"writer.{serializationMethodName}(\"{otherProp.WireName}\", (int?){otherProp.Name.ToFirstCharacterUpperCase()});"); + else + writer.WriteBlock(lines: $"writer.{serializationMethodName}(null, {otherProp.Name.ToFirstCharacterUpperCase()});"); if (!includeElse) includeElse = true; } @@ -510,8 +549,12 @@ private void WriteSerializerBodyForIntersectionModel(CodeMethod method, CodeClas .OrderBy(static x => x, CodePropertyTypeBackwardComparer) .ThenBy(static x => x.Name)) { + var serializationMethodName = GetSerializationMethodName(otherProp.Type, method); writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({otherProp.Name.ToFirstCharacterUpperCase()} != null)"); - writer.WriteBlock(lines: $"writer.{GetSerializationMethodName(otherProp.Type, method)}(null, {otherProp.Name.ToFirstCharacterUpperCase()});"); + if (otherProp.Type is CodeType{TypeDefinition: CodeEnum {BackingType: JsonSchemaType.Integer}}) + writer.WriteBlock(lines: $"writer.{serializationMethodName}(\"{otherProp.WireName}\", (int?){otherProp.Name.ToFirstCharacterUpperCase()});"); + else + writer.WriteBlock(lines: $"writer.{serializationMethodName}(null, {otherProp.Name.ToFirstCharacterUpperCase()});"); if (!includeElse) includeElse = true; } @@ -529,7 +572,12 @@ private void WriteSerializerBodyForIntersectionModel(CodeMethod method, CodeClas .Select(static x => x.Name.ToFirstCharacterUpperCase()) .OrderBy(static x => x) .Aggregate(static (x, y) => $"{x}, {y}"); - writer.WriteLine($"writer.{GetSerializationMethodName(complexProperties.First().Type, method)}(null, {propertiesNames});"); + var prop = complexProperties.First(); + var serializationMethodName = GetSerializationMethodName(prop.Type, method); + if (prop.Type is CodeType{TypeDefinition: CodeEnum {BackingType: JsonSchemaType.Integer}}) + writer.WriteLine($"writer.{serializationMethodName}(\"{prop.WireName}\", (int?){prop.Name.ToFirstCharacterUpperCase()});"); + else + writer.WriteLine($"writer.{GetSerializationMethodName(complexProperties.First().Type, method)}(null, {propertiesNames});"); if (includeElse) { writer.CloseBlock(); @@ -678,7 +726,10 @@ private string GetSerializationMethodName(CodeTypeBase propType, CodeMethod meth else return $"WriteCollectionOfObjectValues<{propertyType}>"; else if (currentType.TypeDefinition is CodeEnum enumType) - return $"WriteEnumValue<{enumType.GetFullName()}>"; + if (enumType.BackingType is JsonSchemaType.Integer) + return $"WriteIntValue"; + else + return $"WriteEnumValue<{enumType.GetFullName()}>"; } return propertyType switch diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs index 56a84f434a..41b80b92b7 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/YamlToCSharpTests.cs @@ -353,8 +353,8 @@ public virtual IDictionary> GetFieldDeserializers() { return new Dictionary> { - { "notNullable", n => { NotNullable = n.GetEnumValue(); } }, - { "nullable", n => { Nullable = n.GetEnumValue(); } }, + { "notNullable", n => { NotNullable = (global::ApiSdk.Models.ESample?) n.GetIntValue(); } }, + { "nullable", n => { Nullable = (global::ApiSdk.Models.ESample?) n.GetIntValue(); } }, }; } /// @@ -364,8 +364,8 @@ public virtual IDictionary> GetFieldDeserializers() public virtual void Serialize(ISerializationWriter writer) { if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer)); - writer.WriteEnumValue("notNullable", NotNullable); - writer.WriteEnumValue("nullable", Nullable); + writer.WriteIntValue("notNullable", (int?)NotNullable); + writer.WriteIntValue("nullable", (int?)Nullable); writer.WriteAdditionalData(AdditionalData); } } @@ -376,9 +376,9 @@ public virtual void Serialize(ISerializationWriter writer) [Theory] - [InlineData(NullableObjectInOpenApi3, new[] {"Models/Sample.cs", NullableObjectInOpenApi3_Models_Sample})] - [InlineData(NullableEnumInOpenApi3, new[] {"Models/SampleEnum.cs", NullableEnumInOpenApi3_Models_SampleEnum})] - public async Task CreateOpenApiDocumentWithResultAsync_ReturnsDiagnostics(string input, + [InlineData("NullableObjectInOpenApi3", NullableObjectInOpenApi3, new[] {"Models/Sample.cs", NullableObjectInOpenApi3_Models_Sample})] + [InlineData("NullableEnumInOpenApi3", NullableEnumInOpenApi3, new[] {"Models/SampleEnum.cs", NullableEnumInOpenApi3_Models_SampleEnum})] + public async Task CreateOpenApiDocumentWithResultAsync_ReturnsDiagnostics(string description, string input, string[] expectedData) { if (expectedData.Length % 2 != 0)