google / google/grr

OpenAPI doesn't support the semantics of protobuf.oneof so we can't describe it at the moment

Open
#822 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
5.1k
Forks
796
PR merge metrics
No merged PRs in 30d

Description

(Note: I use `oneOf`, `anyOf`, `allOf` (camel-case) to refer to OpenAPI's keywords and `protobuf.oneof` to refer to protobuf's.)

After more thorough investigation and experimentation I figured out that at the moment there is no way to correctly describe `protobuf.oneof`’s behaviour using the OpenAPI Specification.

The first big problem is related to route parameters descriptions in OpenAPI. OpenAPI [does not allow interdependence between parameters](https://github.com/OAI/OpenAPI-Specification/issues/256) (including mutual exclusion which is the core semantic of `protobuf.oneof`). Therefore, there is no way to describe the behavior of `protobuf.oneof` when used to describe a route parameter, because the best we could do with OpenAPI is describe a single parameter with a *single name* (`field_oneof1`, for example) that accepts `anyOf` the schemas of the fields that are part of the `protobuf.oneof`. This is not correct because the `protobuf.oneof` does not define a field with multiple possible types, but rather it defines a mutual exclusion between multiple fields, each with *their own name and type*, so this OpenAPI solution would fail to describe the API correctly (i.e. instead of specifying that a method expects either a `field_one` argument of type `int32`, or a `field_two` argument of type `string`, the OpenAPI description would specify that the API method expects an argument `field_oneof` which can be either of type `int32` or `string`). Therefore, defining the `protobuf.oneof` semantic for method arguments with OpenAPI is impossible at the moment, but what about response types?

The OpenAPI Specification offers the special operators `oneOf`, `anyOf`, `allOf` which can be used to describe a schema by combining other schemas in the following ways:

- “this schema should respect exactly `oneOf` the following schemas”,
- “this schema should respect at least a schema of `anyOf` of the following schemas”,
- “this schema should respect `allOf` the the following schemas (i.e. this schema is a merge of all the following schemas)”.

So the idea I had, was that, at least for result types of API methods, we could describe the protobuf message schema correctly using `anyOf`&`allOf` in the following way: use `allOf` to combine the static fields of the protobuf message with the `protobuf.oneof` fields that have different options. For each `protobuf.oneof` field, define an `anyOf` that holds exactly one OpenAPI object schema for each inner field of a `protobuf.oneof` field. This OpenAPI object schema has exactly one field with the name of the `protobuf.oneof` inner field and its type description. This is a hacky approach, but it seems to work with the popular [ReDoc documentation generation tool](https://github.com/Redocly/redoc), but with a big caveat: if there are two `protobuf.oneof` fields that are described using this approach, only the first one that is described will be displayed.

A second solution for describing the `protobuf.oneof`s that are part of result type messages, would be to describe a separate message for each `protobuf.oneof`s fields combination and describe the result type message schema as an OpenAPI `oneOf` of all the possible messages. This solution implies generating a number equal to the cardinal of the Cartesian product of all the sets that represent the inner fields of all `protobuf.oneof`s that are part of the result type protobuf message, which can be a relatively large number of type schemas to display in the documentation and it would require a (confusing/impractical) naming convention for the generated combination types.

Therefore, there is no way to practically describe the `protobuf.oneof` using OpenAPI, neither for parameter types, nor for result types.

The current “compromise” solution that I'm developing is to define all the inner `protobuf.oneof`s’ fields as standalone parameters or top-level message fields and add a comment describing the fact that they are part of a `protobuf.oneof` and only one of them should be chosen per `protobuf.oneof`.

If in the future the OpenAPI Specification allows to describe the desired behavior, support for it should be implemented in the OpenAPI description generation code.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.