From 9a1891e8a61244c159074f0de8524242647b8923 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 10:03:00 -0500 Subject: [PATCH] - fixes enums projection Signed-off-by: Vincent Biret --- CHANGELOG.md | 1 + .../Extensions/OpenApiSchemaExtensions.cs | 12 +- src/Kiota.Builder/KiotaBuilder.cs | 2 +- .../OpenApiSchemaExtensionsTests.cs | 4 +- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 126 ++++++++++++++++++ 5 files changed, 136 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c34afd5d9d..6107044483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Fixed a bug where the discriminator validation rule would report false positives on nullable union types. +- Fixed a bug where the order of enum declaration might results in a missing enum type. [#3935](https://github.com/microsoft/kiota/issues/3935) ## [1.9.1] - 2023-12-13 diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs index 2a37aaa315..c8ba6495c1 100644 --- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs @@ -125,17 +125,17 @@ public static bool IsEnum(this OpenApiSchema schema) { if (schema is null) return false; return schema.Enum.OfType().Any(static x => !string.IsNullOrEmpty(x.Value)) && - (string.IsNullOrEmpty(schema.Type) || "string".Equals(schema.Type, StringComparison.OrdinalIgnoreCase)) || - schema.AnyOf.Where(static x => x.IsSemanticallyMeaningful(true)).Count(static x => x.IsEnum()) == 1 && !schema.AnyOf.Where(static x => x.IsSemanticallyMeaningful(true)).Any(static x => !x.IsEnum()) || - schema.OneOf.Where(static x => x.IsSemanticallyMeaningful(true)).Count(static x => x.IsEnum()) == 1 && !schema.OneOf.Where(static x => x.IsSemanticallyMeaningful(true)).Any(static x => !x.IsEnum()) - ; // number and boolean enums are not supported + (string.IsNullOrEmpty(schema.Type) || "string".Equals(schema.Type, StringComparison.OrdinalIgnoreCase)); // number and boolean enums are not supported } public static bool IsComposedEnum(this OpenApiSchema schema) { - return (schema.IsInclusiveUnion() && schema.AnyOf.Any(static x => x.IsEnum())) || (schema.IsExclusiveUnion() && schema.OneOf.Any(static x => x.IsEnum())); + if (schema is null) return false; + return schema.AnyOf.Count(static x => !x.IsSemanticallyMeaningful(true)) == 1 && schema.AnyOf.Count(static x => x.IsEnum()) == 1 || + schema.OneOf.Count(static x => !x.IsSemanticallyMeaningful(true)) == 1 && schema.OneOf.Count(static x => x.IsEnum()) == 1; } - private static bool IsSemanticallyMeaningful(this OpenApiSchema schema, bool ignoreNullableObjects = false) + public static bool IsSemanticallyMeaningful(this OpenApiSchema schema, bool ignoreNullableObjects = false) { + if (schema is null) return false; return schema.Properties.Any() || schema.Items != null || (!string.IsNullOrEmpty(schema.Type) && diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index c876f9d66d..58d000a9f0 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1831,7 +1831,7 @@ private CodeTypeBase CreateComposedModelDeclaration(OpenApiUrlTreeNode currentNo if (!string.IsNullOrEmpty(schema.Type) || !string.IsNullOrEmpty(schema.Format)) return GetPrimitiveType(schema, string.Empty); if (schema.AnyOf.Any() || schema.OneOf.Any() || schema.AllOf.Any()) // we have an empty node because of some local override for schema properties and need to unwrap it. - return CreateModelDeclarations(currentNode, (schema.AnyOf.FirstOrDefault() ?? schema.OneOf.FirstOrDefault() ?? schema.AllOf.FirstOrDefault())!, operation, parentElement, suffixForInlineSchema, response, typeNameForInlineSchema, isRequestBody); + return CreateModelDeclarations(currentNode, (schema.AnyOf.FirstOrDefault(static x => x.IsSemanticallyMeaningful(true)) ?? schema.OneOf.FirstOrDefault(static x => x.IsSemanticallyMeaningful(true)) ?? schema.AllOf.FirstOrDefault(static x => x.IsSemanticallyMeaningful(true)))!, operation, parentElement, suffixForInlineSchema, response, typeNameForInlineSchema, isRequestBody); return null; } private CodeTypeBase? CreateCollectionModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation? operation, CodeNamespace codeNamespace, string typeNameForInlineSchema, bool isRequestBody) diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs index 1c1985b8a5..19accd89df 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs @@ -557,7 +557,7 @@ public void IsEnumIgnoresNullableUnions() } } }; - Assert.True(schema.IsEnum()); + Assert.False(schema.IsEnum()); } [Fact] public void IsEnumFailsOnNullableInheritance() @@ -591,7 +591,7 @@ public void IsEnumIgnoresNullableExclusiveUnions() } } }; - Assert.True(schema.IsEnum()); + Assert.False(schema.IsEnum()); } private static readonly OpenApiSchema numberSchema = new OpenApiSchema { diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 5d7da8d05d..6807d654ba 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -7299,6 +7299,132 @@ public async Task InheritanceWithAllOfWith3Parts() Assert.Single(resultClass.Properties.Where(x => x.Name.Equals("groupprop1", StringComparison.OrdinalIgnoreCase))); Assert.Single(resultClass.Properties.Where(x => x.Name.Equals("groupprop2", StringComparison.OrdinalIgnoreCase))); } + [Fact] + public async Task EnumsWithNullableDoesNotResultInInlineType() + { + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await using var fs = await GetDocumentStream(@"openapi: 3.0.1 +info: + title: OData Service for namespace microsoft.graph + description: This OData service is located at https://graph.microsoft.com/v1.0 + version: 1.0.1 +servers: + - url: https://graph.microsoft.com/v1.0 +paths: + '/communications/calls/{call-id}/reject': + post: + requestBody: + description: Action parameters + content: + application/json: + schema: + type: object + properties: + reason: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.rejectReason' + - type: object + nullable: true + callbackUri: + type: string + nullable: true + required: true + responses: + '204': + description: Success, +components: + schemas: + microsoft.graph.rejectReason: + title: rejectReason + enum: + - none + - busy + - forbidden + - unknownFutureValue + type: string"); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath, IncludeAdditionalData = false }, _httpClient); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + Assert.NotNull(codeModel); + var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.communications.calls.item.reject"); + Assert.NotNull(requestBuilderNS); + var requestBuilderClass = requestBuilderNS.FindChildByName("RejectRequestBuilder", false); + Assert.NotNull(requestBuilderClass); + var reasonCandidate = requestBuilderNS.FindChildByName("RejectPostRequestBody_reason", false); + Assert.Null(reasonCandidate); + var modelsNS = codeModel.FindNamespaceByName("ApiSdk.Models"); + Assert.NotNull(modelsNS); + var graphModelsNS = modelsNS.FindNamespaceByName("ApiSdk.Models.Microsoft.Graph"); + Assert.NotNull(graphModelsNS); + var rejectReasonEnum = graphModelsNS.FindChildByName("RejectReason", false); + Assert.NotNull(rejectReasonEnum); + } + + [Fact] + public async Task EnumsWithNullableDoesNotResultInInlineTypeInReveredOrder() + { + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await using var fs = await GetDocumentStream(@"openapi: 3.0.1 +info: + title: OData Service for namespace microsoft.graph + description: This OData service is located at https://graph.microsoft.com/v1.0 + version: 1.0.1 +servers: + - url: https://graph.microsoft.com/v1.0 +paths: + '/communications/calls/{call-id}/reject': + post: + requestBody: + description: Action parameters + content: + application/json: + schema: + type: object + properties: + reason: + anyOf: + - type: object + nullable: true + - $ref: '#/components/schemas/microsoft.graph.rejectReason' + callbackUri: + type: string + nullable: true + required: true + responses: + '204': + description: Success, +components: + schemas: + microsoft.graph.rejectReason: + title: rejectReason + enum: + - none + - busy + - forbidden + - unknownFutureValue + type: string"); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath, IncludeAdditionalData = false }, _httpClient); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + Assert.NotNull(codeModel); + var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.communications.calls.item.reject"); + Assert.NotNull(requestBuilderNS); + var requestBuilderClass = requestBuilderNS.FindChildByName("RejectRequestBuilder", false); + Assert.NotNull(requestBuilderClass); + var reasonCandidate = requestBuilderNS.FindChildByName("RejectPostRequestBody_reason", false); + Assert.Null(reasonCandidate); + var modelsNS = codeModel.FindNamespaceByName("ApiSdk.Models"); + Assert.NotNull(modelsNS); + var graphModelsNS = modelsNS.FindNamespaceByName("ApiSdk.Models.Microsoft.Graph"); + Assert.NotNull(graphModelsNS); + var rejectReasonEnum = graphModelsNS.FindChildByName("RejectReason", false); + Assert.NotNull(rejectReasonEnum); + } + [Fact] public async Task AnyTypeResponse() {