diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index e6baccc6..3030cafb 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,8 @@ 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 -> 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 enumTy.AddMember field intValue <- intValue + 1L + registerNew(tyName, enumTy :> Type) enumTy :> Type | _ -> ns.MarkTypeAsNameAlias tyName @@ -641,9 +646,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/v3/named-object-alias-oneof.json b/tests/SwaggerProvider.ProviderTests/Schemas/v3/named-object-alias-oneof.json new file mode 100644 index 00000000..d88da4d0 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Schemas/v3/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..8334264e --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Swagger.NamedObjectAliases.Tests.fs @@ -0,0 +1,24 @@ +module Swagger.NamedObjectAliases.Tests + +open SwaggerProvider +open Xunit +open FsUnitTyped + +[] +let Schema = __SOURCE_DIRECTORY__ + "/Schemas/v3/named-object-alias-oneof.json" + +type Api = OpenApiClientProvider + +[] +let ``named component alias response resolves to the referenced object type``() = + let methodInfo = + typeof.GetMethods() + |> Array.filter(fun candidate -> candidate.Name = "GetParent") + |> Array.exactlyOne + + let responseType = methodInfo.ReturnType.GetGenericArguments() |> Array.exactlyOne + + responseType |> shouldEqual typeof + + let response = Api.Parent_Child("oneOf") + response.ChildValue |> shouldEqual "oneOf" diff --git a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj index a79af7dc..22a38967 100644 --- a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj +++ b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj @@ -16,6 +16,7 @@ + diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index a63e1ed9..8253e414 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -77,10 +77,13 @@ let private anyOfSingleRefSchema = }""" [] -let ``allOf single $ref resolves to the referenced type without creating a new object type``() = +let ``allOf single $ref resolves to exactly one referenced type``() = let types = compileV3Schema allOfSingleRefSchema false - // PetRef collapses into Pet via ReleaseNameReservation; the referenced type is present. - types |> List.exists(fun t -> t.Name = "Pet") |> shouldEqual true + // PetRef collapses into Pet via ReleaseNameReservation; the referenced type is registered once. + types + |> List.filter(fun t -> t.Name = "Pet") + |> List.length + |> shouldEqual 1 [] let ``allOf single $ref does not produce a separate wrapper type``() = @@ -89,9 +92,13 @@ let ``allOf single $ref does not produce a separate wrapper type``() = types |> List.exists(fun t -> t.Name = "PetRef") |> shouldEqual false [] -let ``oneOf single $ref resolves to the referenced type``() = +let ``oneOf single $ref resolves to exactly one referenced type``() = let types = compileV3Schema oneOfSingleRefSchema false - types |> List.exists(fun t -> t.Name = "Dog") |> shouldEqual true + + types + |> List.filter(fun t -> t.Name = "Dog") + |> List.length + |> shouldEqual 1 [] let ``oneOf single $ref does not produce a separate wrapper type``() = @@ -99,9 +106,13 @@ let ``oneOf single $ref does not produce a separate wrapper type``() = types |> List.exists(fun t -> t.Name = "DogRef") |> shouldEqual false [] -let ``anyOf single $ref resolves to the referenced type``() = +let ``anyOf single $ref resolves to exactly one referenced type``() = let types = compileV3Schema anyOfSingleRefSchema false - types |> List.exists(fun t -> t.Name = "Cat") |> shouldEqual true + + types + |> List.filter(fun t -> t.Name = "Cat") + |> List.length + |> shouldEqual 1 [] let ``anyOf single $ref does not produce a separate wrapper type``() =