diff --git a/CHANGELOG.md b/CHANGELOG.md index 7163a2b856..b9b71002e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed incorrect optional types in method parameters in Python [#4507](https://github.com/microsoft/kiota/issues/4507) - 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) - Fixed a bug where allOf structure with one entry and properties would not be considered as inheritance case. [#4346](https://github.com/microsoft/kiota/issues/4346) +- Fixed a bug where some allOf scenarios would be missing properties if type object wasn't set on the schema. [#4074](https://github.com/microsoft/kiota/issues/4074) ## [1.14.0] - 2024-05-02 diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs index 4db49e2874..be677c9e5c 100644 --- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs @@ -60,10 +60,14 @@ public static bool IsArray(this OpenApiSchema? schema) FlattenEmptyEntries([schema.Items], static x => x.AnyOf.Union(x.AllOf).Union(x.OneOf).ToList(), 1).FirstOrDefault() is OpenApiSchema flat && flat.IsSemanticallyMeaningful()); } - public static bool IsObject(this OpenApiSchema? schema) + public static bool IsObjectType(this OpenApiSchema? schema) { return "object".Equals(schema?.Type, StringComparison.OrdinalIgnoreCase); } + public static bool HasAnyProperty(this OpenApiSchema? schema) + { + return schema?.Properties is { Count: > 0 }; + } public static bool IsInclusiveUnion(this OpenApiSchema? schema) { return schema?.AnyOf?.Count(static x => IsSemanticallyMeaningful(x, true)) > 1; @@ -178,7 +182,7 @@ public static IEnumerable GetSchemaReferenceIds(this OpenApiSchema schem } private static IEnumerable FlattenEmptyEntries(this IEnumerable schemas, Func> subsequentGetter, int? maxDepth = default) { - if (schemas == null) return Enumerable.Empty(); + if (schemas == null) return []; ArgumentNullException.ThrowIfNull(subsequentGetter); if ((maxDepth ?? 1) <= 0) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index ebf216a63f..26d3ec95d9 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1698,7 +1698,7 @@ private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, Ope return CreateComposedModelDeclaration(currentNode, schema, operation, suffix, codeNamespace, isRequestBody, typeNameForInlineSchema); } - if (schema.IsObject() || schema.Properties.Any() || schema.IsEnum() || !string.IsNullOrEmpty(schema.AdditionalProperties?.Type)) + if (schema.IsObjectType() || schema.HasAnyProperty() || schema.IsEnum() || !string.IsNullOrEmpty(schema.AdditionalProperties?.Type)) { // no inheritance or union type, often empty definitions with only additional properties are used as property bags. return CreateModelDeclarationAndType(currentNode, schema, operation, codeNamespace, suffix, response: responseValue, typeNameForInlineSchema: typeNameForInlineSchema, isRequestBody); @@ -2129,7 +2129,7 @@ private Dictionary CollectAllProperties(OpenApiSchema sch Dictionary result = schema.Properties?.ToDictionary(static x => x.Key, static x => x.Value, StringComparer.Ordinal) ?? new(StringComparer.Ordinal); if (schema.AllOf?.Any() ?? false) { - foreach (var supProperty in schema.AllOf.Where(static x => x.IsObject() && !x.IsReferencedSchema() && x.Properties is not null).SelectMany(static x => x.Properties)) + foreach (var supProperty in schema.AllOf.Where(static x => !x.IsReferencedSchema() && x.HasAnyProperty()).SelectMany(static x => x.Properties)) { result.Add(supProperty.Key, supProperty.Value); } diff --git a/src/Kiota.Builder/Validation/MissingDiscriminator.cs b/src/Kiota.Builder/Validation/MissingDiscriminator.cs index ad12d74ea2..c889f5cf5a 100644 --- a/src/Kiota.Builder/Validation/MissingDiscriminator.cs +++ b/src/Kiota.Builder/Validation/MissingDiscriminator.cs @@ -35,7 +35,7 @@ private static void ValidateSchema(OpenApiSchema schema, IValidationContext cont { if (!schema.IsInclusiveUnion() && !schema.IsExclusiveUnion()) return; - if (schema.AnyOf.All(static x => !x.IsObject()) && schema.OneOf.All(static x => !x.IsObject())) + if (schema.AnyOf.All(static x => !x.IsObjectType()) && schema.OneOf.All(static x => !x.IsObjectType())) return; if (string.IsNullOrEmpty(schema.GetDiscriminatorPropertyName()) || !schema.GetDiscriminatorMappings(idx).Any()) context.CreateWarning(nameof(MissingDiscriminator), $"The schema {address} is a polymorphic type but does not define a discriminator. This will result in a serialization errors."); diff --git a/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs b/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs index 0257c53902..78a4b31572 100644 --- a/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs +++ b/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs @@ -23,9 +23,9 @@ public UrlFormEncodedComplex() : base(nameof(UrlFormEncodedComplex), static (con private static void ValidateSchema(OpenApiSchema schema, IValidationContext context, string operationId, string schemaName) { if (schema == null) return; - if (!schema.IsObject()) + if (!schema.IsObjectType()) context.CreateWarning(nameof(UrlFormEncodedComplex), $"The operation {operationId} has a {schemaName} which is not an object type. This is not supported by Kiota and serialization will fail."); - if (schema.Properties.Any(static x => x.Value.IsObject())) + if (schema.Properties.Any(static x => x.Value.IsObjectType())) context.CreateWarning(nameof(UrlFormEncodedComplex), $"The operation {operationId} has a {schemaName} with a complex properties and the url form encoded content type. This is not supported by Kiota and serialization of complex properties will fail."); } } diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs index 9077ecbe49..08d46a13c7 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs @@ -43,17 +43,19 @@ public void Defensive() Assert.False(OpenApiSchemaExtensions.IsInclusiveUnion(null)); Assert.False(OpenApiSchemaExtensions.IsExclusiveUnion(null)); Assert.False(OpenApiSchemaExtensions.IsArray(null)); - Assert.False(OpenApiSchemaExtensions.IsObject(null)); + Assert.False(OpenApiSchemaExtensions.IsObjectType(null)); + Assert.False(OpenApiSchemaExtensions.HasAnyProperty(null)); Assert.False(OpenApiSchemaExtensions.IsReferencedSchema(null)); Assert.Null(OpenApiSchemaExtensions.MergeIntersectionSchemaEntries(null)); Assert.False(new OpenApiSchema { Reference = null }.IsReferencedSchema()); Assert.False(new OpenApiSchema { Type = null }.IsArray()); - Assert.False(new OpenApiSchema { Type = null }.IsObject()); + Assert.False(new OpenApiSchema { Type = null }.IsObjectType()); Assert.False(new OpenApiSchema { AnyOf = null }.IsInclusiveUnion()); Assert.False(new OpenApiSchema { AllOf = null }.IsInherited()); Assert.False(new OpenApiSchema { AllOf = null }.IsIntersection()); Assert.False(new OpenApiSchema { OneOf = null }.IsExclusiveUnion()); + Assert.False(new OpenApiSchema { Properties = null }.HasAnyProperty()); var original = new OpenApiSchema { AllOf = null }; Assert.Equal(original, original.MergeIntersectionSchemaEntries()); diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index a093746039..5119e345eb 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -7702,6 +7702,55 @@ public async Task InheritanceWithAllOfWith3Parts1Schema2Inline(bool reverseOrder Assert.Single(resultClass.Properties.Where(static x => x.Name.Equals("groupprop2", StringComparison.OrdinalIgnoreCase))); } [Fact] + public async Task InheritanceWithoutObjectTypeHasAllProperties() + { + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await using var fs = await GetDocumentStream(@"openapi: 3.0.3 +servers: + - url: 'https://example.com' +info: + title: example + version: 0.0.1 +paths: + /path: + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/outerPayload' + responses: + '201': + description: Created + content: + application/json: + schema: + type: string + +components: + schemas: + outerPayload: + allOf: + - $ref: '#/components/schemas/innerPayload' + - properties: + someField: + type: string + innerPayload: + properties: + anotherField: + type: string"); + 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 outerPayloadClass = codeModel.FindChildByName("outerPayload"); + Assert.NotNull(outerPayloadClass); + Assert.Equal("innerPayload", outerPayloadClass.StartBlock.Inherits?.Name, StringComparer.OrdinalIgnoreCase); + Assert.Single(outerPayloadClass.Properties); + Assert.Single(outerPayloadClass.Properties.Where(static x => x.Name.Equals("someField", StringComparison.OrdinalIgnoreCase))); + } + [Fact] public async Task EnumsWithNullableDoesNotResultInInlineType() { var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());