diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a7c1d6d1..d8d518cda7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug where multiple Visual Studio Code instances would make the extension install/update fail. [#3686](https://github.com/microsoft/kiota/issues/3686) - Fixed a bug where models properties named "additionalData" or "backingstore" would be ignored. [#4224](https://github.com/microsoft/kiota/issues/4224) +- Fixed a bug where multiple allOf entries type would not get merged if they were part of a discriminator. [#4325](https://github.com/microsoft/kiota/issues/4325) - PREVIEW: Renamed the config commands to workspace. [#4310](https://github.com/microsoft/kiota/issues/4310) - PREVIEW: Moved preview configuration files to the .kiota directory. [#4310](https://github.com/microsoft/kiota/issues/4310) - PREVIEW: Moved the copy descriptions to dedicated folders. [#4310](https://github.com/microsoft/kiota/issues/4310) diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs index 4c705bdeeb..b0bba5e344 100644 --- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs @@ -78,7 +78,9 @@ public static bool IsInherited(this OpenApiSchema? schema) { if (schema is null) return false; var meaningfulSchemas = schema.AllOf.FlattenSchemaIfRequired(static x => x.AllOf).Where(static x => x.IsSemanticallyMeaningful()).ToArray(); - return meaningfulSchemas.Count(static x => !string.IsNullOrEmpty(x.Reference?.Id)) == 1 && meaningfulSchemas.Count(static x => string.IsNullOrEmpty(x.Reference?.Id)) == 1; + return meaningfulSchemas.Count(static x => !string.IsNullOrEmpty(x.Reference?.Id)) == 1 && + (meaningfulSchemas.Count(static x => string.IsNullOrEmpty(x.Reference?.Id)) == 1 || + schema.IsSemanticallyMeaningful()); } internal static OpenApiSchema? MergeIntersectionSchemaEntries(this OpenApiSchema? schema) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 097d68ce33..17c2a1040c 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1511,9 +1511,9 @@ private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode currentNode, O TypeDefinition = codeDeclaration, }; } - private CodeType CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation? operation, string classNameSuffix, CodeNamespace codeNamespace, bool isRequestBody) + private CodeType CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation? operation, string classNameSuffix, CodeNamespace codeNamespace, bool isRequestBody, string typeNameForInlineSchema) { - var allOfs = schema.AllOf.FlattenSchemaIfRequired(static x => x.AllOf); + var allOfs = (schema.IsSemanticallyMeaningful() ? new OpenApiSchema[] { schema } : []).Union(schema.AllOf.FlattenSchemaIfRequired(static x => x.AllOf)); CodeElement? codeDeclaration = null; var className = string.Empty; var codeNamespaceFromParent = GetShortestNamespace(codeNamespace, schema); @@ -1524,7 +1524,9 @@ private CodeType CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, var shortestNamespace = string.IsNullOrEmpty(referenceId) ? codeNamespaceFromParent : rootNamespace?.FindOrAddNamespace(shortestNamespaceName); className = (currentSchema.GetSchemaName() is string cName && !string.IsNullOrEmpty(cName) ? cName : - currentNode.GetClassName(config.StructuredMimeTypes, operation: operation, suffix: classNameSuffix, schema: schema, requestBody: isRequestBody)) + (string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(typeNameForInlineSchema) ? + typeNameForInlineSchema : + currentNode.GetClassName(config.StructuredMimeTypes, operation: operation, suffix: classNameSuffix, schema: currentSchema, requestBody: isRequestBody))) .CleanupSymbolName(); if (shortestNamespace != null) codeDeclaration = AddModelDeclarationIfDoesntExist(currentNode, currentSchema, className, shortestNamespace, codeDeclaration as CodeClass); @@ -1635,7 +1637,7 @@ private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, Ope if (schema.IsInherited()) { - return CreateInheritedModelDeclaration(currentNode, schema, operation, suffix, codeNamespace, isRequestBody); + return CreateInheritedModelDeclaration(currentNode, schema, operation, suffix, codeNamespace, isRequestBody, typeNameForInlineSchema); } if (schema.IsIntersection() && schema.MergeIntersectionSchemaEntries() is OpenApiSchema mergedSchema) @@ -1783,8 +1785,9 @@ private CodeNamespace GetShortestNamespace(CodeNamespace currentNamespace, OpenA } private CodeClass AddModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass? inheritsFrom = null) { - if (inheritsFrom == null && schema.AllOf.FirstOrDefault(static x => x.Reference != null) is OpenApiSchema parentSchema) + if (inheritsFrom == null && schema.AllOf.Where(static x => x.Reference != null).ToArray() is { Length: 1 } referencedSchemas) {// any non-reference would be the current class in some description styles + var parentSchema = referencedSchemas[0]; var parentClassNamespace = GetShortestNamespace(currentNamespace, parentSchema); inheritsFrom = (CodeClass)AddModelDeclarationIfDoesntExist(currentNode, parentSchema, parentSchema.GetSchemaName().CleanupSymbolName(), parentClassNamespace); } @@ -2027,6 +2030,11 @@ internal static void AddDiscriminatorMethod(CodeClass newClass, string discrimin } var className = currentNode.GetClassName(config.StructuredMimeTypes, schema: discriminatorSchema).CleanupSymbolName(); var shouldInherit = discriminatorSchema.AllOf.Any(x => currentSchema.Reference?.Id.Equals(x.Reference?.Id, StringComparison.OrdinalIgnoreCase) ?? false); + if (baseClass is not null && !discriminatorSchema.IsInherited()) + { + logger.LogWarning("Discriminator {ComponentKey} is not inherited from {ClassName}.", componentKey, baseClass.Name); + return null; + } var codeClass = AddModelDeclarationIfDoesntExist(currentNode, discriminatorSchema, className, GetShortestNamespace(currentNamespace, discriminatorSchema), shouldInherit ? baseClass : null); return new CodeType { diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 58fcb12a22..318f48d1c9 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -1810,7 +1810,7 @@ public void Inline_Property_Inheritance_Is_Supported() Type = "object", Properties = new Dictionary { { - "info", new OpenApiSchema { + "info2", new OpenApiSchema { Type = "object", Properties = new Dictionary { { @@ -1855,12 +1855,12 @@ public void Inline_Property_Inheritance_Is_Supported() var itemsNS = codeModel.FindNamespaceByName("ApiSdk.resource.item"); var responseClass = itemsNS.FindChildByName("ResourceGetResponse"); var derivedResourceClass = itemsNS.FindChildByName("ResourceGetResponse_derivedResource"); - var derivedResourceInfoClass = itemsNS.FindChildByName("ResourceGetResponse_derivedResource_info"); + var derivedResourceInfoClass = itemsNS.FindChildByName("ResourceGetResponse_derivedResource_info2"); Assert.NotNull(resourceClass); Assert.NotNull(derivedResourceClass); - Assert.NotNull(derivedResourceClass.StartBlock); + Assert.NotNull(derivedResourceClass.StartBlock.Inherits); Assert.Equal(derivedResourceClass.StartBlock.Inherits.TypeDefinition, resourceClass); Assert.NotNull(derivedResourceInfoClass); Assert.NotNull(responseClass); @@ -7328,7 +7328,83 @@ public async Task InheritanceWithAllOfInBaseType() Assert.NotNull(codeModel.FindChildByName("Group")); } [Fact] - public async Task InheritanceWithAllOfWith3Parts() + public async Task InheritanceWithAllOfWith3Parts3Schema() + { + 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: + /directoryObject: + get: + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/microsoft.graph.directoryObject' + /group: + get: + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/microsoft.graph.group' +components: + schemas: + microsoft.graph.directoryObject: + title: 'directoryObject' + required: ['@odata.type'] + type: 'object' + properties: + '@odata.type': + type: 'string' + default: '#microsoft.graph.directoryObject' + discriminator: + propertyName: '@odata.type' + mapping: + '#microsoft.graph.group': '#/components/schemas/microsoft.graph.group' + microsoft.graph.groupFacet1: + title: 'group part 1' + type: 'object' + properties: + groupprop1: + type: 'string' + microsoft.graph.groupFacet2: + title: 'group part 2' + type: 'object' + properties: + groupprop2: + type: 'string' + microsoft.graph.group: + title: 'group' + allOf: + - '$ref': '#/components/schemas/microsoft.graph.directoryObject' + - '$ref': '#/components/schemas/microsoft.graph.groupFacet1' + - '$ref': '#/components/schemas/microsoft.graph.groupFacet2'"); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var directoryObjectClass = codeModel.FindChildByName("DirectoryObject"); + Assert.NotNull(directoryObjectClass); + var resultClass = codeModel.FindChildByName("Group"); + Assert.NotNull(resultClass); + Assert.Equal(4, resultClass.Properties.Count()); + Assert.Null(resultClass.StartBlock.Inherits); + Assert.Single(resultClass.Properties.Where(static x => x.Kind is CodePropertyKind.AdditionalData)); + Assert.Single(resultClass.Properties.Where(static x => x.Name.Equals("oDataType", StringComparison.OrdinalIgnoreCase))); + Assert.Single(resultClass.Properties.Where(static x => x.Name.Equals("groupprop1", StringComparison.OrdinalIgnoreCase))); + Assert.Single(resultClass.Properties.Where(static x => x.Name.Equals("groupprop2", StringComparison.OrdinalIgnoreCase))); + } + [Fact] + public async Task InheritanceWithAllOfWith3Parts1Schema2Inline() { var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await using var fs = await GetDocumentStream(@"openapi: 3.0.1 @@ -7343,7 +7419,6 @@ public async Task InheritanceWithAllOfWith3Parts() get: responses: '200': - description: Example response content: application/json: schema: @@ -7361,7 +7436,6 @@ public async Task InheritanceWithAllOfWith3Parts() discriminator: propertyName: '@odata.type' mapping: - '#microsoft.graph.user': '#/components/schemas/microsoft.graph.user' '#microsoft.graph.group': '#/components/schemas/microsoft.graph.group' microsoft.graph.group: allOf: @@ -7383,9 +7457,11 @@ public async Task InheritanceWithAllOfWith3Parts() var codeModel = builder.CreateSourceModel(node); var resultClass = codeModel.FindChildByName("Group"); Assert.NotNull(resultClass); + Assert.Equal("directoryObject", resultClass.StartBlock.Inherits?.Name, StringComparer.OrdinalIgnoreCase); Assert.Equal(2, resultClass.Properties.Count()); - Assert.Single(resultClass.Properties.Where(x => x.Name.Equals("groupprop1", StringComparison.OrdinalIgnoreCase))); - Assert.Single(resultClass.Properties.Where(x => x.Name.Equals("groupprop2", StringComparison.OrdinalIgnoreCase))); + Assert.Empty(resultClass.Properties.Where(static x => x.Name.Equals("oDataType", StringComparison.OrdinalIgnoreCase))); + Assert.Single(resultClass.Properties.Where(static x => x.Name.Equals("groupprop1", StringComparison.OrdinalIgnoreCase))); + Assert.Single(resultClass.Properties.Where(static x => x.Name.Equals("groupprop2", StringComparison.OrdinalIgnoreCase))); } [Fact] public async Task EnumsWithNullableDoesNotResultInInlineType()