My issues with Swagger
As far as I can tell, Swagger has 2 main use cases: it’s a specification you can use to scaffold your client/server or a 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, 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 it, though some cases could be handled with e.g. macros. As such, we are forced to use some more manual approach.
The only solution supported by the Swagger team is Java implementation aimed towards 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 in runtime to generate either JSON or 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 could annotate the class,
- if we want to annotate class, we need to extract its content into a method, so that we could annotate it,
- of course models also require annotation which makes it, a bother if you reuse some 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 swagger-core
generator.
Personally, I would like to have some limited DSL, that handles both Akka-HTTP routing creation and swagger generation, but let’s deal with what we have. As we see later, we should be thankful, that author of swagger-akka-http made it possible to use it with Scala without going completely berserk.
Validations? Use 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 Swagger team, it should produce a correct specification. I agree: it should.
The Swagger specification has few assumptions/requirements, that you might not notice, but which are extremely important: paths’ names and models’ names 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 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 pibull 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 directives is missing.
The Java’s generator uses qualified named for neither paths nor models. That means that if in the scope of a whole application you happen to have 2 routes/model classes with the same simple name, by default resulting specification is invalid. You would have to override the class name using value
field of an annotation.
On several occasions I have an issue when I used the best name possible for my class - short, descriptive, without redundant information, that could be deduced from the package - to see that, well, the fact that it is a different scope, doesn’t mean that Swagger would generate a different name. And so the docs broke.
Annotations are an incomplete model
Let us assume you are one of those freaks, that believes that good documentation matters, as so if you can provide your users information about e.g. what you expect from the input, you should do it as precisely as you can.
When it comes to model, Swagger allows you to specify, not only what primitive you use somewhere, and what is the set of allowed values, but also something like format. For instance, field with 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 endpoint always receives a string with a valid date format.
Thing is, annotations don’t allow you to define this value everywhere. In some place you can e.g. @ApiParam
in some 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 expects JSON, you cannot!
Runtime dependencies
I prefer Circe. And Jaws. 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, that I decided to write my own small sbt-plugin to move this unpleasant phase to the build.
Outdated generator
The generator works on Open API 2.0 specification. The newest one is 3.0. 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. Same with, Seq
, List
, Map
, … swagger-core
1.5.x which generates Open API 2.0 has something called ModelConverter
to make Option act like a nullable (instead of a container with a field), sequences become JSON’s lists and Map
s becomes objects.
There is no Scala model converter for Swagger 2.x aka Open API 3.0 at the moment. Author of swagger-akka-http does his best to bring it to us, and by that make it possible to migrate to newer Swagger, but he is alone in his efforts. And since 1.5.x is not actively developed some of the issues above might not be ever addressed.
No polymorphism for UI
Swagger specification supports polymorphism. As a matter of the fact, annotation also generates them. Still, you cannot see that, because Swagger UI (both older, 2.0, and newest, 3.0) are not supporting 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 official tools.
Summary
I guess my problems are not that common. Surely they are not something Java community complains about, otherwise, 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 better Swagger replacement, or at least better Swagger tooling for Scala.