Skip to content

twelsh-aw/protoc-gen-openapi

 
 

Repository files navigation

protoc-gen-openapi

GitHub release (latest by date) lint status go report card

Yes, this is another protoc generator for OpenAPI. I created this for a couple reasons...

  • I wanted to learn protoc generation with a real-world problem.
  • The official google one sticks to gRPC and envoy standards. My team and I use Twirp and other REST frameworks. Sometimes you just want to define models and an API for docs.
  • Others try to do too much per the spec and fail to do the most common things well.

DISCLAIMER: This will be a limited subset of the OAPI3 specification. Not everything will make it in here. Why? Read the last bullet point above. :)

Some patterns were heavily inspired by gnostic.

Installation

go install github.com/technicallyjosh/protoc-gen-openapi@latest

Options

Option Description Default
version The version of the API. 0.0.1
title The title of the API.
description A description of the API.
ignore A list of proto package names to ignore delimited by pipes.
default_response The default response to be used.1
content_type The content type to be associated with all operations.1 application/json
json_names Use the JSON names that Protobuf provides. Otherwise, proto field names are used. false
json_out Create a JSON file instead of the default YAML. false
host The host to be used for all operations.1

1 Can be overridden on a file, service, or method.

Build Examples

Below are some basic examples on how to use this generator.

Protoc

protoc \
  --openapi_out=. \
  --openapi_opt=version=1.0.0 \
  --openapi_opt=title="My Awesome API" \
  api/some_service.proto

Buf

buf.yaml

# ... other things
deps:
  - buf.build/technicallyjosh/protoc-gen-openapi

buf.gen.yaml

plugins:
  - name: go
    out: api
    opt:
      - paths=source_relative
  - name: openapi
    strategy: all # important so all files are ran in the same generation.
    out: api
    opt:
      - title=My Awesome API
      - description=Look how awesome my API is!
      - ignore=module.v1|module.v2
      - default_response=SomeErrorObject

Basic Usage Example

syntax = "proto3";

import "oapi/v1/field.proto";
import "oapi/v1/file.proto";
import "oapi/v1/method.proto";
import "oapi/v1/service.proto";

option (oapi.v1.file) = {
  servers {
    url: "myawesomeapi.com"
  }

  security_schemes: {
    name: "bearer_auth"
    scheme: {
      type: "http"
      scheme: "bearer"
      bearer_format: "JWT"
    }
  }

  // Since this is at the file level, it's applied to all.
  security: {
    name: "bearer_auth"
    scopes: ["read:resource"]
  }
};

service MyService {
  option (oapi.v1.service) = {
    prefix: "/v1"
    x_display_name: "My Service"
  };

  rpc CreateSomething (CreateSomethingRequest) returns (CreateSomethingResponse) {
    option (oapi.v1.method) = {
      post: "create-something"
      summary: "Create Something"
      status: 201
    };
  }
}

message CreateSomethingRequest {
  // The name of something.
  // Example: something-awesome
  string name = 1 [(oapi.v1.required) = true];
}

message CreateSomethingResponse {
  // The ID of something.
  string id = 1;
  string name = 2;
}

Features

Note

Defining features is a work in progress. I aim to explain all that's possible the best I can.

Server definitions

You can define servers at the file, service, or method level. Each one overrides the previous. This allows for more advanced composition.

Example:

syntax = "proto3";

import "google/protobuf/empty.proto";
import "oapi/v1/file.proto";
import "oapi/v1/method.proto";
import "oapi/v1/service.proto";


option (oapi.v1.file) = {
  servers {
    url: "myawesomeapi.com" // file-defined for all services and methods
  }
};

service MyService {
  option (oapi.v1.service) = {
    servers {
      url: "myawesomeapi2.com" // overrides file-defined
    }
  };

  rpc CreateSomething (google.protobuf.Empty) returns (google.protobuf.Empty) {
    option (oapi.v1.method) = {
      servers {
        url: "myaweseomeapi3.com" // overrides service-defined
      }
    };
  }
}

Service Prefixes

Each service can have a path prefix set for all methods to inherit. This is useful when versioning your API or if you have a parameter that is defined for each method route.

You can override the entire path in the method by starting the path out with a /.

Example:

syntax = "proto3";

import "google/protobuf/empty.proto";
import "oapi/v1/file.proto";
import "oapi/v1/method.proto";
import "oapi/v1/service.proto";

option (oapi.v1.file) = {
  servers {
    url: "myawesomeapi.com"
  }
};

service MyService {
  option (oapi.v1.service) = {
    prefix: "/v1"
  };

  rpc CreateSomething (google.protobuf.Empty) returns (google.protobuf.Empty) {
    option (oapi.v1.method) = {
      post: "create" // becomes /v1/create
    };
  }

  rpc OverrideSomething (google.protobuf.Empty) returns (google.protobuf.Empty) {
    option (oapi.v1.method) = {
      get: "/create" // becomes /create
    };
  }
}

Features In Progress

  • Enum requirements on fields

Contributing

Coming... Right now I prefer that it's just me until I get a solid hold on generator patterns and the package is stable. I'm fully open to any suggestions though!

About

OpenAPI generation from Protobuf

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 97.6%
  • Makefile 1.2%
  • Dockerfile 1.2%