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.
go install github.com/technicallyjosh/protoc-gen-openapi@latest
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.
Below are some basic examples on how to use this generator.
protoc \
--openapi_out=. \
--openapi_opt=version=1.0.0 \
--openapi_opt=title="My Awesome API" \
api/some_service.proto
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
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;
}
Note
Defining features is a work in progress. I aim to explain all that's possible the best I can.
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
}
};
}
}
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
};
}
}
- Enum requirements on fields
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!