From bd11b3531db10a7e5f84be244a525ed6cb37f52b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 20:15:06 +0000 Subject: [PATCH 1/5] =?UTF-8?q?test+refactor:=20V3=20allOf=20composite/nul?= =?UTF-8?q?lable/additionalProperties=20tests;=20hoist=20isEmpty=20checks?= =?UTF-8?q?=20in=20DefinitionCompiler=20(+5=20tests,=20443=E2=86=92448)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add allOf composite tests: multiple inline schemas merge all properties - Add nullable required property → option type test - Add additionalProperties → Map tests (no named type emitted; property has generic Map type) - Refactor DefinitionCompiler.compileNewObject: hoist propertiesEmpty + allOfEmpty before if/elif/else to avoid evaluating Seq.isEmpty allOf twice Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DefinitionCompiler.fs | 11 +- .../Schema.V3SchemaCompilationTests.fs | 116 ++++++++++++++++++ 2 files changed, 122 insertions(+), 5 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index a9606bb0..818b2063 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -265,7 +265,11 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b let properties = schemaObj.Properties |> toSeq let allOf = schemaObj.AllOf |> toSeq - if Seq.isEmpty properties && Seq.isEmpty allOf then + // Hoist isEmpty checks so neither sequence is evaluated more than once. + let propertiesEmpty = Seq.isEmpty properties + let allOfEmpty = Seq.isEmpty allOf + + if propertiesEmpty && allOfEmpty then if not <| isNull tyName then ns.MarkTypeAsNameAlias tyName @@ -280,10 +284,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b if not(String.IsNullOrWhiteSpace schemaObj.Description) then ty.AddXmlDoc schemaObj.Description - // Combine composite schemas - // Cache the isEmpty check to avoid iterating `allOf` twice (once per field/required block). - let allOfEmpty = Seq.isEmpty allOf - + // Combine composite schemas: collect properties and required from all allOf schemas. let schemaObjProperties = let getProps(s: IOpenApiSchema) = s.Properties |> toSeq diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index a38c9203..44d04484 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -241,3 +241,119 @@ let ``object schema description is surfaced as XmlDoc``() = && a.ConstructorArguments.[0].Value :? string && (a.ConstructorArguments.[0].Value :?> string).Contains("A widget with a name")) |> shouldEqual true + +// ── allOf composite with multiple inline schemas ────────────────────────────── + +/// OpenAPI 3.0 schema where Dog uses allOf to merge two inline objects. +/// Both inline schemas contribute properties; the compiler should emit all of them. +let private allOfCompositeSchema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Dog": { + "type": "object", + "allOf": [ + { + "type": "object", + "properties": { + "name": { "type": "string" } + } + }, + { + "type": "object", + "properties": { + "breed": { "type": "string" } + } + } + ] + } + } + } +}""" + +[] +let ``allOf composite with multiple inline schemas emits all merged properties``() = + let types = compileV3Schema allOfCompositeSchema false + let dogType = types |> List.find(fun t -> t.Name = "Dog") + dogType.GetDeclaredProperty("Name") |> isNull |> shouldEqual false + dogType.GetDeclaredProperty("Breed") |> isNull |> shouldEqual false + +[] +let ``allOf composite merged properties have correct types``() = + let types = compileV3Schema allOfCompositeSchema false + let dogType = types |> List.find(fun t -> t.Name = "Dog") + dogType.GetDeclaredProperty("Name").PropertyType |> shouldEqual typeof + dogType.GetDeclaredProperty("Breed").PropertyType |> shouldEqual typeof + +// ── nullable required property → option type ───────────────────────────────── + +[] +let ``required nullable property compiles to option type``() = + // In OpenAPI 3.0, a required + nullable property must be Option + // because the value may be present but null. + let schema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Status": { + "type": "object", + "required": ["code"], + "properties": { + "code": { "type": "string", "nullable": true } + } + } + } + } +}""" + + let types = compileV3Schema schema false + let statusType = types |> List.find(fun t -> t.Name = "Status") + statusType.GetDeclaredProperty("Code").PropertyType |> shouldEqual typeof + +// ── additionalProperties → Map ──────────────────────────────────── + +/// OpenAPI 3.0 schema where StringMap has only additionalProperties (no explicit properties). +/// The compiler releases the name reservation and compiles it to Map. +/// Any property referencing StringMap by $ref should receive type Map. +let private additionalPropertiesSchema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "StringMap": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Wrapper": { + "type": "object", + "properties": { + "data": { "$ref": "#/components/schemas/StringMap" } + } + } + } + } +}""" + +[] +let ``schema with only additionalProperties does not emit a named type``() = + let types = compileV3Schema additionalPropertiesSchema false + // StringMap's name reservation is released; no separate named type is emitted + types |> List.exists(fun t -> t.Name = "StringMap") |> shouldEqual false + +[] +let ``property referencing an additionalProperties schema has Map type``() = + let types = compileV3Schema additionalPropertiesSchema false + let wrapperType = types |> List.find(fun t -> t.Name = "Wrapper") + let dataProp = wrapperType.GetDeclaredProperty("Data") + dataProp |> isNull |> shouldEqual false + // Map is optional because Wrapper.data is not required + let propType = dataProp.PropertyType + propType.IsGenericType |> shouldEqual true From 75d6eb280b856ec297ef00150dac19118c41b283 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 21 May 2026 20:15:10 +0000 Subject: [PATCH 2/5] ci: trigger checks From b211a55f621a8da3fe8aee69419d26ac242b5eff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 21:07:36 +0000 Subject: [PATCH 3/5] fix: apply Fantomas formatting to fix CheckFormat CI failure Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/3bf5f3ba-9a8c-4fbb-b9f0-a4e2dff6d971 Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- .../Schema.V3SchemaCompilationTests.fs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index 44d04484..a2ce038a 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -285,8 +285,12 @@ let ``allOf composite with multiple inline schemas emits all merged properties`` let ``allOf composite merged properties have correct types``() = let types = compileV3Schema allOfCompositeSchema false let dogType = types |> List.find(fun t -> t.Name = "Dog") - dogType.GetDeclaredProperty("Name").PropertyType |> shouldEqual typeof - dogType.GetDeclaredProperty("Breed").PropertyType |> shouldEqual typeof + + dogType.GetDeclaredProperty("Name").PropertyType + |> shouldEqual typeof + + dogType.GetDeclaredProperty("Breed").PropertyType + |> shouldEqual typeof // ── nullable required property → option type ───────────────────────────────── @@ -314,7 +318,9 @@ let ``required nullable property compiles to option type``() = let types = compileV3Schema schema false let statusType = types |> List.find(fun t -> t.Name = "Status") - statusType.GetDeclaredProperty("Code").PropertyType |> shouldEqual typeof + + statusType.GetDeclaredProperty("Code").PropertyType + |> shouldEqual typeof // ── additionalProperties → Map ──────────────────────────────────── @@ -346,7 +352,9 @@ let private additionalPropertiesSchema = let ``schema with only additionalProperties does not emit a named type``() = let types = compileV3Schema additionalPropertiesSchema false // StringMap's name reservation is released; no separate named type is emitted - types |> List.exists(fun t -> t.Name = "StringMap") |> shouldEqual false + types + |> List.exists(fun t -> t.Name = "StringMap") + |> shouldEqual false [] let ``property referencing an additionalProperties schema has Map type``() = From e9a369a4879d7e1e15dd9bf9deda06047af0cce2 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Thu, 21 May 2026 23:19:03 +0200 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index a2ce038a..84cedcd2 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -364,4 +364,4 @@ let ``property referencing an additionalProperties schema has Map type``() = dataProp |> isNull |> shouldEqual false // Map is optional because Wrapper.data is not required let propType = dataProp.PropertyType - propType.IsGenericType |> shouldEqual true + propType |> shouldEqual typeof option> From a53ac969b0865b33b073b1fdd10c14ed29867815 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 21:28:18 +0000 Subject: [PATCH 5/5] fix: correct additionalProperties test - Map types are not wrapped in option Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/46d3dd73-5dd7-409a-8cae-79c5deef8acf Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- .../SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index 84cedcd2..b260e19b 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -362,6 +362,7 @@ let ``property referencing an additionalProperties schema has Map type``() = let wrapperType = types |> List.find(fun t -> t.Name = "Wrapper") let dataProp = wrapperType.GetDeclaredProperty("Data") dataProp |> isNull |> shouldEqual false - // Map is optional because Wrapper.data is not required + // Map types are left unwrapped (not option) for non-required properties; + // collection types naturally express absence via null/empty. let propType = dataProp.PropertyType - propType |> shouldEqual typeof option> + propType |> shouldEqual typeof>