SwaggerProvider
SwaggerProvider copied to clipboard
OpenApiClientProvider with Stripe's open API hangs (3MB json)
Description
The autocompletion hangs and stops working when using OpenApiClientProvider with Stripe's open API. No visible error happens anywhere.
Repro steps
- Try to use OpenApiClientProvider with the stripe API
open SwaggerProvider
let [<Literal>] Schema = "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.sdk.json"
// __SOURCE_DIRECTORY__ + "/openapi/stripe.spec3.json"
type Stripe = OpenApiClientProvider<Schema>
let main argv =
let stripe = Stripe.Client()
// the autocompletion dies or stops working at this point
That JSON file is about 3MB
Expected behaviour
I would expect to work as with any other open API or to get a visible error somewhere
Actual behavior
The autocompletion dies or stops working and doesn't seem to recover. To wait a large amount of time does not help.
Known workarounds
None:
- All F# IDEs behave in the same way.
- Serving the schema from the localhost doesn't make any difference.
Related information
- Mac OS (I tried all known F# IDEs)
- Master
- I used the stripe API which is quite popular: https://github.com/stripe/openapi/tree/master/openapi
A workaround I use is using the Stripe .NET library.
But it's not painless. It's quite low-level library, I have over 500 lines of F# code around it. They change and rename and release new versions all the time, and don't mind the backward compatibility:
A payment system should be a generic component. That would be so nice to have an out-of-box working payment solution for new F# projects. There is nothing project specific, "I just want to make an online-payment". I would be willing to help creating this kind of library...
But the workflows are more complex than you initially think: A customer may want to store a card (Stripe PaymentApiCardId), but the card will expire. A customer can call to you and ask for a (partial) refund. A customer can call to the card company and dispute the transaction. Stripe can respond to a webhook, but you have to have the transaction state-fully stored. And another topic is the EU PSD2 SCA regulations... and local country specific payment regulations.
A generic library is hard to make if you have to include a data storage (some kind of database) and a web-server endpoint for webhooks and also a way of redirecting users to 3D Secure. Suddenly you have to make decisions about technology and that is not generic anymore.
Digging a bit this, it seems to me that the reading of the network schema, parsing it, etc, takes only 2.5 seconds total. But after that, there is some background process, involving ProvidedTypes.fs, that generates a temp-dll (like \AppData\Local\Temp\tmp434F.dll) of having the types. It will finish in around 50 seconds in my computer, and then it's actually done.
This is my little FSI script I used to test what happens:
#I @"C:\git\SwaggerProvider\src\SwaggerProvider.DesignTime\obj\Debug\netstandard2.0\"
#r @"C:\git\SwaggerProvider\src\SwaggerProvider.Runtime\bin\Debug\netstandard2.0\SwaggerProvider.Runtime.dll"
let [<Literal>] Schema = "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.sdk.json";;
let t = System.DateTime.Now;; //#time didn't give correct results so let's user the old way...
type Stripe = SwaggerProvider.OpenApiClientProvider<Schema, PreferAsync = true>;;
printfn "%f" (System.DateTime.Now-t).TotalSeconds;;
What could be done to fix this, is lazy schema exploration:
- Also cache the schema type (OpenApiDocument) and use
.AddMembersDelayed
(.AddXmlDocDelayed
, ...) instead of.AddMembers
- ...to compile the types from the parsed tree only when the user explores the type, not all at the beginning.
- And the recursive type generations should yield lazily recursion, not all at once. And they should be cached (e.g. with lazy-keyword) to avoid resolving them many times.
- That way the slowness would at least divide evenly over the whole user experience, but with e.g. Stripe API, most of the users use actually only tiny bit of the whole API.
- I guess FSharp.Data (its also generative type-provider like this) does it with World Bank provider, I'm expecting they don't download the whole bank at once...
- That would be quite a big change for non-major use-case.
I'm also experiencing a substantial slow down when consuming a bigger OpenApi file like https://api.tourism.testingmachine.eu/swagger/v1/swagger.json.
We have a solution for Stripe now: https://github.com/simontreanor/FunStripe
Nice! But I've experienced those issues also with other OpenAPI services (not 3MB though) and it would be nice to have a solution for those performance problems.
I also had issues of slowdown with bigger openapi files, would be great if there could be some perf optimization, or improve caching (or generate some offline schema that could be re-used from file system, once the generation has happened already?) This type provider is awesome 💌
There are a lot of nested types. The time is spent on ProvidedTypes.fs on method .Compile(isHostedExecution)
This takes 7 seconds wait:
// phase 2 - emit member definitions
This takes 45 seconds wait:
// phase 3 - emit member code
Most of the time in these 2:
// Emit the constructor (if any)
// Emit the methods
If iterateTypes
function could just run parallel on nestedTypes, that could help a lot. However the parameter f
is doing operations on non-theadsafe manner. So using Array.Parallel.iter
would need a major refactor to avoid causing erros.
As this is TP issue in general, I did open issue for the SDK: https://github.com/fsprojects/FSharp.TypeProviders.SDK/issues/341