My issues with Swagger
As far as I can tell, Swagger has two main use cases: it’s a specification you can use to scaffold your client and server, or live documentation generated from an existing implementation. I have an issue with the latter when it comes to Akka HTTP.
Annotations everywhere
First of all, here’s how it works. Akka HTTP has no Swagger generator. Considering the complexity of the routing DSL, it would be hard, if not impossible, to achieve this, though some cases could be handled with, e.g., macros. As such, we are forced to use a more manual approach.
The only solution supported by the Swagger team is the Java implementation aimed toward the JEE stack, as some of the annotations it uses are exactly the ones used to generate routing in JEE.
Exactly! Annotations. Descriptions for models and directives are defined using annotations, which will be processed at runtime to generate either a JSON or a YAML document.
That imposes some restrictions on us:
- if we want to gather some endpoints, we should organize them in classes or traits, so that we can annotate the class,
- if we want to annotate a class, we need to extract its content into a method, so that we can annotate it,
- of course, models also require annotations, which makes it a bother if you reuse a model from another module that you wouldn’t want to annotate.
Once we organize the code this way and annotate it, we still need to put Class[_] instances into the swagger-core generator.
Personally, I would like to have a limited DSL that handles both Akka HTTP routing creation and Swagger generation, but let’s deal with what we have. As we will see later, we should be thankful that the author of swagger-akka-http made it possible to use it with Scala without going completely berserk.
Validations? Use an online parser
swagger-core, the library responsible for generating Swagger specifications, does not validate its own output. One could ask: what could be wrong? The generator is made by the Swagger team; it should produce a correct specification. I agree: it should.
The Swagger specification has a few assumptions/requirements that you might not notice but that are extremely important: the names of paths and models should be unique.
If you are writing it yourself, that is an intuitive assumption. You wouldn’t even notice that you handled that issue. So what would the generator do with the following files:
package com.pets
@Api(value = "/pet", description = "Operations about pets")
trait PetHttpService extends HttpService {
@ApiOperation(
httpMethod = "GET",
response = classOf[Pet],
value = "Returns a pet based on ID"
)
@ApiImplicitParams(Array(
new ApiImplicitParam(
name = "petId",
required = false,
dataType = "integer",
paramType = "path",
value = "ID of pet that needs to be fetched"
)
))
@ApiResponses(Array(
new ApiResponse(code = 400, message = "Invalid ID Supplied"),
new ApiResponse(code = 404, message = "Pet not found"))
)
def petGetRoute = get { path("pet" / IntNumber) { petId =>
complete(s"Hello, I'm pet ${petId}!")
} }
}
package com.pets.pitbulls
@Api(value = "/pet/pitbulls", description = "Operations about pitbulls")
trait PetHttpService extends HttpService {
@ApiOperation(
httpMethod = "GET",
response = classOf[Pet],
value = "Returns a pitbull based on ID"
)
@ApiImplicitParams(Array(
new ApiImplicitParam(
name = "petId",
required = false,
dataType = "integer",
paramType = "path",
value = "ID of pet that needs to be fetched"
)
))
@ApiResponses(Array(
new ApiResponse(code = 400, message = "Invalid ID Supplied"),
new ApiResponse(code = 404, message = "Pet not found"))
)
def petGetRoute = get { path("pet" / "pitbulls" / IntNumber) { petId =>
complete(s"Hello, I'm pitbull ${petId}!")
} }
}
Well, first of all, it wouldn’t complain. Second, it would generate JSON or YAML. Then, once you opened the docs in, e.g., Swagger UI, you would find that one of the directives is missing.
The Java generator uses qualified names for neither paths nor models. That means that if in the scope of a whole application you happen to have two route/model classes with the same simple name, by default, the resulting specification is invalid. You would have to override the class name using the value field of an annotation.
On several occasions I had an issue when I used the best name possible for my class - short, descriptive, without redundant information that could be deduced from the package - only to see that the fact that it is in a different scope doesn’t mean that Swagger will generate a different name. And so the docs broke.
Annotations are an incomplete model
Let us assume you are one of those freaks who believe that good documentation matters, and so if you can provide your users with information about, e.g., what you expect from the input, you should do it as precisely as you can.
When it comes to a model, Swagger allows you to specify not only what primitive you use somewhere and what the set of allowed values is, but also something like format. For instance, a field with the string data type might have a date format. Then Swagger UI could generate a nice calendar, and client generators could output code that makes sure that the endpoint always receives a string with a valid date format.
The thing is, annotations don’t allow you to define this value everywhere. In some places you can, e.g., use @ApiParam; in others you cannot, e.g., @ApiModelProperty. That means that if you are passing an argument to the endpoint you can specify that this string is actually a date or dateTime. But if you send or expect JSON, you cannot!
Runtime dependencies
I prefer Circe. And Jawn. Surely, I could live without Jackson. And without runtime reflection.
If the rest of your project uses only type-level programming and compile-time reflection, you might be unhappy about this sudden need to include dependencies and ugly Java idiosyncrasies.
Since swagger-akka-http doesn’t address this, I decided to write my own small sbt-plugin to move this unpleasant phase to the build.
Outdated generator
The generator works with the OpenAPI 2.0 specification. The newest version is 3.0. The thing is, you cannot migrate if you are using Swagger with Scala.
It is easy to forget, but Scala’s Option is not something that Swagger would recognize out of the box. The same goes for Seq, List, Map, … swagger-core 1.5.x, which generates OpenAPI 2.0, has something called ModelConverter to make Option act like a nullable type (instead of a container with a field), sequences become JSON lists, and Maps become objects.
There is no Scala model converter for swagger-core 2.x (which targets OpenAPI 3.0) at the moment. The author of swagger-akka-http does his best to bring it to us and, by doing that, make it possible to migrate to the newer Swagger version, but he is alone in his efforts. And since 1.5.x is not actively developed, some of the issues above might never be addressed.
No polymorphism for U(I)
The Swagger specification supports polymorphism. As a matter of fact, the annotations also generate them. Still, you cannot see this, because neither the older (2.0) nor the newer (3.0) Swagger UI supports polymorphic models.
This issue not only affects people who generate Swagger from code (like me) but also people who would like to write Swagger in an editor and generate interfaces from the specification. You see, Swagger Editor is built on top of Swagger UI. You can generate specifications from polymorphic classes and you can generate polymorphic classes from the specification, but you cannot preview it using the official tools.
Summary
I guess my problems are not that common. Surely they are not something the Java community complains about; otherwise, the Swagger team would do something about them.
But I believe I am not the only one. I hope that one day we will have either a better replacement for Swagger or at least better Swagger tooling for Scala.