How to generate swagger3 (OpenAPI3) spec in (.json/.yaml) from protobuf (.proto) files?
Asked Answered
L

4

12

My original use-case:

I am building an application in GO with a gRPC server (using protobuf), and wrapping it inside an HTTPS server (using gin). Only the HTTPS server is being published to the clients for use (by which I mean that my application can be accessed via REST API, that actually then dials on the gRPC endpoint), and I am publishing it using Swagger OpenAPI3 (version 3 is the main requirement here) specification. Both gRPC and HTTPS is required, and any solution should adhere to this architecture.

I don't want to maintain my server specification at two places, that is I don't want to maintain both proto files (.proto) and swagger spec (.json/.yaml). Since I absolutely have to write proto files to generate gRPC server, I want to automate the swagger spec generation (OpenAPI3).

Where I am:

I am able to generate swagger OpenAPI2 spec from protobuf files (.proto) using grpc-gateway library something like so: grpc-rest-go-example. But my requirement is OpenAPI3; more specifically I want to use the oneOf feature in OpenAPI3 and map to it from oneof feature of proto. This is not possible with OpenAPI2, as it does not allow an API to have a request/response body of multiple type definitions, which was a feature added in OpenAPI3 by enabling oneOf, anyOf and allOf constructs.

While trying to do so, I stumbled upon this library by GoogleAPIs googleapis/gnostic, the description for which is:

This repository contains a Go command line tool which converts JSON and YAML OpenAPI descriptions to and from equivalent Protocol Buffer representations.

At first look, this seems to exactly solve my problem, but as it turns out, this library only interconverts between protocol buffer (protobuf) binary (.pb) and swagger OpenAPI2/OpenAPI3 (.json/.yaml) files, which brings me to my new problem.

So for example for the following .pb file:


�3.0.1�…�
�Example service��Example service description*�
�Example contact2=

Apache 2.0�/http://www.apache.org/licenses/LICENSE-2.0.html:�1.0�!
�//localhost:9999/example/api/v1"â�
�
�/exampleResource��"���Example API��Example API description*�example-operation2B
@
example-query��query��example-query description �R�
    Ê��stringBÇ��œ�
�200�”�
‘�
�OK�Š�
C
�application/json�/
-�+
)#/components/schemas/common.StatusMessage
C
�application/yaml�/
-�+
)#/components/schemas/common.StatusMessage�¥�
�400���
š�
�Bad Request�Š�
C
�application/json�/
-�+
)#/components/schemas/common.StatusMessage
C
�application/yaml�/
-�+
)#/components/schemas/common.StatusMessage*Y
W
U
�common.StatusMessage�=
;Ê��objectú�/
�
�message��
    ��string
�
�status��
    ��string

it generates the following swagger file:

openapi: 3.0.1
info:
  title: Example service
  description: Example service description
  contact:
    name: Example contact
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: "1.0"
servers:
- url: //localhost:9999/example/api/v1
paths:
  /exampleResource:
    get:
      summary: Example API
      description: Example API description
      operationId: example-operation
      parameters:
      - name: example-query
        in: query
        description: example-query description
        required: true
        schema:
          type: string
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/common.StatusMessage'
            application/yaml:
              schema:
                $ref: '#/components/schemas/common.StatusMessage'
        400:
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/common.StatusMessage'
            application/yaml:
              schema:
                $ref: '#/components/schemas/common.StatusMessage'
components:
  schemas:
    common.StatusMessage:
      type: object
      properties:
        message:
          type: string
        status:
          type: string

The .pb may not view properly, access it here. So something like:

�status��
    ��string

looks like:

<0x06>status<0x12><0x0b>
    Ê<0x01><0x06>string

For above example, I first wrote the swagger spec and then generated the .pb but same can be done the other way around.

Current state:

If I have a way to convert between (.pb) and (.proto) files, the conversion loop will be closed and complete (.proto -> .pb -> .json/.yaml -> .pb -> .proto).

I am sure there has to be a way to achieve this, and hence there exists a solution to my original problem. But I could not find any article or piece of code that does it. Are there sane ways to interconvert between .pb and .proto files?

If you have an entirely different solution to my original use-case, please feel free to share that as well. It would help a lot.

Thanks in advance!

Edits:

(1) Thanks to recent comments, it is clear that a "conversion" between .pb and .proto is an absurd ask in the first place. But the original problem remains the same i.e. how to generate swagger3 (OpenAPI3) spec from protobuf file (.proto), either using annotations, tags or otherwise. Changing question title accordingly.

(2) Just the next day after I posted this, I bumped into gnostic-grpc repository, the description of which says:

This tool converts an OpenAPI v3.0 API description into a description of a gRPC service that can be used to implement that API using gRPC-JSON Transcoding.

Again, this got me exited too soon. Actually, this was a GSOC project, and as amazing as the idea of this repository is, it does not fulfill the requirements. Moreover, this is not an interconversion library, and very immature for any production use. In fact, it fails to deliver some of the basic fundamental features of the OpenAPI3 spec.

But this repository is headed in the right direction. My conclusion is to have a custom plugin that does this, mostly by extending the annotations library in GO.

(3) Apparently there are no good and obvious candidates for converting from .proto to OpenAPI3 spec (.yaml/.json), except gnostic-grpc which is very immature and highly work in progress for any kind of real use.

