From a6f7721398af287240e8b950ca364edf01791b0e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:05:55 +0000 Subject: [PATCH 1/5] fix: prevent duplicate ProvidedTypeDefinition for named component aliases (#477) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a named component schema is a single-ref oneOf/anyOf/allOf wrapper that resolves to another named object component, registerInNsAndInDef was calling ns.RegisterType for a ProvidedTypeDefinition that was already registered under a different component path. This caused GetProvidedTypes() to return the same PTD twice, resulting in 'duplicate entry in type index table' during assembly emit. Fix: before calling ns.RegisterType, check whether the ProvidedTypeDefinition is already present in pathToType.Values (meaning it was registered for another path). If so, skip the namespace registration — the pathToType cache entry for the alias path is still added, so lookups continue to resolve correctly. Adds 4 regression tests covering oneOf/allOf/anyOf named alias cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DefinitionCompiler.fs | 18 +++- .../Schema.V3SchemaCompilationTests.fs | 102 ++++++++++++++++++ 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index e6baccc6..ac910c46 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -236,13 +236,23 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b providedField, providedProperty let registerInNsAndInDef tyPath (ns: NamespaceAbstraction) (name, ty: Type) = + // Detect alias: a ProvidedTypeDefinition that was already registered for a different + // component path (e.g. a named component that is a single-ref oneOf/anyOf/allOf wrapper). + // For aliases we only need the pathToType cache entry; adding the same + // ProvidedTypeDefinition to the namespace a second time would cause a + // "duplicate entry" error during assembly emit (issue #477). + let isAlias = + match ty with + | :? ProvidedTypeDefinition -> pathToType.Values |> Seq.exists(fun v -> obj.ReferenceEquals(v, ty)) + | _ -> false + if not <| pathToType.ContainsKey tyPath then pathToType.Add(tyPath, ty) - //else failwithf "Second time compilation of type definition '%s'. This is a bug in DefinitionCompiler" tyPath - match ty with - | :? ProvidedTypeDefinition as prTy -> ns.RegisterType(name, prTy) - | _ -> () + if not isAlias then + match ty with + | :? ProvidedTypeDefinition as prTy -> ns.RegisterType(name, prTy) + | _ -> () let rec compileByPath(tyPath: string) : Type = match pathToType.TryGetValue tyPath with diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index a63e1ed9..116e38ab 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -108,6 +108,108 @@ let ``anyOf single $ref does not produce a separate wrapper type``() = let types = compileV3Schema anyOfSingleRefSchema false types |> List.exists(fun t -> t.Name = "CatRef") |> shouldEqual false +// ── Named component alias to another named object component (issue #477) ───── +// +// When a named component schema is a single-ref oneOf/anyOf/allOf wrapper that +// resolves to another named object component, the resolved ProvidedTypeDefinition +// must appear in the type list exactly once. Before the fix, registerInNsAndInDef +// would register the same PTD a second time (once for the target, once for the +// alias), causing "duplicate entry '' in type index table" during assembly emit. + +let private namedAliasOneOfSchema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Parent": { + "oneOf": [ { "$ref": "#/components/schemas/Parent_Child" } ] + }, + "Parent_Child": { + "type": "object", + "required": ["childValue"], + "properties": { + "childValue": { "type": "string" } + } + } + } + } +}""" + +let private namedAliasAllOfSchema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "MyAlias": { + "allOf": [ { "$ref": "#/components/schemas/MyObject" } ] + }, + "MyObject": { + "type": "object", + "properties": { "value": { "type": "integer" } } + } + } + } +}""" + +let private namedAliasAnyOfSchema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "AliasAnyOf": { + "anyOf": [ { "$ref": "#/components/schemas/TargetObject" } ] + }, + "TargetObject": { + "type": "object", + "properties": { "name": { "type": "string" } } + } + } + } +}""" + +[] +let ``oneOf named alias to object component does not produce duplicate provided type``() = + // Regression test for issue #477: Parent aliases Parent_Child via oneOf. + // The resolved ProvidedTypeDefinition must appear exactly once. + let types = compileV3Schema namedAliasOneOfSchema false + + types + |> List.filter(fun t -> t.Name = "Parent_Child") + |> List.length + |> shouldEqual 1 + +[] +let ``oneOf named alias to object component exposes the target type``() = + let types = compileV3Schema namedAliasOneOfSchema false + + types + |> List.exists(fun t -> t.Name = "Parent_Child") + |> shouldEqual true + +[] +let ``allOf named alias to object component does not produce duplicate provided type``() = + let types = compileV3Schema namedAliasAllOfSchema false + + types + |> List.filter(fun t -> t.Name = "MyObject") + |> List.length + |> shouldEqual 1 + +[] +let ``anyOf named alias to object component does not produce duplicate provided type``() = + let types = compileV3Schema namedAliasAnyOfSchema false + + types + |> List.filter(fun t -> t.Name = "TargetObject") + |> List.length + |> shouldEqual 1 + // ── Required vs optional properties ────────────────────────────────────────── [] From 0216d427f82865f3559ac15d9d47ca3fbd01609e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jul 2026 12:05:59 +0000 Subject: [PATCH 2/5] ci: trigger checks From 56db451640839ad19523f5f8b21a1e9b3a4a97dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Krzywizna?= Date: Tue, 28 Jul 2026 14:18:44 +0200 Subject: [PATCH 3/5] [AGENT] Fix named object alias registration --- .../DefinitionCompiler.fs | 11 ++- .../Schemas/named-object-alias-allof.json | 48 ++++++++++ .../Schemas/named-object-alias-anyof.json | 48 ++++++++++ .../named-object-alias-namespaced.json | 48 ++++++++++ .../Schemas/named-object-alias-oneof.json | 48 ++++++++++ .../Swagger.NamedObjectAliases.Tests.fs | 93 +++++++++++++++++++ .../SwaggerProvider.ProviderTests.fsproj | 1 + 7 files changed, 292 insertions(+), 5 deletions(-) create mode 100644 tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-allof.json create mode 100644 tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-anyof.json create mode 100644 tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-namespaced.json create mode 100644 tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-oneof.json create mode 100644 tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index e6baccc6..64c7ed5c 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -235,10 +235,12 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b providedField, providedProperty - let registerInNsAndInDef tyPath (ns: NamespaceAbstraction) (name, ty: Type) = + let registerInDef tyPath (ty: Type) = if not <| pathToType.ContainsKey tyPath then pathToType.Add(tyPath, ty) - //else failwithf "Second time compilation of type definition '%s'. This is a bug in DefinitionCompiler" tyPath + + let registerInNsAndInDef tyPath (ns: NamespaceAbstraction) (name, ty: Type) = + registerInDef tyPath ty match ty with | :? ProvidedTypeDefinition as prTy -> ns.RegisterType(name, prTy) @@ -252,6 +254,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b | true, def -> let ns, tyName = tyPath |> DefinitionPath.Parse |> nsRoot.Resolve let ty = compileBySchema ns tyName def true (registerInNsAndInDef tyPath ns) true + registerInDef tyPath ty ty :> Type | false, _ when tyPath.StartsWith DefinitionPath.DefinitionPrefix -> failwithf $"Cannot find definition '%s{tyPath}' in schema definitions %A{pathToType.Keys |> Seq.toArray}" @@ -605,6 +608,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b enumTy.AddMember field intValue <- intValue + 1L + registerNew(tyName, enumTy :> Type) enumTy :> Type | _ -> ns.MarkTypeAsNameAlias tyName @@ -641,9 +645,6 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b elTy.MakeArrayType 1 | ty, format -> failwithf $"Type %s{tyName}(%A{ty},%s{format}) should be caught by other match statement (%A{resolvedType})" - if fromByPathCompiler then - registerNew(tyName, tyType) - if isRequired then tyType else if tyType.IsValueType then diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-allof.json b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-allof.json new file mode 100644 index 00000000..ae5d2741 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-allof.json @@ -0,0 +1,48 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Named allOf object alias", + "version": "1.0.0" + }, + "paths": { + "/parent": { + "get": { + "operationId": "getParent", + "responses": { + "200": { + "description": "Parent response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Parent" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Parent": { + "allOf": [ + { + "$ref": "#/components/schemas/Parent_Child" + } + ] + }, + "Parent_Child": { + "type": "object", + "required": [ + "childValue" + ], + "properties": { + "childValue": { + "type": "string" + } + } + } + } + } +} diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-anyof.json b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-anyof.json new file mode 100644 index 00000000..d16d677b --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-anyof.json @@ -0,0 +1,48 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Named anyOf object alias", + "version": "1.0.0" + }, + "paths": { + "/parent": { + "get": { + "operationId": "getParent", + "responses": { + "200": { + "description": "Parent response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Parent" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Parent": { + "anyOf": [ + { + "$ref": "#/components/schemas/Parent_Child" + } + ] + }, + "Parent_Child": { + "type": "object", + "required": [ + "childValue" + ], + "properties": { + "childValue": { + "type": "string" + } + } + } + } + } +} diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-namespaced.json b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-namespaced.json new file mode 100644 index 00000000..14815f94 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-namespaced.json @@ -0,0 +1,48 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Namespaced named object alias", + "version": "1.0.0" + }, + "paths": { + "/alias": { + "get": { + "operationId": "getParent", + "responses": { + "200": { + "description": "Alias response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/A.Parent" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "A.Parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/B.Parent" + } + ] + }, + "B.Parent": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + } + } + } +} diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-oneof.json b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-oneof.json new file mode 100644 index 00000000..d88da4d0 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-oneof.json @@ -0,0 +1,48 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Named oneOf object alias", + "version": "1.0.0" + }, + "paths": { + "/parent": { + "get": { + "operationId": "getParent", + "responses": { + "200": { + "description": "Parent response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Parent" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/Parent_Child" + } + ] + }, + "Parent_Child": { + "type": "object", + "required": [ + "childValue" + ], + "properties": { + "childValue": { + "type": "string" + } + } + } + } + } +} diff --git a/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs b/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs new file mode 100644 index 00000000..3ac06483 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs @@ -0,0 +1,93 @@ +module Swagger.NamedObjectAliases.Tests + +open SwaggerProvider +open Xunit +open FsUnitTyped + +[] +let OneOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-oneof.json" + +[] +let AnyOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-anyof.json" + +[] +let AllOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-allof.json" + +[] +let NamespacedSchema = + __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-namespaced.json" + +type OneOfApi = OpenApiClientProvider +type AnyOfApi = OpenApiClientProvider +type AllOfApi = OpenApiClientProvider +type NamespacedApi = OpenApiClientProvider + +let private oneOfClient = OneOfApi.Client() +let private anyOfClient = AnyOfApi.Client() +let private allOfClient = AllOfApi.Client() +let private namespacedClient = NamespacedApi.Client() + +// These helpers are compile-time assertions that operation responses expose the child property. +let private getOneOfChildValue() = + task { + let! response = oneOfClient.GetParent() + return response.ChildValue + } + +let private getAnyOfChildValue() = + task { + let! response = anyOfClient.GetParent() + return response.ChildValue + } + +let private getAllOfChildValue() = + task { + let! response = allOfClient.GetParent() + return response.ChildValue + } + +let private getNamespacedValue() = + task { + let! response = namespacedClient.GetParent() + return response.Value + } + +let private getResponseType(client: obj) = + let methodInfo = + client.GetType().GetMethods() + |> Array.filter(fun candidate -> candidate.Name = "GetParent") + |> Array.exactlyOne + + methodInfo.ReturnType.GetGenericArguments() |> Array.exactlyOne + +[] +let ``oneOf named component alias response resolves to child object type``() = + getResponseType oneOfClient + |> shouldEqual typeof + + let response = OneOfApi.Parent_Child("oneOf") + response.ChildValue |> shouldEqual "oneOf" + +[] +let ``anyOf named component alias response resolves to child object type``() = + getResponseType anyOfClient + |> shouldEqual typeof + + let response = AnyOfApi.Parent_Child("anyOf") + response.ChildValue |> shouldEqual "anyOf" + +[] +let ``allOf named component alias response resolves to child object type``() = + getResponseType allOfClient + |> shouldEqual typeof + + let response = AllOfApi.Parent_Child("allOf") + response.ChildValue |> shouldEqual "allOf" + +[] +let ``namespaced aliases with equal leaf names register the child object only once``() = + getResponseType namespacedClient + |> shouldEqual typeof + + let response = NamespacedApi.B.Parent("namespaced") + response.Value |> shouldEqual "namespaced" diff --git a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj index a79af7dc..f454aef6 100644 --- a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj +++ b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj @@ -17,6 +17,7 @@ + From d0bdfa6ec9d4b68090d904de34527444c8874499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Krzywizna?= Date: Tue, 28 Jul 2026 14:18:44 +0200 Subject: [PATCH 4/5] [AGENT] Address alias registration review --- .../DefinitionCompiler.fs | 1 + .../Schemas/named-object-alias-multiple.json | 114 ++++++++++++++++++ .../Swagger.NamedObjectAliases.Tests.fs | 84 +++++++------ .../SwaggerProvider.ProviderTests.fsproj | 2 +- 4 files changed, 163 insertions(+), 38 deletions(-) create mode 100644 tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-multiple.json diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index 64c7ed5c..3030cafb 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -254,6 +254,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b | true, def -> let ns, tyName = tyPath |> DefinitionPath.Parse |> nsRoot.Resolve let ty = compileBySchema ns tyName def true (registerInNsAndInDef tyPath ns) true + // An alias can resolve to an existing provided type, so only cache its component path here. registerInDef tyPath ty ty :> Type | false, _ when tyPath.StartsWith DefinitionPath.DefinitionPrefix -> diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-multiple.json b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-multiple.json new file mode 100644 index 00000000..fb7f8730 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-multiple.json @@ -0,0 +1,114 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Multiple named object aliases", + "version": "1.0.0" + }, + "paths": { + "/parent-a": { + "get": { + "operationId": "getParentA", + "responses": { + "200": { + "description": "First alias response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParentA" + } + } + } + } + } + } + }, + "/parent-b": { + "get": { + "operationId": "getParentB", + "responses": { + "200": { + "description": "Second alias response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParentB" + } + } + } + } + } + } + }, + "/alias-chain": { + "get": { + "operationId": "getAliasChain", + "responses": { + "200": { + "description": "Alias chain response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AliasA" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ParentA": { + "oneOf": [ + { + "$ref": "#/components/schemas/Child" + } + ] + }, + "ParentB": { + "anyOf": [ + { + "$ref": "#/components/schemas/Child" + } + ] + }, + "Child": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + "AliasA": { + "oneOf": [ + { + "$ref": "#/components/schemas/AliasB" + } + ] + }, + "AliasB": { + "allOf": [ + { + "$ref": "#/components/schemas/AliasC" + } + ] + }, + "AliasC": { + "type": "object", + "required": [ + "chainValue" + ], + "properties": { + "chainValue": { + "type": "string" + } + } + } + } + } +} diff --git a/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs b/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs index 3ac06483..5d1f9b6a 100644 --- a/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs +++ b/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs @@ -17,60 +17,47 @@ let AllOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-allof.json let NamespacedSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-namespaced.json" +[] +let MultipleAliasesSchema = + __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-multiple.json" + type OneOfApi = OpenApiClientProvider type AnyOfApi = OpenApiClientProvider type AllOfApi = OpenApiClientProvider type NamespacedApi = OpenApiClientProvider +type MultipleAliasesApi = OpenApiClientProvider -let private oneOfClient = OneOfApi.Client() -let private anyOfClient = AnyOfApi.Client() -let private allOfClient = AllOfApi.Client() -let private namespacedClient = NamespacedApi.Client() - -// These helpers are compile-time assertions that operation responses expose the child property. -let private getOneOfChildValue() = - task { - let! response = oneOfClient.GetParent() - return response.ChildValue - } - -let private getAnyOfChildValue() = - task { - let! response = anyOfClient.GetParent() - return response.ChildValue - } - -let private getAllOfChildValue() = - task { - let! response = allOfClient.GetParent() - return response.ChildValue - } - -let private getNamespacedValue() = - task { - let! response = namespacedClient.GetParent() - return response.Value - } - -let private getResponseType(client: obj) = +let private getResponseType (clientType: System.Type) operationName = let methodInfo = - client.GetType().GetMethods() - |> Array.filter(fun candidate -> candidate.Name = "GetParent") + clientType.GetMethods() + |> Array.filter(fun candidate -> candidate.Name = operationName) |> Array.exactlyOne methodInfo.ReturnType.GetGenericArguments() |> Array.exactlyOne +let private getGeneratedTypeNames(generatedType: System.Type) = + generatedType.DeclaringType.GetNestedTypes() + |> Array.map(fun nestedType -> nestedType.Name) + [] let ``oneOf named component alias response resolves to child object type``() = - getResponseType oneOfClient + getResponseType typeof "GetParent" |> shouldEqual typeof let response = OneOfApi.Parent_Child("oneOf") response.ChildValue |> shouldEqual "oneOf" + let generatedTypeNames = getGeneratedTypeNames typeof + generatedTypeNames |> shouldNotContain "Parent" + + generatedTypeNames + |> Array.filter(fun typeName -> typeName = "Parent_Child") + |> Array.length + |> shouldEqual 1 + [] let ``anyOf named component alias response resolves to child object type``() = - getResponseType anyOfClient + getResponseType typeof "GetParent" |> shouldEqual typeof let response = AnyOfApi.Parent_Child("anyOf") @@ -78,7 +65,7 @@ let ``anyOf named component alias response resolves to child object type``() = [] let ``allOf named component alias response resolves to child object type``() = - getResponseType allOfClient + getResponseType typeof "GetParent" |> shouldEqual typeof let response = AllOfApi.Parent_Child("allOf") @@ -86,8 +73,31 @@ let ``allOf named component alias response resolves to child object type``() = [] let ``namespaced aliases with equal leaf names register the child object only once``() = - getResponseType namespacedClient + getResponseType typeof "GetParent" |> shouldEqual typeof let response = NamespacedApi.B.Parent("namespaced") response.Value |> shouldEqual "namespaced" + +[] +let ``multiple aliases and alias chains resolve to their object types``() = + getResponseType typeof "GetParentA" + |> shouldEqual typeof + + getResponseType typeof "GetParentB" + |> shouldEqual typeof + + getResponseType typeof "GetAliasChain" + |> shouldEqual typeof + + let child = MultipleAliasesApi.Child("shared") + child.Value |> shouldEqual "shared" + + let chained = MultipleAliasesApi.AliasC("chained") + chained.ChainValue |> shouldEqual "chained" + + let generatedTypeNames = getGeneratedTypeNames typeof + generatedTypeNames |> shouldNotContain "ParentA" + generatedTypeNames |> shouldNotContain "ParentB" + generatedTypeNames |> shouldNotContain "AliasA" + generatedTypeNames |> shouldNotContain "AliasB" diff --git a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj index f454aef6..22a38967 100644 --- a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj +++ b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj @@ -16,8 +16,8 @@ - + From 055cee0b6aa64160f1e06541f6e283cbf375f90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Krzywizna?= Date: Tue, 28 Jul 2026 14:28:46 +0200 Subject: [PATCH 5/5] [AGENT] Align alias tests with repository conventions --- .../{ => v3}/named-object-alias-allof.json | 0 .../{ => v3}/named-object-alias-anyof.json | 0 .../{ => v3}/named-object-alias-multiple.json | 0 .../named-object-alias-namespaced.json | 0 .../{ => v3}/named-object-alias-oneof.json | 0 .../Swagger.NamedObjectAliases.Tests.fs | 29 +++++++++++-------- 6 files changed, 17 insertions(+), 12 deletions(-) rename tests/SwaggerProvider.ProviderTests/Schemas/{ => v3}/named-object-alias-allof.json (100%) rename tests/SwaggerProvider.ProviderTests/Schemas/{ => v3}/named-object-alias-anyof.json (100%) rename tests/SwaggerProvider.ProviderTests/Schemas/{ => v3}/named-object-alias-multiple.json (100%) rename tests/SwaggerProvider.ProviderTests/Schemas/{ => v3}/named-object-alias-namespaced.json (100%) rename tests/SwaggerProvider.ProviderTests/Schemas/{ => v3}/named-object-alias-oneof.json (100%) diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-allof.json b/tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-allof.json similarity index 100% rename from tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-allof.json rename to tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-allof.json diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-anyof.json b/tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-anyof.json similarity index 100% rename from tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-anyof.json rename to tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-anyof.json diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-multiple.json b/tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-multiple.json similarity index 100% rename from tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-multiple.json rename to tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-multiple.json diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-namespaced.json b/tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-namespaced.json similarity index 100% rename from tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-namespaced.json rename to tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-namespaced.json diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-oneof.json b/tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-oneof.json similarity index 100% rename from tests/SwaggerProvider.ProviderTests/Schemas/named-object-alias-oneof.json rename to tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-oneof.json diff --git a/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs b/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs index 5d1f9b6a..baca5db9 100644 --- a/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs +++ b/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs @@ -5,21 +5,22 @@ open Xunit open FsUnitTyped [] -let OneOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-oneof.json" +let OneOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/v3/named-object-alias-oneof.json" [] -let AnyOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-anyof.json" +let AnyOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/v3/named-object-alias-anyof.json" [] -let AllOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-allof.json" +let AllOfSchema = __SOURCE_DIRECTORY__ + "/Schemas/v3/named-object-alias-allof.json" [] let NamespacedSchema = - __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-namespaced.json" + __SOURCE_DIRECTORY__ + + "/Schemas/v3/named-object-alias-namespaced.json" [] let MultipleAliasesSchema = - __SOURCE_DIRECTORY__ + "/Schemas/named-object-alias-multiple.json" + __SOURCE_DIRECTORY__ + "/Schemas/v3/named-object-alias-multiple.json" type OneOfApi = OpenApiClientProvider type AnyOfApi = OpenApiClientProvider @@ -80,24 +81,28 @@ let ``namespaced aliases with equal leaf names register the child object only on response.Value |> shouldEqual "namespaced" [] -let ``multiple aliases and alias chains resolve to their object types``() = +let ``multiple aliases resolve to their shared object type``() = getResponseType typeof "GetParentA" |> shouldEqual typeof getResponseType typeof "GetParentB" |> shouldEqual typeof - getResponseType typeof "GetAliasChain" - |> shouldEqual typeof - let child = MultipleAliasesApi.Child("shared") child.Value |> shouldEqual "shared" - let chained = MultipleAliasesApi.AliasC("chained") - chained.ChainValue |> shouldEqual "chained" - let generatedTypeNames = getGeneratedTypeNames typeof generatedTypeNames |> shouldNotContain "ParentA" generatedTypeNames |> shouldNotContain "ParentB" + +[] +let ``alias chains resolve to their final object type``() = + getResponseType typeof "GetAliasChain" + |> shouldEqual typeof + + let chained = MultipleAliasesApi.AliasC("chained") + chained.ChainValue |> shouldEqual "chained" + + let generatedTypeNames = getGeneratedTypeNames typeof generatedTypeNames |> shouldNotContain "AliasA" generatedTypeNames |> shouldNotContain "AliasB"