From 1eae3013bc8ed1f8ef032a5d8278c48e5aa3f5d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:55:36 +0000 Subject: [PATCH 1/6] Initial plan From 8354f62ee7e9baca01a22dbe363c7ee8a2337022 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:15:36 +0000 Subject: [PATCH 2/6] Fix nullable date deserialization by checking JsonSchemaType.Null flag Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- .../v3/DefinitionCompiler.fs | 6 +++- .../Schemas/v3/nullable-date.yaml | 31 +++++++++++++++++++ .../SwaggerProvider.ProviderTests.fsproj | 1 + .../TestNullableDate.fsx | 20 ++++++++++++ .../v3/Swagger.NullableDate.Tests.fs | 27 ++++++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/SwaggerProvider.ProviderTests/Schemas/v3/nullable-date.yaml create mode 100644 tests/SwaggerProvider.ProviderTests/TestNullableDate.fsx create mode 100644 tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index 573d05f..4e01a3e 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -279,7 +279,11 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = if String.IsNullOrEmpty propName then failwithf $"Property cannot be created with empty name. TypeName:%A{tyName}; SchemaObj:%A{schemaObj}" - let isRequired = schemaObjRequired.Contains propName + // Check if the property is nullable (OpenAPI 3.0 nullable becomes Null type flag in 3.1) + let isNullable = + propSchema.Type.HasValue && propSchema.Type.Value.HasFlag(JsonSchemaType.Null) + + let isRequired = schemaObjRequired.Contains propName && not isNullable let pTy = compileBySchema ns (ns.ReserveUniqueName tyName (nicePascalName propName)) propSchema isRequired ns.RegisterType false diff --git a/tests/SwaggerProvider.ProviderTests/Schemas/v3/nullable-date.yaml b/tests/SwaggerProvider.ProviderTests/Schemas/v3/nullable-date.yaml new file mode 100644 index 0000000..8a010a5 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/Schemas/v3/nullable-date.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: Nullable Date Test API + version: 1.0.0 +paths: + /test: + get: + operationId: getTest + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/PersonDto' +components: + schemas: + PersonDto: + type: object + required: + - id + - name + properties: + id: + type: string + name: + type: string + birthDate: + type: string + format: date + nullable: true diff --git a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj index d221006..b33f456 100644 --- a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj +++ b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj @@ -26,6 +26,7 @@ + diff --git a/tests/SwaggerProvider.ProviderTests/TestNullableDate.fsx b/tests/SwaggerProvider.ProviderTests/TestNullableDate.fsx new file mode 100644 index 0000000..45bbf6e --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/TestNullableDate.fsx @@ -0,0 +1,20 @@ +#r "nuget: SwaggerProvider" +open SwaggerProvider + +[] +let Schema = __SOURCE_DIRECTORY__ + "/Schemas/v3/nullable-date.yaml" + +type TestApi = OpenApiClientProvider + +// Check the type of BirthDate property +let personType = typeof +let birthDateProp = personType.GetProperty("BirthDate") + +printfn "BirthDate property type: %A" birthDateProp.PropertyType +printfn "Is generic: %b" birthDateProp.PropertyType.IsGenericType + +if birthDateProp.PropertyType.IsGenericType then + let genericTypeDef = birthDateProp.PropertyType.GetGenericTypeDefinition() + printfn "Generic type definition: %A" genericTypeDef + printfn "Is Option: %b" (genericTypeDef = typedefof>) + printfn "Is Nullable: %b" (genericTypeDef = typedefof>) diff --git a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs new file mode 100644 index 0000000..95a5b02 --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs @@ -0,0 +1,27 @@ +module Swagger.NullableDate.Tests + +open SwaggerProvider +open Xunit +open FsUnitTyped + +[] +let Schema = __SOURCE_DIRECTORY__ + "/../Schemas/v3/nullable-date.yaml" + +type TestApi = OpenApiClientProvider + +[] +let ``PersonDto should have nullable birthDate property``() = + let personType = typeof + let birthDateProp = personType.GetProperty("BirthDate") + birthDateProp |> shouldNotEqual null + + // The property should be Option or Nullable + let propType = birthDateProp.PropertyType + propType.IsGenericType |> shouldEqual true + + let genericTypeDef = propType.GetGenericTypeDefinition() + let isOptionOrNullable = + genericTypeDef = typedefof> || genericTypeDef = typedefof> + + isOptionOrNullable |> shouldEqual true + "BirthDate should be wrapped as Option or Nullable because it's marked as nullable in the schema" |> ignore From f1b5cb0181cf6e7e936e0475794853e2886f48ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:19:09 +0000 Subject: [PATCH 3/6] Address code review feedback: clarify nullable logic and clean up test Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs | 3 +++ .../v3/Swagger.NullableDate.Tests.fs | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index 4e01a3e..fc33145 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -283,6 +283,9 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = let isNullable = propSchema.Type.HasValue && propSchema.Type.Value.HasFlag(JsonSchemaType.Null) + // A property is "required" for type generation if it's in the required list AND not nullable. + // Nullable properties must be wrapped as Option/Nullable to represent null values, + // even if they're in the required list (required + nullable means must be present but can be null). let isRequired = schemaObjRequired.Contains propName && not isNullable let pTy = diff --git a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs index 95a5b02..6f9013e 100644 --- a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs +++ b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs @@ -15,7 +15,7 @@ let ``PersonDto should have nullable birthDate property``() = let birthDateProp = personType.GetProperty("BirthDate") birthDateProp |> shouldNotEqual null - // The property should be Option or Nullable + // The property should be Option (default) or Nullable (with PreferNullable=true) let propType = birthDateProp.PropertyType propType.IsGenericType |> shouldEqual true @@ -24,4 +24,3 @@ let ``PersonDto should have nullable birthDate property``() = genericTypeDef = typedefof> || genericTypeDef = typedefof> isOptionOrNullable |> shouldEqual true - "BirthDate should be wrapped as Option or Nullable because it's marked as nullable in the schema" |> ignore From 76baf77a310a6545777d322093377d014ffe9d79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:21:03 +0000 Subject: [PATCH 4/6] Fix constructor parameter logic to match nullable-aware type generation Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- .../v3/DefinitionCompiler.fs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index fc33145..69f8e2a 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -310,14 +310,22 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = let ctorParams, fields = let required, optional = List.zip (List.ofSeq schemaObjProperties) members - |> List.partition(fun (x, _) -> schemaObjRequired.Contains x.Key) + |> List.partition(fun (x, _) -> + let propSchema = x.Value + let isNullable = + propSchema.Type.HasValue && propSchema.Type.Value.HasFlag(JsonSchemaType.Null) + schemaObjRequired.Contains x.Key && not isNullable) required @ optional |> List.map(fun (x, (f, p)) -> let paramName = niceCamelName p.Name + + let propSchema = x.Value + let isNullable = + propSchema.Type.HasValue && propSchema.Type.Value.HasFlag(JsonSchemaType.Null) let prParam = - if schemaObjRequired.Contains x.Key then + if schemaObjRequired.Contains x.Key && not isNullable then ProvidedParameter(paramName, f.FieldType) else let paramDefaultValue = this.GetDefaultValue f.FieldType From 000d423e1ca312e2119faa04b940e683fe9dfac6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:23:42 +0000 Subject: [PATCH 5/6] Refactor: extract nullable check into helper function for maintainability Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- .../v3/DefinitionCompiler.fs | 16 +++++++--------- .../v3/Swagger.NullableDate.Tests.fs | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index 69f8e2a..8d3a874 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -268,6 +268,10 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = | true -> getReq schemaObj |> Set.ofSeq + // Helper to check if a schema has the Null type flag (OpenAPI 3.0 nullable) + let isSchemaNullable (schema: IOpenApiSchema) = + schema.Type.HasValue && schema.Type.Value.HasFlag(JsonSchemaType.Null) + // Generate fields and properties let members = let generateProperty = generateProperty(UniqueNameGenerator()) @@ -280,8 +284,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = failwithf $"Property cannot be created with empty name. TypeName:%A{tyName}; SchemaObj:%A{schemaObj}" // Check if the property is nullable (OpenAPI 3.0 nullable becomes Null type flag in 3.1) - let isNullable = - propSchema.Type.HasValue && propSchema.Type.Value.HasFlag(JsonSchemaType.Null) + let isNullable = isSchemaNullable propSchema // A property is "required" for type generation if it's in the required list AND not nullable. // Nullable properties must be wrapped as Option/Nullable to represent null values, @@ -311,18 +314,13 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = let required, optional = List.zip (List.ofSeq schemaObjProperties) members |> List.partition(fun (x, _) -> - let propSchema = x.Value - let isNullable = - propSchema.Type.HasValue && propSchema.Type.Value.HasFlag(JsonSchemaType.Null) + let isNullable = isSchemaNullable x.Value schemaObjRequired.Contains x.Key && not isNullable) required @ optional |> List.map(fun (x, (f, p)) -> let paramName = niceCamelName p.Name - - let propSchema = x.Value - let isNullable = - propSchema.Type.HasValue && propSchema.Type.Value.HasFlag(JsonSchemaType.Null) + let isNullable = isSchemaNullable x.Value let prParam = if schemaObjRequired.Contains x.Key && not isNullable then diff --git a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs index 6f9013e..238ed54 100644 --- a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs +++ b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs @@ -20,7 +20,7 @@ let ``PersonDto should have nullable birthDate property``() = propType.IsGenericType |> shouldEqual true let genericTypeDef = propType.GetGenericTypeDefinition() - let isOptionOrNullable = + let hasNullableWrapper = genericTypeDef = typedefof> || genericTypeDef = typedefof> - isOptionOrNullable |> shouldEqual true + hasNullableWrapper |> shouldEqual true From 78ae53fa94ea2a791a8689d10525fdb5c107340f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:25:23 +0000 Subject: [PATCH 6/6] Add null check to isSchemaNullable for robustness Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index 8d3a874..979bcd7 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -270,7 +270,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = // Helper to check if a schema has the Null type flag (OpenAPI 3.0 nullable) let isSchemaNullable (schema: IOpenApiSchema) = - schema.Type.HasValue && schema.Type.Value.HasFlag(JsonSchemaType.Null) + not (isNull schema) && schema.Type.HasValue && schema.Type.Value.HasFlag(JsonSchemaType.Null) // Generate fields and properties let members =