[Repo Assist] feat: add WrapNullableStrings static parameter to OpenApiClientProvider#357
Conversation
…er (closes #122) When WrapNullableStrings=true, optional/nullable string fields are wrapped as Option<string> instead of plain string, matching the treatment of other optional value types (bool, int, float, etc.). Defaults to false to preserve backwards compatibility. - Add WrapNullableStrings static parameter to DefinitionCompiler (optional arg) - Pass parameter through Provider.OpenApiClient.fs to DefinitionCompiler - Include WrapNullableStrings in the type-generation cache key - Add compilePropertyTypeWith helper in test helpers - Add 5 tests covering WrapNullableStrings=false/true/required scenarios Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@copilot merge develop into this branch |
…ue-122-wrap-nullable-strings-362a357b9d702fbf # Conflicts: # tests/SwaggerProvider.Tests/v3/Schema.TestHelpers.fs # tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
There's no |
|
/repo-assist i don't want add WrapNullableStrings static parameters. i want to have the beharior of WrapNullableStrings=true by defaiult |
|
✗ Repo Assist encountered failed, see workflow run. |
|
@copilot i don't want add WrapNullableStrings static parameters. i want to have the beharior of WrapNullableStrings=true by defaiult |
…avior (remove WrapNullableStrings param) Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/345881f5-ff34-40a2-aeba-703b53cb2dbc Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Done in commit |
There was a problem hiding this comment.
Pull request overview
This PR aims to address #122 by allowing nullable/optional OpenAPI string properties to be represented as Option<string> in generated client types, aligning string behavior with existing optional value-type wrapping.
Changes:
- Updates v3
DefinitionCompilerto wrap non-requiredstringtypes inOption<string>. - Updates v3 mapping tests to expect
Option<string>for non-required strings (including inPreferNullablemode). - Updates the v2-compat compilation test (via v3 pipeline) to expect
Option<string>for an optional string property.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs |
Adds unconditional wrapping of non-required string into Option<string>. |
src/SwaggerProvider.DesignTime/Provider.OpenApiClient.fs |
No functional change shown in diff; still constructs DefinitionCompiler without a WrapNullableStrings toggle. |
tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs |
Changes expectations so optional strings map to Option<string>, including under PreferNullable. |
tests/SwaggerProvider.Tests/v3/Schema.V2SchemaCompilationTests.fs |
Changes v2-compat expectation so an optional string property becomes string option. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| else if tyType = typeof<string> then | ||
| ProvidedTypeBuilder.MakeGenericType(typedefof<Option<obj>>, [ tyType ]) |
There was a problem hiding this comment.
The new else if tyType = typeof<string> branch wraps all non-required plain strings in Option<string> unconditionally. This is a breaking change vs the prior behavior, and it also doesn't match the PR description/title which says this should be controlled by an opt-in WrapNullableStrings static parameter (default false). Gate this wrapping behind a wrapNullableStrings flag (threaded from the type provider) so the default remains string unless explicitly enabled.
| else if tyType = typeof<string> then | |
| ProvidedTypeBuilder.MakeGenericType(typedefof<Option<obj>>, [ tyType ]) |
| let useDateOnly = cfg.SystemRuntimeAssemblyVersion.Major >= 6 | ||
|
|
||
| let defCompiler = DefinitionCompiler(schema, preferNullable, useDateOnly) | ||
|
|
There was a problem hiding this comment.
DefinitionCompiler is still constructed with only (schema, preferNullable, useDateOnly), and there is no threading of the new WrapNullableStrings setting described in the PR. If the intent is an opt-in behavior, add WrapNullableStrings as a static parameter, include it in the cache key, and pass it through here to DefinitionCompiler so default behavior stays backward-compatible.
| // ── Optional string is wrapped in Option<string> ───────────────────────────── | ||
|
|
||
| [<Fact>] | ||
| let ``optional string is not wrapped in Option``() = | ||
| let ``optional string maps to Option<string>``() = | ||
| let ty = compilePropertyType " type: string\n" false | ||
|
|
||
| // string is a reference type — not wrapped in Option<T> even when non-required | ||
| ty |> shouldEqual typeof<string> | ||
| // plain string fields are wrapped in Option<string> when non-required | ||
| ty | ||
| |> shouldEqual(typedefof<Option<_>>.MakeGenericType(typeof<string>)) |
There was a problem hiding this comment.
This test now asserts that a non-required plain string maps to Option<string> by default. Per the PR description, WrapNullableStrings is supposed to be opt-in with default false; if so, the default expectation should remain string, and there should be a separate test that enables WrapNullableStrings=true (or uses a helper that passes the flag into DefinitionCompiler) and then expects Option<string>.
| [<Fact>] | ||
| let ``PreferNullable: optional string is not wrapped (reference type)``() = | ||
| // Reference types like string are not wrapped in Nullable<T> since they are | ||
| // already nullable by nature — same behaviour as Option mode. | ||
| let ``PreferNullable: optional string maps to Option<string>``() = | ||
| // Strings are always wrapped in Option<string> when non-required, even with PreferNullable. | ||
| let ty = compilePropertyTypeWith true " type: string\n" false | ||
| ty |> shouldEqual typeof<string> | ||
| ty |> shouldEqual typeof<string option> |
There was a problem hiding this comment.
PreferNullable tests are now asserting Option<string> for optional strings. If WrapNullableStrings is intended to be opt-in, this should be conditional on that flag as well (and the test should set the flag explicitly). Otherwise this changes the semantics of PreferNullable mode for reference types without any configuration toggle.
| nameProp.PropertyType |> shouldEqual typeof<string> | ||
| // optional string — string is a reference type, not wrapped in Option | ||
| tagProp.PropertyType |> shouldEqual typeof<string> | ||
| // optional string — now wrapped in Option<string> | ||
| tagProp.PropertyType |> shouldEqual typeof<string option> |
There was a problem hiding this comment.
This v2-compat test now expects an optional string property (tag) to be string option, which makes the behavior change unconditional for Swagger 2.0 documents compiled through the v3 pipeline. If the goal is to keep backward compatibility and require an explicit WrapNullableStrings=true opt-in, update this test to keep the default as string and add a separate opt-in test case.
|
@copilot why don't you use Option.ofString if runtime to avoid Some(null) ? |
🤖 This PR was created by Repo Assist, an automated AI assistant.
Closes #122
Root Cause
DefinitionCompilerwraps non-required value types (e.g.,bool option,int option) inOption<T>, but skips reference types entirely because they're already nullable by the CLR. This meansnullable: truestring fields come through as plainstringeven though the user might want the F# type system to reflect the optional/absent distinction.Fix
Add a
WrapNullableStringsstatic parameter (defaultfalse) toOpenApiClientProvider. When set totrue, optional/nullable string fields are wrapped inOption<string>, consistent with howFSharp.Data.SqlClientmapsvarchar NULL→string option.The default is
falseto preserve full backwards compatibility — existing users who read nullable strings asstringare unaffected unless they opt in.Changes
src/SwaggerProvider.DesignTime/v3/DefinitionCompiler.fs?wrapNullableStrings: booloptional parameter; applyOption<string>wrapping when enabledsrc/SwaggerProvider.DesignTime/Provider.OpenApiClient.fsWrapNullableStringsstatic parameter, thread it through toDefinitionCompiler, include in cache keytests/SwaggerProvider.Tests/v3/Schema.TestHelpers.fscompilePropertyTypeWithhelper that acceptsprovideNullableandwrapNullableStringstests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fsTest Status
✅ Build: succeeded (0 errors, warnings are pre-existing)
✅ Unit tests: 268 passed, 0 failed, 2 skipped (same as baseline)
✅ New WrapNullableStrings tests: all 5 pass
✅ Format check:
fantomasapplied and re-verified cleanIntegration/provider tests require a running Swashbuckle test server and were not run; the new parameter does not touch HTTP or serialization logic.