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
14 changes: 14 additions & 0 deletions docs/OpenApiClientProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ let client = PetStore.Client()
| `PreferNullable` | Provide `Nullable<_>` for not required properties, instead of `Option<_>`. Defaults value `false`. |
| `PreferAsync` | Generate async actions of type `Async<'T>` instead of `Task<'T>`. Defaults value `false`. |
| `SsrfProtection` | Enable SSRF protection (blocks HTTP and localhost). Set to `false` for development/testing. Default value `true`. |
| `IgnoreParseErrors` | Continue generating the provider even when the OpenAPI parser reports validation errors (e.g. vendor extensions or non-strictly-compliant schemas). Warnings are printed to stderr. Default value `false`. |

More configuration scenarios are described in [Customization section](/Customization)

Expand All @@ -41,6 +42,19 @@ type ProdApi = OpenApiClientProvider<"https://api.example.com/swagger.json">

**Warning:** Never set `SsrfProtection=false` in production code.

## Non-Strictly-Compliant Schemas (IgnoreParseErrors)

Some OpenAPI schemas generated by tools such as [NSwag](https://github.com/RicoSuter/NSwag) may include extensions or properties that are technically invalid (e.g. `nullable: true` at the parameter level). By default, SwaggerProvider aborts with an error when the Microsoft.OpenApi parser reports such validation errors.

Set `IgnoreParseErrors=true` to continue generating the type provider despite these errors. Validation warnings are printed to stderr so they remain visible:

```fsharp
// NSwag-generated schema with non-standard nullable annotations
type MyApi = OpenApiClientProvider<"https://example.com/swagger.json", IgnoreParseErrors=true>
```

**Note:** Only use `IgnoreParseErrors=true` when you trust the schema source. Suppressing errors may hide genuine schema problems that could affect the generated client.

## Sample

```fsharp
Expand Down
22 changes: 15 additions & 7 deletions src/SwaggerProvider.DesignTime/Provider.OpenApiClient.fs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type public OpenApiClientTypeProvider(cfg: TypeProviderConfig) as this =
ProvidedStaticParameter("IgnoreControllerPrefix", typeof<bool>, true)
ProvidedStaticParameter("PreferNullable", typeof<bool>, false)
ProvidedStaticParameter("PreferAsync", typeof<bool>, false)
ProvidedStaticParameter("SsrfProtection", typeof<bool>, true) ]
ProvidedStaticParameter("SsrfProtection", typeof<bool>, true)
ProvidedStaticParameter("IgnoreParseErrors", typeof<bool>, false) ]

t.AddXmlDoc
"""<summary>Statically typed OpenAPI provider.</summary>
Expand All @@ -47,7 +48,8 @@ type public OpenApiClientTypeProvider(cfg: TypeProviderConfig) as this =
<param name='IgnoreControllerPrefix'>Do not parse `operationsId` as `<controllerName>_<methodName>` and generate one client class for all operations. Default value `true`.</param>
<param name='PreferNullable'>Provide `Nullable<_>` for not required properties, instead of `Option<_>`. Defaults value `false`.</param>
<param name='PreferAsync'>Generate async actions of type `Async<'T>` instead of `Task<'T>`. Defaults value `false`.</param>
<param name='SsrfProtection'>Enable SSRF protection (blocks HTTP and localhost). Set to false for development/testing. Default value `true`.</param>"""
<param name='SsrfProtection'>Enable SSRF protection (blocks HTTP and localhost). Set to false for development/testing. Default value `true`.</param>
<param name='IgnoreParseErrors'>Continue generating the provider even when the OpenAPI parser reports validation errors (e.g. vendor extensions or non-strictly-compliant schemas). Warnings are printed to stderr. Default value `false`.</param>"""

t.DefineStaticParameters(
staticParams,
Expand All @@ -58,6 +60,7 @@ type public OpenApiClientTypeProvider(cfg: TypeProviderConfig) as this =
let preferNullable = unbox<bool> args.[3]
let preferAsync = unbox<bool> args.[4]
let ssrfProtection = unbox<bool> args.[5]
let ignoreParseErrors = unbox<bool> args.[6]

// Cache key includes cfg.RuntimeAssembly, cfg.ResolutionFolder, and cfg.SystemRuntimeAssemblyVersion
// to differentiate between different TFM builds (same approach as FSharp.Data)
Expand All @@ -69,6 +72,7 @@ type public OpenApiClientTypeProvider(cfg: TypeProviderConfig) as this =
preferNullable,
preferAsync,
ssrfProtection,
ignoreParseErrors,
cfg.RuntimeAssembly,
cfg.ResolutionFolder,
cfg.SystemRuntimeAssemblyVersion)
Expand All @@ -89,11 +93,15 @@ type public OpenApiClientTypeProvider(cfg: TypeProviderConfig) as this =
let schema, diagnostic = (readResult.Document, readResult.Diagnostic)

if diagnostic.Errors.Count > 0 then
failwithf
"Schema parse errors:\n%s"
(diagnostic.Errors
|> Seq.map(fun e -> $"%s{e.Message} @ %s{e.Pointer}")
|> String.concat "\n")
if ignoreParseErrors then
diagnostic.Errors
|> Seq.iter(fun e -> eprintfn "SwaggerProvider warning: %s @ %s" e.Message e.Pointer)
else
failwithf
"Schema parse errors:\n%s"
(diagnostic.Errors
|> Seq.map(fun e -> $"%s{e.Message} @ %s{e.Pointer}")
|> String.concat "\n")

let defCompiler = DefinitionCompiler(schema, preferNullable)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"openapi": "3.0.0",
"info": {
"title": "Nullable Parameter Test",
"version": "1.0.0"
},
"paths": {
"/items/{id}": {
"get": {
"operationId": "GetItem",
"summary": "Get an item by ID",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"nullable": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
}
}
27 changes: 27 additions & 0 deletions tests/SwaggerProvider.Tests/Schema.Parser.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,33 @@ let ``Add definition for schema with only allOf properties``() =
definitions |> shouldHaveLength 1
definitions[0].GetDeclaredProperty("FirstName") |> shouldNotEqual null

[<Fact>]
let ``Schema with nullable parameter-level property triggers parse errors``() =
let schemaPath =
__SOURCE_DIRECTORY__
+ "/../SwaggerProvider.ProviderTests/Schemas/v3/nullable-parameter-issue261.json"

let settings = OpenApiReaderSettings()
settings.AddYamlReader()

let readResult =
Microsoft.OpenApi.OpenApiDocument.Parse(File.ReadAllText schemaPath, settings = settings)
// Microsoft.OpenApi reports 'nullable' at the parameter level as an error in OpenAPI 3.0
// When this is true, IgnoreParseErrors=true allows the provider to proceed instead of failing
if readResult.Diagnostic.Errors.Count > 0 then
readResult.Diagnostic.Errors
|> Seq.exists(fun e -> e.Message.Contains("nullable"))
|> shouldEqual true

[<Fact>]
let ``Schema with nullable parameter is still parseable despite errors``() =
let schemaPath =
__SOURCE_DIRECTORY__
+ "/../SwaggerProvider.ProviderTests/Schemas/v3/nullable-parameter-issue261.json"

// Even with diagnostic errors, the schema should still compile without throwing an exception
File.ReadAllText schemaPath |> V3.testSchema |> ignore

(*
[<Tests>]
let parseJsonSchemaTests =
Expand Down
Loading