Using springdoc-openapi to generate an OpenAPI spec is a convenient and fast way of embedding the existing annotations into the spec. For example, a @NotNull check both ensures the check happens and tells all potential clients that the field cannot accept null. This is an efficient way to do it, but there are two major downsides. First, for large projects it becomes increasingly hard to know beforehand which changes affect the spec, because essentially any model could be exposed, either on purpose or by accident. Second, the generation focuses on translating the models and does not always ensure that the resulting spec is correct.
The fix for the first problem was fairly simple: commit the spec to git and assign a specific reviewer group to that file, enforcing ownership of the external contract. This also makes it easier to see when a code change alters the spec, as it shows up in the review overview. The spec is kept up to date by regenerating it in CI and checking that there are no differences from the committed file.
Validation
Committing the spec also opened the door to fixing the second problem. With the spec as a file in the repository, checks can be run directly against it. Here is a quick overview of the issues we ran into and how we fixed them:
- A “license” field was added but never actually filled out, which triggered a warning in all clients.
- Simply removing the field fixed it.
- A sealed interface with a few specific implementations was generated as a
oneOfwithtypeas the discriminator. The generator also added thetypefield as a regular property on each option, so a payload could match more than one option, which makes theoneOfambiguous according to the standard.- Moving the
typefield into anallOfalongside theoneOffixed it. This was a weird issue that we would not have known about without the check.
- Moving the
- Our models are written in English, but the exposed JSON values are translated to Danish. One of those values contained a non-ASCII character, which caused issues with some client generators.
- Replacing it with its ASCII equivalent fixed it.
- An automatic test ensures consistency by verifying that the committed spec contains only ASCII characters.
- The same translation caused two English enum constants to map to the same Danish JSON value, so the value was declared twice in the enum.
- The fix was simple, but we also added an automatic test to ensure all enum values in the spec are unique.
Additionally, we extended the CI to run a check whenever the spec changes, verifying that a client can be generated from it without any warnings. This ensures errors are caught early.
Takeaway
When exposing an OpenAPI spec as a contract for external parties, it should be committed to the codebase so each version can be properly tracked alongside the application code. This should be verified in the CI pipeline, and additional checks can be made to verify that the spec is valid and can be used to generate a client.