Skip to content

Commit 97d324d

Browse files
github-actions[bot]CopilotCopilotsergey-tihon
authored
[Repo Assist] Fix: map format:date to DateOnly on .NET 6+ targets (closes #240) (#321)
* 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<DateOnly> rather than Option<DateTimeOffset>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks * 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<Option<DateOnly>>' 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<Option<System.DateOnly>> to compare the exact closed type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks * Fix DateOnly: check target runtime version instead of design-time host (#323) * fix: build --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Sergey Tihon <sergey.tihon@gmail.com>
1 parent 168908d commit 97d324d

File tree

7 files changed

+40
-10
lines changed

7 files changed

+40
-10
lines changed

src/SwaggerProvider.DesignTime/Provider.OpenApiClient.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ type public OpenApiClientTypeProvider(cfg: TypeProviderConfig) as this =
118118
|> Seq.map(fun e -> $"%s{e.Message} @ %s{e.Pointer}")
119119
|> Seq.toList
120120

121-
let defCompiler = DefinitionCompiler(schema, preferNullable)
121+
let useDateOnly = cfg.SystemRuntimeAssemblyVersion.Major >= 6
122+
let defCompiler = DefinitionCompiler(schema, preferNullable, useDateOnly)
122123

123124
let opCompiler =
124125
OperationCompiler(schema, defCompiler, ignoreControllerPrefix, ignoreOperationId, preferAsync)

src/SwaggerProvider.DesignTime/Provider.SwaggerClient.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ type public SwaggerTypeProvider(cfg: TypeProviderConfig) as this =
8686

8787
let schema = SwaggerParser.parseSchema schemaData
8888

89-
let defCompiler = DefinitionCompiler(schema, preferNullable)
89+
let useDateOnly = cfg.SystemRuntimeAssemblyVersion.Major >= 6
90+
let defCompiler = DefinitionCompiler(schema, preferNullable, useDateOnly)
9091

9192
let opCompiler =
9293
OperationCompiler(schema, defCompiler, ignoreControllerPrefix, ignoreOperationId, preferAsync)

src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ and NamespaceAbstraction(name: string) =
150150
Some ty)
151151

152152
/// Object for compiling definitions.
153-
type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
153+
type DefinitionCompiler(schema: SwaggerObject, provideNullable, useDateOnly: bool) as this =
154154
let definitionToSchemaObject = Map.ofSeq schema.Definitions
155155
let definitionToType = Collections.Generic.Dictionary<_, _>()
156156
let nsRoot = NamespaceAbstraction("Root")
@@ -352,7 +352,16 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
352352
| Float -> typeof<float32>
353353
| Double -> typeof<double>
354354
| String -> typeof<string>
355-
| Date
355+
| Date ->
356+
// Use DateOnly only when the target runtime supports it (.NET 6+).
357+
// We check useDateOnly (derived from cfg.SystemRuntimeAssemblyVersion) rather than
358+
// probing the design-time host process, which may differ from the consumer's runtime.
359+
if useDateOnly then
360+
System.Type.GetType("System.DateOnly")
361+
|> Option.ofObj
362+
|> Option.defaultValue typeof<DateTime>
363+
else
364+
typeof<DateTime>
356365
| DateTime -> typeof<DateTime>
357366
| File -> typeof<byte>.MakeArrayType 1
358367
| Enum(_, "string") -> typeof<string>

src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ and NamespaceAbstraction(name: string) =
165165
Some ty)
166166

