Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
ty :> Type

let resolvedType =
// If schemaObj.Type is missing, but allOf is present andallOf subschema has one element, use that
// If schemaObj.Type is missing, but allOf/oneOf/anyOf is present with one subschema, use that
if
not schemaObj.Type.HasValue
&& not(isNull schemaObj.AllOf)
Expand All @@ -422,6 +422,28 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
Some firstAllOf.Type.Value
else
None
else if
not schemaObj.Type.HasValue
&& not(isNull schemaObj.OneOf)
&& schemaObj.OneOf.Count = 1
then
let firstOneOf = schemaObj.OneOf.[0]

if not(isNull firstOneOf) && firstOneOf.Type.HasValue then
Some firstOneOf.Type.Value
else
None
else if
not schemaObj.Type.HasValue
&& not(isNull schemaObj.AnyOf)
&& schemaObj.AnyOf.Count = 1
then
let firstAnyOf = schemaObj.AnyOf.[0]

if not(isNull firstAnyOf) && firstAnyOf.Type.HasValue then
Some firstAnyOf.Type.Value
else
None
else if schemaObj.Type.HasValue then
Some schemaObj.Type.Value
else
Expand Down Expand Up @@ -475,6 +497,28 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
ns.ReleaseNameReservation tyName
compileByPath <| getFullPath schemaRef.Reference.Id
| _ -> compileNewObject()
// Handle oneOf with single reference (resolves to the referenced type)
| _ when
not(isNull schemaObj.OneOf)
&& schemaObj.OneOf.Count = 1
&& (schemaObj.Properties |> isNull || schemaObj.Properties.Count = 0)
->
match schemaObj.OneOf.[0] with
| :? OpenApiSchemaReference as schemaRef when not(isNull schemaRef.Reference) ->
ns.ReleaseNameReservation tyName
compileByPath <| getFullPath schemaRef.Reference.Id
| _ -> compileNewObject()
// Handle anyOf with single reference (resolves to the referenced type)
| _ when
not(isNull schemaObj.AnyOf)
&& schemaObj.AnyOf.Count = 1
&& (schemaObj.Properties |> isNull || schemaObj.Properties.Count = 0)
->
match schemaObj.AnyOf.[0] with
| :? OpenApiSchemaReference as schemaRef when not(isNull schemaRef.Reference) ->
ns.ReleaseNameReservation tyName
compileByPath <| getFullPath schemaRef.Reference.Id
| _ -> compileNewObject()
| _ when
resolvedType.IsNone
|| resolvedType = Some JsonSchemaType.Object
Expand Down
94 changes: 94 additions & 0 deletions tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,100 @@ let ``allOf $ref to integer alias resolves to int32``() =
let ty = compileAllOfRefType " type: integer\n" true
ty |> shouldEqual typeof<int32>

// ── $ref to primitive-type alias (via oneOf wrapper) ─────────────────────────
// oneOf: [$ref] with a single entry is semantically equivalent to a direct $ref.
// Some code generators (e.g., NSwag, Kiota) emit this form.

/// Compile a schema where `TestType.Value` uses `oneOf: [$ref]` to reference a component alias.
let private compileOneOfRefType (aliasYaml: string) (required: bool) : Type =
let requiredBlock =
if required then
" required:\n - Value\n"
else
""

let schemaStr =
sprintf
"""openapi: "3.0.0"
info:
title: OneOfRefAliasTest
version: "1.0.0"
paths: {}
components:
schemas:
AliasType:
%s TestType:
type: object
%s properties:
Value:
oneOf:
- $ref: '#/components/schemas/AliasType'
"""
(aliasYaml.TrimEnd() + "\n")
requiredBlock

compileSchemaAndGetValueType schemaStr

/// Compile a schema where `TestType.Value` uses `anyOf: [$ref]` to reference a component alias.
let private compileAnyOfRefType (aliasYaml: string) (required: bool) : Type =
let requiredBlock =
if required then
" required:\n - Value\n"
else
""

let schemaStr =
sprintf
"""openapi: "3.0.0"
info:
title: AnyOfRefAliasTest
version: "1.0.0"
paths: {}
components:
schemas:
AliasType:
%s TestType:
type: object
%s properties:
Value:
anyOf:
- $ref: '#/components/schemas/AliasType'
"""
(aliasYaml.TrimEnd() + "\n")
requiredBlock

compileSchemaAndGetValueType schemaStr

[<Fact>]
let ``oneOf $ref to string alias resolves to string``() =
let ty = compileOneOfRefType " type: string\n" true
ty |> shouldEqual typeof<string>

[<Fact>]
let ``oneOf $ref to integer alias resolves to int32``() =
let ty = compileOneOfRefType " type: integer\n" true
ty |> shouldEqual typeof<int32>

[<Fact>]
let ``anyOf $ref to string alias resolves to string``() =
let ty = compileAnyOfRefType " type: string\n" true
ty |> shouldEqual typeof<string>

[<Fact>]
let ``anyOf $ref to integer alias resolves to int32``() =
let ty = compileAnyOfRefType " type: integer\n" true
ty |> shouldEqual typeof<int32>

[<Fact>]
let ``optional oneOf $ref to integer alias resolves to Option<int32>``() =
let ty = compileOneOfRefType " type: integer\n" false
ty |> shouldEqual typeof<int32 option>

[<Fact>]
let ``optional anyOf $ref to integer alias resolves to Option<int32>``() =
let ty = compileAnyOfRefType " type: integer\n" false
ty |> shouldEqual typeof<int32 option>

// ── Optional $ref to primitive-type alias ─────────────────────────────────────
// When a $ref/allOf alias property is non-required, value types must be wrapped
// in Option<T> consistent with the behaviour of ordinary optional primitive properties.
Expand Down
Loading