Not long ago Apple introduced a brand new package for generating networking code from OpenAPI specs – the Swift OpenAPI Generator. The idea of course is not new, Swagger has had its codegen for quite a long time, but it is quite old, unmaintained and relies on Alamofire which we decided to dump in favor of URLSession. So we decided to give Apple a shot and try their generator. This post will not be about how to use it, you can find a bunch of tutorials online or in WWDC videos, I wanna cover our experience and point out some important things.
If you look at the above mentioned tutorials you will probably end up with an integrated generator’s SPM plugin. This is nice, simple and it is set up in a moment. But once we’ve tried that we experienced a significant increase in compile time. Basically, an empty app that compiles in less than a second (clean build of course) now has a clean build time of over 40 seconds and the reason is that the plugin needs to build the whole generator and that takes some time.
But this can be easily solved – just get rid of the plugin and use the generator's binary instead. I suggest using Mint for that as you can specify the generator’s version in Mintfile and you make sure, that you compile each version of the generator just once.
Let’s get back to the plugin integration for a while to see other points why I don’t like it. To make the plugin work you need to have your OpenAPI spec as a resource in your app and it is the same story with the generator’s configuration file. That is something I don’t like as there is no other relevant reason to have it there.
Once you ditch the plugin you can have the OpenAPI spec and the configuration file as part of your repository, without having it in the app bundle, as all you will do is just pass those files as generator’s binary arguments. Also, this reduces the build time to a reasonable value.
Will it even compile? 😬
Well, when we were experimenting with the generator we used some simple OpenAPI specs that worked very well. So we moved to more real life examples and tried it with various specs from our projects. I was actually expecting some issues but all our API specs worked like a charm. That was pretty impressive I think.
So I decided to try some more stuff and used the generator to one of our customer’s API – and here I’ve come to the first issues. The first thing is that the generator currently doesn’t support recursive objects, so if you have them in your schema, the generated code will not compile. There is currently an open issue for that so it is a known thing.
This kind of error is pretty obvious, but that is not always the case. In the same spec the compiler complains about code ambiguity and here starts the fun. How do you know what the root cause is?
Well, you can inspect the spec – the problematic spec has around 6 000 lines so you can enjoy it during the long evenings. If this is not something you wanna do, you can file an issue on the generator, but it looks like you cannot do that without inspecting the spec and isolating the cause because the first question in the issue thread will be to provide the problematic spec and you will probably not be allowed to that as the spec in question belongs to your customer and should probably not be brought to the public.
What code does it actually generate?
The generated code is wrapped into APIProtocol protocol which is nice at first glance but if you have a big spec with a lot of endpoints the protocol becomes huge. This can become an issue when it comes to testing as no testing code is currently generated. You have to implement all functions in that protocol. To work around that I recommend creating another layer above the generated Client that will provide only the subset of endpoints that your view models (repositories and other such objects based on your architecture) actually use. It is also a good idea so you can make sure that only the right parts of your app have access only to the designated endpoints.
It is a shame that the APIProtocol is just one protocol, I can imagine that generating a separate protocol based on the tags in spec would help to keep the code a bit more organized.
Well once you wanna use the generator you need to import at least two packages to your app – its runtime and transport layer. That is something I don’t actually like, I would prefer if the generated code contained everything that is needed but as long as it saves us quite a lot of time, we should be fine with it.
OpenAPI Generator: Is it any good?
The simple answer is yes, definitely, it is good. By using tools such as OpenAPI Generator we can save a lot of boilerplate code that is boring to write. On the other hand, it is no silver bullet and there are some points that you need to be aware of.
For me, the biggest concern is that once the generated code doesn’t compile, you are facing quite a big issue without any obvious solution. My recommendation would be to use the generator for APIs that are completely under your control. Because in the very beginning your spec is simple, but as it evolves it becomes more and more complicated and some of the issues mentioned earlier might arise. If you or your team develops the API, you can probably work around it pretty easily. But if the API is developed by your customer or someone else, it can become problematic, sometimes even impossible and you don’t have much to do.