From 5f7365dc4df5e02114e8f1841e70911822d5e6eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Mar 2026 16:44:39 +0000 Subject: [PATCH 1/5] Fix: map format:date to DateOnly on .NET 6+ targets (closes #240) On .NET 6+ (NET6_0_OR_GREATER), format:date schema fields now map to System.DateOnly instead of DateTimeOffset/DateTime. System.Text.Json natively serializes DateOnly as yyyy-MM-dd, which is the correct wire format per RFC 3339 / OpenAPI spec. netstandard2.0 builds fall back to the previous types (DateTimeOffset for v3, DateTime for v2) to preserve compatibility. Also adds a NET6+ test asserting that nullable format:date properties have type Option rather than Option. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../v2/DefinitionCompiler.fs | 7 ++++++- .../v3/DefinitionCompiler.fs | 7 ++++++- .../v3/Swagger.NullableDate.Tests.fs | 14 +++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs index fc55346b..4b299097 100644 --- a/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs @@ -352,7 +352,12 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this = | Float -> typeof | Double -> typeof | String -> typeof - | Date + | Date -> +#if NET6_0_OR_GREATER + typeof +#else + typeof +#endif | DateTime -> typeof | File -> typeof.MakeArrayType 1 | Enum(_, "string") -> typeof diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index a2acfdef..bbecde74 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -496,7 +496,12 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = // for `application/octet-stream` request body // for `multipart/form-data` : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#considerations-for-file-uploads typeof - | HasFlag JsonSchemaType.String, "date" + | HasFlag JsonSchemaType.String, "date" -> +#if NET6_0_OR_GREATER + typeof +#else + typeof +#endif | HasFlag JsonSchemaType.String, "date-time" -> typeof | HasFlag JsonSchemaType.String, "uuid" -> typeof | HasFlag JsonSchemaType.String, _ -> typeof diff --git a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs index 3d16a482..bfd1d3bc 100644 --- a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs +++ b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs @@ -17,7 +17,7 @@ let ``PersonDto should have nullable birthDate property``() = let birthDateProp = personType.GetProperty("BirthDate") birthDateProp |> shouldNotEqual null - // The property should be Option (default) or Nullable (with PreferNullable=true) + // The property should be Option (on .NET 6+) or Option (netstandard2.0) let propType = birthDateProp.PropertyType propType.IsGenericType |> shouldEqual true @@ -29,6 +29,18 @@ let ``PersonDto should have nullable birthDate property``() = hasNullableWrapper |> shouldEqual true +#if NET6_0_OR_GREATER +[] +let ``PersonDto birthDate property should be Option on NET6+``() = + let personType = typeof + let birthDateProp = personType.GetProperty("BirthDate") + birthDateProp |> shouldNotEqual null + + // On NET6+, format: date should map to DateOnly wrapped in Option + let propType = birthDateProp.PropertyType + propType |> shouldEqual typedefof> +#endif + [] let ``PersonDto can deserialize JSON with null birthDate using type provider deserialization``() = // This JSON is from the issue - a person with null birthDate From 79ece4f9cb0a5a8c4083b62f571b754ebfdd933b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Mar 2026 16:49:07 +0000 Subject: [PATCH 2/5] ci: trigger checks From 82fc2cc8d613b2cbd3fcf9723ee603cc95ebe625 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Mar 2026 16:28:45 +0000 Subject: [PATCH 3/5] Fix CI: use runtime type detection for DateOnly; fix test typedefof/scope issue The design-time assembly is compiled as netstandard2.0, so #if NET6_0_OR_GREATER is never true at design-time and DateOnly was never used. Replace compile-time conditional with Type.GetType runtime detection, which correctly returns System.DateOnly when running on .NET 6+. Also fix the test: 'typedefof>' was wrong in two ways: - DateOnly was not in scope (needed System.DateOnly) - typedefof returns the open generic type definition, not the closed type; use typeof> to compare the exact closed type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../v2/DefinitionCompiler.fs | 10 +++++----- .../v3/DefinitionCompiler.fs | 10 +++++----- .../v3/Swagger.NullableDate.Tests.fs | 4 +--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs index 4b299097..1b38b8bf 100644 --- a/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs @@ -353,11 +353,11 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this = | Double -> typeof | String -> typeof | Date -> -#if NET6_0_OR_GREATER - typeof -#else - typeof -#endif + // Runtime detection: design-time assembly targets netstandard2.0 so + // compile-time NET6_0_OR_GREATER is not available; DateOnly exists on .NET 6+ + System.Type.GetType("System.DateOnly") + |> Option.ofObj + |> Option.defaultValue typeof | DateTime -> typeof | File -> typeof.MakeArrayType 1 | Enum(_, "string") -> typeof diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index bbecde74..8cf0b2eb 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -497,11 +497,11 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = // for `multipart/form-data` : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#considerations-for-file-uploads typeof | HasFlag JsonSchemaType.String, "date" -> -#if NET6_0_OR_GREATER - typeof -#else - typeof -#endif + // Runtime detection: design-time assembly targets netstandard2.0 so + // compile-time NET6_0_OR_GREATER is not available; DateOnly exists on .NET 6+ + System.Type.GetType("System.DateOnly") + |> Option.ofObj + |> Option.defaultValue typeof | HasFlag JsonSchemaType.String, "date-time" -> typeof | HasFlag JsonSchemaType.String, "uuid" -> typeof | HasFlag JsonSchemaType.String, _ -> typeof diff --git a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs index bfd1d3bc..a1b0f945 100644 --- a/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs +++ b/tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs @@ -29,7 +29,6 @@ let ``PersonDto should have nullable birthDate property``() = hasNullableWrapper |> shouldEqual true -#if NET6_0_OR_GREATER [] let ``PersonDto birthDate property should be Option on NET6+``() = let personType = typeof @@ -38,8 +37,7 @@ let ``PersonDto birthDate property should be Option on NET6+``() = // On NET6+, format: date should map to DateOnly wrapped in Option let propType = birthDateProp.PropertyType - propType |> shouldEqual typedefof> -#endif + propType |> shouldEqual typeof> [] let ``PersonDto can deserialize JSON with null birthDate using type provider deserialization``() = From 9f6f5cf277aead1351338914a8c506bd2626c947 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Mar 2026 16:45:06 +0000 Subject: [PATCH 4/5] ci: trigger checks From cf3d13e7dba38c54cd1b8b9d7844c248514058d6 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 23:05:21 +0100 Subject: [PATCH 5/5] Fix DateOnly: check target runtime version instead of design-time host (#323) --- .../Provider.OpenApiClient.fs | 3 ++- .../Provider.SwaggerClient.fs | 3 ++- .../v2/DefinitionCompiler.fs | 16 ++++++++++------ .../v3/DefinitionCompiler.fs | 16 ++++++++++------ .../SwaggerProvider.Tests/Schema.Parser.Tests.fs | 4 ++-- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/Provider.OpenApiClient.fs b/src/SwaggerProvider.DesignTime/Provider.OpenApiClient.fs index 4fce4157..14f15fd7 100644 --- a/src/SwaggerProvider.DesignTime/Provider.OpenApiClient.fs +++ b/src/SwaggerProvider.DesignTime/Provider.OpenApiClient.fs @@ -103,7 +103,8 @@ type public OpenApiClientTypeProvider(cfg: TypeProviderConfig) as this = |> Seq.map(fun e -> $"%s{e.Message} @ %s{e.Pointer}") |> String.concat "\n") - let defCompiler = DefinitionCompiler(schema, preferNullable) + let useDateOnly = cfg.SystemRuntimeAssemblyVersion.Major >= 6 + let defCompiler = DefinitionCompiler(schema, preferNullable, useDateOnly) let opCompiler = OperationCompiler(schema, defCompiler, ignoreControllerPrefix, ignoreOperationId, preferAsync) diff --git a/src/SwaggerProvider.DesignTime/Provider.SwaggerClient.fs b/src/SwaggerProvider.DesignTime/Provider.SwaggerClient.fs index b7940b5a..d96d52d1 100644 --- a/src/SwaggerProvider.DesignTime/Provider.SwaggerClient.fs +++ b/src/SwaggerProvider.DesignTime/Provider.SwaggerClient.fs @@ -86,7 +86,8 @@ type public SwaggerTypeProvider(cfg: TypeProviderConfig) as this = let schema = SwaggerParser.parseSchema schemaData - let defCompiler = DefinitionCompiler(schema, preferNullable) + let useDateOnly = cfg.SystemRuntimeAssemblyVersion.Major >= 6 + let defCompiler = DefinitionCompiler(schema, preferNullable, useDateOnly) let opCompiler = OperationCompiler(schema, defCompiler, ignoreControllerPrefix, ignoreOperationId, preferAsync) diff --git a/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs index 1b38b8bf..14231ef8 100644 --- a/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs @@ -150,7 +150,7 @@ and NamespaceAbstraction(name: string) = Some ty) /// Object for compiling definitions. -type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this = +type DefinitionCompiler(schema: SwaggerObject, provideNullable, useDateOnly: bool) as this = let definitionToSchemaObject = Map.ofSeq schema.Definitions let definitionToType = Collections.Generic.Dictionary<_, _>() let nsRoot = NamespaceAbstraction("Root") @@ -353,11 +353,15 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this = | Double -> typeof | String -> typeof | Date -> - // Runtime detection: design-time assembly targets netstandard2.0 so - // compile-time NET6_0_OR_GREATER is not available; DateOnly exists on .NET 6+ - System.Type.GetType("System.DateOnly") - |> Option.ofObj - |> Option.defaultValue typeof + // Use DateOnly only when the target runtime supports it (.NET 6+). + // We check useDateOnly (derived from cfg.SystemRuntimeAssemblyVersion) rather than + // probing the design-time host process, which may differ from the consumer's runtime. + if useDateOnly then + System.Type.GetType("System.DateOnly") + |> Option.ofObj + |> Option.defaultValue typeof + else + typeof | DateTime -> typeof | File -> typeof.MakeArrayType 1 | Enum(_, "string") -> typeof diff --git a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs index 8cf0b2eb..7b2a918d 100644 --- a/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs @@ -165,7 +165,7 @@ and NamespaceAbstraction(name: string) = Some ty) /// Object for compiling definitions. -type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = +type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: bool) as this = let pathToSchema = if isNull schema.Components then Map.empty @@ -497,11 +497,15 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this = // for `multipart/form-data` : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#considerations-for-file-uploads typeof | HasFlag JsonSchemaType.String, "date" -> - // Runtime detection: design-time assembly targets netstandard2.0 so - // compile-time NET6_0_OR_GREATER is not available; DateOnly exists on .NET 6+ - System.Type.GetType("System.DateOnly") - |> Option.ofObj - |> Option.defaultValue typeof + // Use DateOnly only when the target runtime supports it (.NET 6+). + // We check useDateOnly (derived from cfg.SystemRuntimeAssemblyVersion) rather than + // probing the design-time host process, which may differ from the consumer's runtime. + if useDateOnly then + System.Type.GetType("System.DateOnly") + |> Option.ofObj + |> Option.defaultValue typeof + else + typeof | HasFlag JsonSchemaType.String, "date-time" -> typeof | HasFlag JsonSchemaType.String, "uuid" -> typeof | HasFlag JsonSchemaType.String, _ -> typeof diff --git a/tests/SwaggerProvider.Tests/Schema.Parser.Tests.fs b/tests/SwaggerProvider.Tests/Schema.Parser.Tests.fs index 6b22c548..5fa92459 100644 --- a/tests/SwaggerProvider.Tests/Schema.Parser.Tests.fs +++ b/tests/SwaggerProvider.Tests/Schema.Parser.Tests.fs @@ -13,7 +13,7 @@ module V2 = let testSchema schemaStr = let schema = SwaggerParser.parseSchema schemaStr - let defCompiler = DefinitionCompiler(schema, false) + let defCompiler = DefinitionCompiler(schema, false, Environment.Version.Major >= 6) let opCompiler = OperationCompiler(schema, defCompiler, true, false, true) opCompiler.CompileProvidedClients(defCompiler.Namespace) ignore <| defCompiler.Namespace.GetProvidedTypes() @@ -35,7 +35,7 @@ module V3 = |> Seq.map (fun e -> e.Message) |> String.concat ";\n- ")*) try - let defCompiler = DefinitionCompiler(schema, false) + let defCompiler = DefinitionCompiler(schema, false, Environment.Version.Major >= 6) let opCompiler = OperationCompiler(schema, defCompiler, true, false, true) opCompiler.CompileProvidedClients(defCompiler.Namespace) defCompiler.Namespace.GetProvidedTypes()