diff --git a/CHANGELOG.md b/CHANGELOG.md index 68151f6d3c..eb43e76781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Preserve properties and referenced models inside a single inline `allOf` object. - Dart: escape enum options named value or values to avoid conflicts with generated and built-in enum members. [#7807](https://github.com/microsoft/kiota/issues/7807) - C#: escape Unicode line and paragraph separators in generated string literals so generated clients remain valid C# source. - External reference allowlist wildcards no longer cross the URI authority boundary, so a wildcard in the scheme or host cannot match text in the path, the user information or the port. diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 0d583b399d..133a36f5a5 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -2095,6 +2095,13 @@ private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, IOp return CreateModelDeclarationAndType(currentNode, mergedSchema, operation, codeNamespace, suffix, response: responseValue, typeNameForInlineSchema: typeNameForInlineSchema, isRequestBody, dynamicBindingSuffixContext: suffixForInlineSchema); } + if (schema.AllOf is { Count: 1 } && !schema.AllOf[0].IsReferencedSchema() && schema.AllOf[0].HasAnyProperty() && + schema.MergeAllOfSchemaEntries() is IOpenApiSchema singleInlineSchema) + { + // A single inline object still contributes properties, even without an inheritance relationship. + return CreateModelDeclarationAndType(currentNode, singleInlineSchema, operation, codeNamespace, suffix, response: responseValue, typeNameForInlineSchema: typeNameForInlineSchema, isRequestBody, dynamicBindingSuffixContext: suffixForInlineSchema); + } + if ((schema.IsInclusiveUnion() || schema.IsExclusiveUnion()) && string.IsNullOrEmpty(schema.Format) && !schema.IsODataPrimitiveType()) { // OData types are oneOf string, type + format, enum diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.SingleInlineAllOf.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.SingleInlineAllOf.cs new file mode 100644 index 0000000000..f8fc642598 --- /dev/null +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.SingleInlineAllOf.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; + +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Configuration; + +using Microsoft.Extensions.Logging.Abstractions; + +using Xunit; + +namespace Kiota.Builder.Tests; + +public sealed partial class KiotaBuilderTests +{ + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true, true)] + public async Task SingleInlineAllOfPreservesReferencedPropertiesAsync(bool wrapped, bool explicitType, bool siblingProperty = false) + { + const string inlineSchema = """ + {"type":"object","required":["other"],"properties":{ + "other":{"$ref":"#/components/schemas/Identification"} + }} + """; + var accountSchema = wrapped ? "{" + (explicitType ? "\"type\":\"object\"," : "") + + (siblingProperty ? "\"properties\":{\"label\":{\"type\":\"string\"}}," : "") + + "\"allOf\":[" + inlineSchema + "]}" : inlineSchema; + var description = """ + { + "openapi":"3.0.3","info":{"title":"Single inline allOf","version":"1.0"}, + "paths":{"/account":{"get":{"responses":{"200":{"description":"Success","content":{ + "application/json":{"schema":{"$ref":"#/components/schemas/Account"}} + }}}}}}, + "components":{"schemas":{ + "Account":ACCOUNT_SCHEMA, + "Identification":{"type":"object","properties":{"identification":{"type":"string"}}} + }} + } + """.Replace("ACCOUNT_SCHEMA", accountSchema); + await using var stream = await GetDocumentStreamAsync(description); + var builder = new KiotaBuilder(NullLogger.Instance, + new GenerationConfiguration { IncludeAdditionalData = false }, _httpClient); + var document = await builder.CreateOpenApiDocumentAsync(stream, cancellationToken: TestContext.Current.CancellationToken); + Assert.NotNull(document); + var model = builder.CreateSourceModel(builder.CreateUriSpace(document)); + var account = model.FindChildByName("Account"); + Assert.NotNull(account); + var other = Assert.Single(account.Properties, static p => p.Name == "other"); + Assert.Equal("other", other.Name); + if (siblingProperty) + Assert.Contains(account.Properties, static p => p.Name == "label"); + var identification = model.FindChildByName("Identification"); + Assert.NotNull(identification); + Assert.Same(identification, Assert.IsType(other.Type).TypeDefinition); + Assert.Contains(identification.Properties, static p => p.Name == "identification"); + } +}