167167
/// Object for compiling definitions.
168-
type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this =
168+
type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: bool) as this =
169169
let pathToSchema =
170170
if isNull schema.Components then
171171
Map.empty
@@ -496,7 +496,16 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable) as this =
496496
// for `application/octet-stream` request body
497497
// for `multipart/form-data` : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#considerations-for-file-uploads
498498
typeof<IO.Stream>
499-
| HasFlag JsonSchemaType.String, "date"
499+
| HasFlag JsonSchemaType.String, "date" ->
500+
// Use DateOnly only when the target runtime supports it (.NET 6+).
501+
// We check useDateOnly (derived from cfg.SystemRuntimeAssemblyVersion) rather than
502+
// probing the design-time host process, which may differ from the consumer's runtime.
503+
if useDateOnly then
504+
System.Type.GetType("System.DateOnly")
505+
|> Option.ofObj
506+
|> Option.defaultValue typeof<DateTimeOffset>
507+
else
508+
typeof<DateTimeOffset>
500509
| HasFlag JsonSchemaType.String, "date-time" -> typeof<DateTimeOffset>
501510
| HasFlag JsonSchemaType.String, "uuid" -> typeof<Guid>
502511
| HasFlag JsonSchemaType.String, _ -> typeof<string>

tests/SwaggerProvider.ProviderTests/v3/Swagger.NullableDate.Tests.fs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let ``PersonDto should have nullable birthDate property``() =
1717
let birthDateProp = personType.GetProperty("BirthDate")
1818
birthDateProp |> shouldNotEqual null
1919

20-
// The property should be Option<DateTimeOffset> (default) or Nullable<DateTimeOffset> (with PreferNullable=true)
20+
// The property should be Option<DateOnly> (on .NET 6+) or Option<DateTimeOffset> (netstandard2.0)
2121
let propType = birthDateProp.PropertyType
2222
propType.IsGenericType |> shouldEqual true
2323

@@ -29,6 +29,16 @@ let ``PersonDto should have nullable birthDate property``() =
2929

3030
hasNullableWrapper |> shouldEqual true
3131

32+
[<Fact>]
33+
let ``PersonDto birthDate property should be Option<DateOnly> on NET6+``() =
34+
let personType = typeof<TestApi.PersonDto>
35+
let birthDateProp = personType.GetProperty("BirthDate")
36+
birthDateProp |> shouldNotEqual null
37+
38+
// On NET6+, format: date should map to DateOnly wrapped in Option<T>
39+
let propType = birthDateProp.PropertyType
40+
propType |> shouldEqual typeof<Option<System.DateOnly>>
41+
3242
[<Fact>]
3343
let ``PersonDto can deserialize JSON with null birthDate using type provider deserialization``() =
3444
// This JSON is from the issue - a person with null birthDate

tests/SwaggerProvider.Tests/Schema.Parser.Tests.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module V2 =
1313
let testSchema schemaStr =
1414
let schema = SwaggerParser.parseSchema schemaStr
1515

16-
let defCompiler = DefinitionCompiler(schema, false)
16+
let defCompiler = DefinitionCompiler(schema, false, Environment.Version.Major >= 6)
1717
let opCompiler = OperationCompiler(schema, defCompiler, true, false, true)
1818
opCompiler.CompileProvidedClients(defCompiler.Namespace)
1919
ignore <| defCompiler.Namespace.GetProvidedTypes()
@@ -35,7 +35,7 @@ module V3 =
3535
|> Seq.map (fun e -> e.Message)
3636
|> String.concat ";\n- ")*)
3737
try
38-
let defCompiler = DefinitionCompiler(schema, false)
38+
let defCompiler = DefinitionCompiler(schema, false, Environment.Version.Major >= 6)
3939
let opCompiler = OperationCompiler(schema, defCompiler, true, false, true)
4040
opCompiler.CompileProvidedClients(defCompiler.Namespace)
4141
defCompiler.Namespace.GetProvidedTypes()

tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ let private compileSchemaAndGetValueType(schemaStr: string) : Type =
3131
| null -> failwith "Failed to parse OpenAPI schema: Document is null."
3232
| doc -> doc
3333

34-
let defCompiler = DefinitionCompiler(schema, false)
34+
let defCompiler = DefinitionCompiler(schema, false, false)
3535
let opCompiler = OperationCompiler(schema, defCompiler, true, false, true)
3636
opCompiler.CompileProvidedClients(defCompiler.Namespace)
3737

0 commit comments

Comments
 (0)