From 7dd639e573799833b3a7c20c51a92e2402995af9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 14 Mar 2022 13:39:40 -0400 Subject: [PATCH 1/3] - fixes #1270 a bug where missing schema title would make union types fail --- CHANGELOG.md | 2 + .../Extensions/OpenApiSchemaExtensions.cs | 6 +- src/Kiota.Builder/KiotaBuilder.cs | 7 + .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 143 ++++++++++++++++++ 4 files changed, 155 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0efdf57dcb..9612d77816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug where unnecessary namespaces would be added to models generation #1273 - Fixed a bug where Go byte arrays would not write deserializers properly. - Fixed a bug where integers would not be recognized when type is not number. +- Fixed a bug where union types with primitive member types would fail to generate #1270 +- Fixed a bug where union types with inline schema member types would fail to generate #1270 ## [0.0.18] - 2022-03-14 diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs index cdb967e700..211780080d 100644 --- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs @@ -13,7 +13,7 @@ public static IEnumerable GetSchemaTitles(this OpenApiSchema schema) { else if(schema.Items != null) return schema.Items.GetSchemaTitles(); else if(!string.IsNullOrEmpty(schema.Title)) - return new List{ schema.Title }; + return new string[] { schema.Title }; else if(schema.AnyOf.Any()) return schema.AnyOf.FlattenIfRequired(classNamesFlattener); else if(schema.AllOf.Any()) @@ -21,9 +21,9 @@ public static IEnumerable GetSchemaTitles(this OpenApiSchema schema) { else if(schema.OneOf.Any()) return schema.OneOf.FlattenIfRequired(classNamesFlattener); else if(!string.IsNullOrEmpty(schema.Reference?.Id)) - return new List{schema.Reference.Id.Split('/').Last().Split('.').Last()}; + return new string[] {schema.Reference.Id.Split('/').Last().Split('.').Last()}; else if(!string.IsNullOrEmpty(schema.Xml?.Name)) - return new List{schema.Xml.Name}; + return new string[] {schema.Xml.Name}; else return Enumerable.Empty(); } public static IEnumerable GetNonEmptySchemas(this OpenApiSchema schema) { diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 0aa2b138ae..acf595d35e 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -810,12 +810,19 @@ private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode currentNode, var unionType = new CodeUnionType { Name = currentNode.GetClassName(operation: operation, suffix: suffixForInlineSchema, schema: schema), }; + var membersWithNoName = 0; foreach(var currentSchema in schemas) { var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id); var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); if(shortestNamespace == null) shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); var className = currentSchema.GetSchemaTitle(); + if (string.IsNullOrEmpty(className)) + if(GetPrimitiveType(currentSchema) is CodeType primitiveType && !string.IsNullOrEmpty(primitiveType.Name)) { + unionType.AddType(primitiveType); + continue; + } else + className = $"{unionType.Name}Member{++membersWithNoName}"; var codeDeclaration = AddModelDeclarationIfDoesntExist(currentNode, currentSchema, className, shortestNamespace); unionType.AddType(new CodeType { TypeDefinition = codeDeclaration, diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index c0b2f33345..0789d91db0 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -970,6 +970,149 @@ public void AddsDiscriminatorMappings(){ Assert.NotNull(castType.TypeDefinition); Assert.Equal(directoryObjectClass, castType.TypeDefinition); } + [Fact] + public void UnionOfPrimitiveTypesWorks() { + var simpleObjet = new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string" + } + } + }, + Reference = new OpenApiReference { + Id = "subNS.simpleObject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument() { + Paths = new OpenApiPaths() { + ["unionType"] = new OpenApiPathItem() { + Operations = { + [OperationType.Get] = new OpenApiOperation() { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { + Content = { + ["application/json"] = new OpenApiMediaType { + Schema = new OpenApiSchema { + OneOf = new List { + simpleObjet, + new OpenApiSchema { + Type = "number" + } + } + } + } + } + }, + } + } + } + } + }, + Components = new OpenApiComponents() { + Schemas = new Dictionary { + { + "subNS.simpleObject", simpleObjet + } + } + }, + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" }); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.unionType"); + Assert.NotNull(requestBuilderNS); + var requestBuilderClass = requestBuilderNS.FindChildByName("unionTypeRequestBuilder", false); + Assert.NotNull(requestBuilderClass); + var requestExecutorMethod = requestBuilderClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); + Assert.NotNull(requestExecutorMethod); + var executorReturnType = requestExecutorMethod.ReturnType as CodeUnionType; + Assert.NotNull(executorReturnType); + Assert.Equal(2, executorReturnType.Types.Count()); + var typeNames = executorReturnType.Types.Select(x => x.Name).ToHashSet(StringComparer.OrdinalIgnoreCase); + Assert.Contains("simpleObject", typeNames); + Assert.Contains("number", typeNames); + } + [Fact] + public void UnionOfInlineSchemasWorks() { + var simpleObjet = new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string" + } + } + }, + Reference = new OpenApiReference { + Id = "subNS.simpleObject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument() { + Paths = new OpenApiPaths() { + ["unionType"] = new OpenApiPathItem() { + Operations = { + [OperationType.Get] = new OpenApiOperation() { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { + Content = { + ["application/json"] = new OpenApiMediaType { + Schema = new OpenApiSchema { + OneOf = new List { + simpleObjet, + new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "name", new OpenApiSchema { + Type = "string" + } + } + } + } + } + } + } + } + }, + } + } + } + } + }, + Components = new OpenApiComponents() { + Schemas = new Dictionary { + { + "subNS.simpleObject", simpleObjet + } + } + }, + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" }); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.unionType"); + Assert.NotNull(requestBuilderNS); + var requestBuilderClass = requestBuilderNS.FindChildByName("unionTypeRequestBuilder", false); + Assert.NotNull(requestBuilderClass); + var requestExecutorMethod = requestBuilderClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); + Assert.NotNull(requestExecutorMethod); + var executorReturnType = requestExecutorMethod.ReturnType as CodeUnionType; + Assert.NotNull(executorReturnType); + Assert.Equal(2, executorReturnType.Types.Count()); + var typeNames = executorReturnType.Types.Select(x => x.Name).ToHashSet(StringComparer.OrdinalIgnoreCase); + Assert.Contains("simpleObject", typeNames); + Assert.Contains("unionTypeResponseMember1", typeNames); + } [InlineData("string", "", "string")]// https://spec.openapis.org/registry/format/ [InlineData("string", "commonmark", "string")] [InlineData("string", "html", "string")] From b5b4e193e8d4d8aee2ad6871068f0eec71855f26 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 15 Mar 2022 12:21:43 -0400 Subject: [PATCH 2/3] - fixes a bug where referenced types with no titles would fail to generate --- src/Kiota.Builder/KiotaBuilder.cs | 1 + .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index acf595d35e..aa128e9751 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -792,6 +792,7 @@ private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentN private static string GetReferenceIdFromOriginalSchema(OpenApiSchema schema, OpenApiSchema parentSchema) { var title = schema.Title; if(!string.IsNullOrEmpty(schema.Reference?.Id)) return schema.Reference.Id; + else if (string.IsNullOrEmpty(title)) return string.Empty; if(parentSchema.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Reference.Id; if(parentSchema.Items?.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Items.Reference.Id; return (parentSchema. diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 0789d91db0..d982037651 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -1113,6 +1113,101 @@ public void UnionOfInlineSchemasWorks() { Assert.Contains("simpleObject", typeNames); Assert.Contains("unionTypeResponseMember1", typeNames); } + [Fact] + public void InheritedTypeWithInlineSchemaWorks() { + var baseObjet = new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "name", new OpenApiSchema { + Type = "string" + } + }, + { + "kind", new OpenApiSchema { + Type = "string" + } + } + }, + Discriminator = new OpenApiDiscriminator { + PropertyName = "kind", + Mapping = new Dictionary { + { + "derivedObject", "#/components/schemas/subNS.derivedObject" + } + } + }, + Reference = new OpenApiReference { + Id = "subNS.baseObject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var derivedObjet = new OpenApiSchema { + Type = "object", + AllOf = new List { + baseObjet, + new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "special", new OpenApiSchema { + Type = "string" + } + } + } + } + }, + Reference = new OpenApiReference { + Id = "subNS.derivedObject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument() { + Paths = new OpenApiPaths() { + ["derivedType"] = new OpenApiPathItem() { + Operations = { + [OperationType.Get] = new OpenApiOperation() { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { + Content = { + ["application/json"] = new OpenApiMediaType { + Schema = derivedObjet + } + } + }, + } + } + } + } + }, + Components = new OpenApiComponents() { + Schemas = new Dictionary { + { + "subNS.baseObject", baseObjet + }, + { + "subNS.derivedObject", derivedObjet + } + } + }, + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" }); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.derivedType"); + Assert.NotNull(requestBuilderNS); + var requestBuilderClass = requestBuilderNS.FindChildByName("derivedTypeRequestBuilder", false); + Assert.NotNull(requestBuilderClass); + var requestExecutorMethod = requestBuilderClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); + Assert.NotNull(requestExecutorMethod); + var executorReturnType = requestExecutorMethod.ReturnType as CodeType; + Assert.NotNull(executorReturnType); + Assert.Contains("DerivedObject", requestExecutorMethod.ReturnType.Name); + } [InlineData("string", "", "string")]// https://spec.openapis.org/registry/format/ [InlineData("string", "commonmark", "string")] [InlineData("string", "html", "string")] From 668bbce8e9fd3d8b461384ac8db43c6b05397746 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 15 Mar 2022 12:24:54 -0400 Subject: [PATCH 3/3] - adds missing changelog entry for inheritance fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9612d77816..238a0c4233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug where integers would not be recognized when type is not number. - Fixed a bug where union types with primitive member types would fail to generate #1270 - Fixed a bug where union types with inline schema member types would fail to generate #1270 +- Fixed a bug where referenced types with no titles would fail to generate #1271 ## [0.0.18] - 2022-03-14