Skip to content

Commit 5397c97

Browse files
github-actions[bot]Copilotlukaszkrzywiznasergey-tihon
authored
[repo-assist] fix: prevent duplicate ProvidedTypeDefinition for named component aliases (#478)
* fix: prevent duplicate ProvidedTypeDefinition for named component aliases (#477) 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> * ci: trigger checks * [AGENT] Fix named object alias registration * [AGENT] Address alias registration review * [AGENT] Align alias tests with repository conventions --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Łukasz Krzywizna <lkrzywizna@selectviewdata.com> Co-authored-by: Sergey Tihon <sergey.tihon@gmail.com>
1 parent 253a695 commit 5397c97

5 files changed

Lines changed: 98 additions & 12 deletions

File tree

src/SwaggerProvider.DesignTime/DefinitionCompiler.fs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,12 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
235235

236236
providedField, providedProperty
237237

238-
let registerInNsAndInDef tyPath (ns: NamespaceAbstraction) (name, ty: Type) =
238+
let registerInDef tyPath (ty: Type) =
239239
if not <| pathToType.ContainsKey tyPath then
240240
pathToType.Add(tyPath, ty)
241-
//else failwithf "Second time compilation of type definition '%s'. This is a bug in DefinitionCompiler" tyPath
241+
242+
let registerInNsAndInDef tyPath (ns: NamespaceAbstraction) (name, ty: Type) =
243+
registerInDef tyPath ty
242244

243245
match ty with
244246
| :? ProvidedTypeDefinition as prTy -> ns.RegisterType(name, prTy)
@@ -252,6 +254,8 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
252254
| true, def ->
253255
let ns, tyName = tyPath |> DefinitionPath.Parse |> nsRoot.Resolve
254256
let ty = compileBySchema ns tyName def true (registerInNsAndInDef tyPath ns) true
257+
// An alias can resolve to an existing provided type, so only cache its component path here.
258+
registerInDef tyPath ty
255259
ty :> Type
256260
| false, _ when tyPath.StartsWith DefinitionPath.DefinitionPrefix ->
257261
failwithf $"Cannot find definition '%s{tyPath}' in schema definitions %A{pathToType.Keys |> Seq.toArray}"
@@ -605,6 +609,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
605609
enumTy.AddMember field
606610
intValue <- intValue + 1L
607611

612+
registerNew(tyName, enumTy :> Type)
608613
enumTy :> Type
609614
| _ ->
610615
ns.MarkTypeAsNameAlias tyName
@@ -641,9 +646,6 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
641646
elTy.MakeArrayType 1
642647
| ty, format -> failwithf $"Type %s{tyName}(%A{ty},%s{format}) should be caught by other match statement (%A{resolvedType})"
643648

644-
if fromByPathCompiler then
645-
registerNew(tyName, tyType)
646-
647649
if isRequired then
648650
tyType
649651
else if tyType.IsValueType then
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"openapi": "3.0.0",
3+
"info": {
4+
"title": "Named oneOf object alias",
5+
"version": "1.0.0"
6+
},
7+
"paths": {
8+
"/parent": {
9+
"get": {
10+
"operationId": "getParent",
11+
"responses": {
12+
"200": {
13+
"description": "Parent response",
14+
"content": {
15+
"application/json": {
16+
"schema": {
17+
"$ref": "#/components/schemas/Parent"
18+
}
19+
}
20+
}
21+
}
22+
}
23+
}
24+
}
25+
},
26+
"components": {
27+
"schemas": {
28+
"Parent": {
29+
"oneOf": [
30+
{
31+
"$ref": "#/components/schemas/Parent_Child"
32+
}
33+
]
34+
},
35+
"Parent_Child": {
36+
"type": "object",
37+
"required": [
38+
"childValue"
39+
],
40+
"properties": {
41+
"childValue": {
42+
"type": "string"
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module Swagger.NamedObjectAliases.Tests
2+
3+
open SwaggerProvider
4+
open Xunit
5+
open FsUnitTyped
6+
7+
[<Literal>]
8+
let Schema = __SOURCE_DIRECTORY__ + "/Schemas/v3/named-object-alias-oneof.json"
9+
10+
type Api = OpenApiClientProvider<Schema, SsrfProtection=false>
11+
12+
[<Fact>]
13+
let ``named component alias response resolves to the referenced object type``() =
14+
let methodInfo =
15+
typeof<Api.Client>.GetMethods()
16+
|> Array.filter(fun candidate -> candidate.Name = "GetParent")
17+
|> Array.exactlyOne
18+
19+
let responseType = methodInfo.ReturnType.GetGenericArguments() |> Array.exactlyOne
20+
21+
responseType |> shouldEqual typeof<Api.Parent_Child>
22+
23+
let response = Api.Parent_Child("oneOf")
24+
response.ChildValue |> shouldEqual "oneOf"

tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<Compile Include="Swagger.I0181.Tests.fs" />
1717
<Compile Include="Swagger.I0219.Tests.fs" />
1818
<Compile Include="Swagger.I0279.Tests.fs" />
19+
<Compile Include="Swagger.NamedObjectAliases.Tests.fs" />
1920
<Compile Include="Swagger.NullableDate.Tests.fs" />
2021
<Compile Include="Swagger.SchemaReaderErrors.Tests.fs" />
2122
<Compile Include="Swashbuckle.ReturnControllers.Tests.fs" />

tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,13 @@ let private anyOfSingleRefSchema =
7777
}"""
7878

7979
[<Fact>]
80-
let ``allOf single $ref resolves to the referenced type without creating a new object type``() =
80+
let ``allOf single $ref resolves to exactly one referenced type``() =
8181
let types = compileV3Schema allOfSingleRefSchema false
82-
// PetRef collapses into Pet via ReleaseNameReservation; the referenced type is present.
83-
types |> List.exists(fun t -> t.Name = "Pet") |> shouldEqual true
82+
// PetRef collapses into Pet via ReleaseNameReservation; the referenced type is registered once.
83+
types
84+
|> List.filter(fun t -> t.Name = "Pet")
85+
|> List.length
86+
|> shouldEqual 1
8487

8588
[<Fact>]
8689
let ``allOf single $ref does not produce a separate wrapper type``() =
@@ -89,19 +92,27 @@ let ``allOf single $ref does not produce a separate wrapper type``() =
8992
types |> List.exists(fun t -> t.Name = "PetRef") |> shouldEqual false
9093

9194
[<Fact>]
92-
let ``oneOf single $ref resolves to the referenced type``() =
95+
let ``oneOf single $ref resolves to exactly one referenced type``() =
9396
let types = compileV3Schema oneOfSingleRefSchema false
94-
types |> List.exists(fun t -> t.Name = "Dog") |> shouldEqual true
97+
98+
types
99+
|> List.filter(fun t -> t.Name = "Dog")
100+
|> List.length
101+
|> shouldEqual 1
95102

96103
[<Fact>]
97104
let ``oneOf single $ref does not produce a separate wrapper type``() =
98105
let types = compileV3Schema oneOfSingleRefSchema false
99106
types |> List.exists(fun t -> t.Name = "DogRef") |> shouldEqual false
100107

101108
[<Fact>]
102-
let ``anyOf single $ref resolves to the referenced type``() =
109+
let ``anyOf single $ref resolves to exactly one referenced type``() =
103110
let types = compileV3Schema anyOfSingleRefSchema false
104-
types |> List.exists(fun t -> t.Name = "Cat") |> shouldEqual true
111+
112+
types
113+
|> List.filter(fun t -> t.Name = "Cat")
114+
|> List.length
115+
|> shouldEqual 1
105116

106117
[<Fact>]
107118
let ``anyOf single $ref does not produce a separate wrapper type``() =

0 commit comments

Comments
 (0)