But for the reverse conversion, i.e. OpenAPI3 spec (.yaml/.json) to .proto, there is a good library called openapi-generator under OpenAPITools, which converts OpenAPI v2/3 spec to client/server stubs for almost all platforms. But since this is not the original ask, the question still remains open.

Lambertson answered 21/4, 2020 at 18:3 Comment(5)
You don't convert protobuf payloads at all to .proto; that doesn't mean anything. There are 2 protobuf formats: (a) the binary protobuf encoding - this is what you are calling .pb in the question, and (b) an opinionated json variant that expresses the same intent in json; with that context (i.e. that the .proto means something completely different), can you clarify what you want to do here?Cronyism
Oh, okay! My exact use case is to write proto files -> generate gRPC server -> generate HTTPS server and also generate swagger spec for the server. I know this is possible for openAPI2 using grpc-gateway library (which uses annotations in .proto file), but cannot find anything for OpenAPI3. I want to convert .proto to OpenAPI3 spec, using annotations, tags or otherwise.Lambertson
grpc-gateway has had an issue open for a couple years to support openAPI v3: github.com/grpc-ecosystem/grpc-gateway/issues/441 I need this too. Maybe we could work on it together. Since it already generates openAPIv2 it seems like the lift would be less than writing one from scratch.Soup
I would love to work on it. Currently I am developing python scripts to do that interconversion, and I'll ofc open source that, but a more universal solution in a more reachable repository will be much more amazing and anyway, python scripts are just a work around. But about the second part, I think it will definitely be less work than writing things from scratch, but still a lot which is good! This is because OpenAPIv3 is highly backwards incompatible.Lambertson
any update on your issue? I have the exact same case, we have openApiv2 and we want to migrate to openApiv3Doubs
A
1

Although, your requirement seems to be to get a YAML (OpenAPNv3) specification from a PROTO, you may checkout this plugin - gnostic-grpc - for gnostic which does the reverse i.e. converts from a YAML/JSON spec to a proto along with gRPC service calls.

Akimbo answered 29/4, 2020 at 8:12 Comment(4)
Yes, I bumped into this just after the day I posted this question. As amazing as the idea of this repository is, this is not ready for use. This was a GSOC project, has bugs. And its conversion is one way, as you also said, and incomplete. It doesn't support most of the openAPI constructs, and the ones it supports are not taking advantage of openAPI 3 features. I have come to the conclusion that writing a plugin is the best solution, it's not difficult. On top of that, I decided to contribute to gnostic because I love the idea. It is a real use case for which surprisingly no solution exists.Lambertson
Absolutely the case. In fact we have been trying to make its use and landed into multiple limitations due to seemingly incomplete or partial support of OpenAPI. But there's surprisingly no other implementation that goes as close to getting proto (specifically gRPC) from YAML other than this one unfortunately.Akimbo
I also have an open question on GitHub for the same.Lambertson
@AnupamaDeshmukh I am one of the main contributors of gnostic-grpc. Could you go into more detail what did not work for you? Feel free to open issues in the repository.Josphinejoss
K
0

We failed to find the direct way to convert .proto file to OpenAPI3/Swagger3. Here is one workaround way

  • convert .proto files to Swagger2 through protoc-gen-openapiv2
  • then convert the Swagger2 files to Swagger3 through this link
    • Swagger Editor
    • Swagger Converter
    • Swagger Codegen version 3.x

Sample

protoc --proto_path=${PROTO_PATH} --swagger_out=logtostderr=true:./swagger.json 

swagger-codegen generate -i ./swagger.json -l openapi-yaml -o swaggerv3.yaml
Keshiakesia answered 26/7, 2022 at 3:5 Comment(3)
But the swagger3 specific information is lost in this translation.Lambertson
@KrishnaBirla, could you please figure out the details of the lost swagger3 specific information? In my case, the converted swaggerv3 files could be works well with the tool openapi-python-client.Keshiakesia
@KrishnaBirla, could you please try another module api-spec-converter, which could convert swagger2.0 to openapi 3. api-spec-converter --from=swagger_2 --to=openapi_3 --syntax=yaml ./swagger2.json > openapi3.yamlKeshiakesia
D
0

you need to change your "buf.gen.yaml" little bit:

version: v1
plugins:
  - plugin: go
    out: proto
    opt: paths=source_relative
  - plugin: go-grpc
    out: proto
    opt: paths=source_relative,require_unimplemented_servers=true
  - plugin: grpc-gateway
    out: proto
    opt: paths=source_relative
  - plugin: openapiv2
    out: proto

after this, do : buf mod update

then, whenever you will use buf generate, it will generate swagger.json file for you.

Disesteem answered 23/2, 2023 at 7:26 Comment(0)
C
0

protoc-gen-openapi of gnostic seems to work. It converts .proto to OpenAPIv3 YAML, and has oneOf and all. See examples for the usage.

For your convenience, here are some snippets:

Installation

go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest

buf.yaml

version: v1
deps:
  - buf.build/googleapis/googleapis
  - buf.build/gnostic/gnostic

buf.gen.yaml

version: v1
plugins:
  - plugin: openapi
    out: .
Candicandia answered 15/7, 2023 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.