Skip to content

Instantly share code, notes, and snippets.

@kevinswiber
Last active April 22, 2020 08:03
Show Gist options
  • Save kevinswiber/867699e4efe57ccde83fb510c9de6075 to your computer and use it in GitHub Desktop.
Save kevinswiber/867699e4efe57ccde83fb510c9de6075 to your computer and use it in GitHub Desktop.
Go template for converting gRPC service proto files into a Postman Collection using `protoc-gen-doc`.
{{- $messages := (index .Files 0).Messages -}}
{{- if gt (len .Files) 1 -}}
{{- range $file := (slice .Files 1) -}}
{{- $messages = concat $messages $file.Messages -}}
{{- end -}}
{{- end -}}
{
"info": {
"name": "{{coalesce (env "POSTMAN_COLLECTION_NAME") "Imported gRPC JSON API"}}",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"description": "## Messages\n\n
{{- range $msg := $messages }}
{{- $msgDesc := (toJson $msg.Description) }}#### {{ $msg.FullName }}\n\n
{{- substr 1 (int (sub (len $msgDesc) 1)) $msgDesc }}\n\n
{{- if $msg.HasFields }}| Field | Type | Description |\n| ----- | ---- | ----------- |\n
{{- range $f := $msg.Fields }}
{{- $desc := (toJson $f.Description) }}{{ $descLen := len $desc}}| {{$f.Name}} | {{$f.FullType}} | {{substr 1 (int (sub (len $desc) 1)) $desc}} |\n
{{- end }}
{{- end }}\n
{{- end }}"
},
"item": [
{{- $fileLen := len .Files }}
{{- range $fileIndex, $file := .Files -}}
{{- if $file.HasServices }}
{
"name": "{{$file.Name}}",
"description": {{$file.Description | toJson }},
"item": [
{{- $serviceLen := len $file.Services}}
{{- range $serviceIndex, $service := .Services}}
{
"name": "{{$service.FullName}}",
"description": {{$service.Description | toJson }},
"items": [
{{- $methodLen := len $service.Methods}}
{{- range $methodIndex, $method := $service.Methods}}
{
"name": "{{$method.Name}}",
"request": {
"description": {{(printf "%s\n\n\n\nRequest Message: `%s`\n\nResponse Message: `%s`\n" $method.Description $method.RequestFullType $method.ResponseFullType) | toJson }},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/grpc+json"
},
{
"key": "Grpc-Insecure",
"value": "{{"{{"}}grpcInsecure{{"}}"}}"
}
],
"url": {
"raw": "{{"{{"}}baseURL{{"}}"}}/{{$service.FullName}}/{{$method.Name}}",
"host": [
"{{"{{"}}baseURL{{"}}"}}"
],
"path": [
"{{$service.FullName}}",
"{{$method.Name}}"
]
},
"body": {
"mode": "raw",
{{- range $msg := $messages -}}
{{if eq $msg.FullName $method.RequestFullType }}
"raw": "{\n
{{- $fieldLen := len $msg.Fields -}}
{{- range $fieldIndex, $field := $msg.Fields -}}
{{" \\\""}}{{$field.Name}}\": {{ if eq $field.FullType "string" -}}
{{- "\\\"\\\"" -}}
{{- else if eq $field.FullType "bool" -}}
{{- "true" -}}
{{- else if (has $field.FullType (list "int" "int32" "int64" "double" "float" "uint32" "uint64" "sint32" "sint64" "fixed32" "fixed64" "sfixed32" "sfixed64")) -}}
{{- "0" -}}
{{- else -}}
{{- "{}" -}}
{{- end -}}{{if lt $fieldIndex (sub $fieldLen 1)}},\n{{end}}
{{- end }}\n}"
}
{{- end }}
{{- end }}
}
}{{if lt $methodIndex (sub $methodLen 1)}},{{end}}
{{- end}}
]
}{{if lt $serviceIndex (sub $serviceLen 1)}},{{end}}
]
{{- end}}
}{{if (lt $fileIndex (sub $fileLen 1))}}{{if (first (slice $.Files (add $fileIndex 1) 1)).HasServices}},{{end}}{{end}}
{{- end}}
{{- end}}
]
}
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a "greeting"
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
{
"info": {
"name": "Hello World gRPC JSON API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"description": "## Messages\n\n#### helloworld.HelloReply\n\nThe response message containing the greetings\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| message | string | |\n\n#### helloworld.HelloRequest\n\nThe request message containing the user's name.\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| name | string | |\n\n"
},
"item": [
{
"name": "helloworld.proto",
"description": "",
"item": [
{
"name": "helloworld.Greeter",
"description": "The greeting service definition.",
"items": [
{
"name": "SayHello",
"request": {
"description": "Sends a \"greeting\"\n\n\n\nRequest Message: `helloworld.HelloRequest`\n\nResponse Message: `helloworld.HelloReply`\n",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/grpc+json"
},
{
"key": "Grpc-Insecure",
"value": "{{grpcInsecure}}"
}
],
"url": {
"raw": "{{baseURL}}/helloworld.Greeter/SayHello",
"host": [
"{{baseURL}}"
],
"path": [
"helloworld.Greeter",
"SayHello"
]
},
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"\"\n}"
}
}
}
]
}
]
}
]
}
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
option objc_class_prefix = "RTG";
package routeguide;
// Interface exported by the server.
service RouteGuide {
// A simple RPC.
//
// Obtains the feature at a given position.
//
// A feature with an empty name is returned if there's no feature at the given
// position.
rpc GetFeature(Point) returns (Feature) {}
// A server-to-client streaming RPC.
//
// Obtains the Features available within the given Rectangle. Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
// A client-to-server streaming RPC.
//
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
// A Bidirectional streaming RPC.
//
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
// A latitude-longitude rectangle, represented as two diagonally opposite
// points "lo" and "hi".
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
// A feature names something at a given point.
//
// If a feature could not be named, the name is empty.
message Feature {
// The name of the feature.
string name = 1;
// The point where the feature is detected.
Point location = 2;
}
// A RouteNote is a message sent while at a given point.
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
// A RouteSummary is received in response to a RecordRoute rpc.
//
// It contains the number of individual points received, the number of
// detected features, and the total distance covered as the cumulative sum of
// the distance between each point.
message RouteSummary {
// The number of points received.
int32 point_count = 1;
// The number of known features passed while traversing the route.
int32 feature_count = 2;
// The distance covered in metres.
int32 distance = 3;
// The duration of the traversal in seconds.
int32 elapsed_time = 4;
}
{
"info": {
"name": "Route Guide gRPC JSON API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"description": "## Messages\n\n#### routeguide.Feature\n\nA feature names something at a given point.\n\nIf a feature could not be named, the name is empty.\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| name | string | The name of the feature. |\n| location | routeguide.Point | The point where the feature is detected. |\n\n#### routeguide.Point\n\nPoints are represented as latitude-longitude pairs in the E7 representation\n(degrees multiplied by 10**7 and rounded to the nearest integer).\nLatitudes should be in the range +/- 90 degrees and longitude should be in\nthe range +/- 180 degrees (inclusive).\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| latitude | int32 | |\n| longitude | int32 | |\n\n#### routeguide.Rectangle\n\nA latitude-longitude rectangle, represented as two diagonally opposite\npoints \"lo\" and \"hi\".\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| lo | routeguide.Point | One corner of the rectangle. |\n| hi | routeguide.Point | The other corner of the rectangle. |\n\n#### routeguide.RouteNote\n\nA RouteNote is a message sent while at a given point.\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| location | routeguide.Point | The location from which the message is sent. |\n| message | string | The message to be sent. |\n\n#### routeguide.RouteSummary\n\nA RouteSummary is received in response to a RecordRoute rpc.\n\nIt contains the number of individual points received, the number of\ndetected features, and the total distance covered as the cumulative sum of\nthe distance between each point.\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| point_count | int32 | The number of points received. |\n| feature_count | int32 | The number of known features passed while traversing the route. |\n| distance | int32 | The distance covered in metres. |\n| elapsed_time | int32 | The duration of the traversal in seconds. |\n\n"
},
"item": [
{
"name": "route_guide.proto",
"description": "",
"item": [
{
"name": "routeguide.RouteGuide",
"description": "Interface exported by the server.",
"items": [
{
"name": "GetFeature",
"request": {
"description": "A simple RPC.\n\nObtains the feature at a given position.\n\nA feature with an empty name is returned if there's no feature at the given\nposition.\n\n\n\nRequest Message: `routeguide.Point`\n\nResponse Message: `routeguide.Feature`\n",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/grpc+json"
},
{
"key": "Grpc-Insecure",
"value": "{{grpcInsecure}}"
}
],
"url": {
"raw": "{{baseURL}}/routeguide.RouteGuide/GetFeature",
"host": [
"{{baseURL}}"
],
"path": [
"routeguide.RouteGuide",
"GetFeature"
]
},
"body": {
"mode": "raw",
"raw": "{\n \"latitude\": 0,\n \"longitude\": 0\n}"
}
}
},
{
"name": "ListFeatures",
"request": {
"description": "A server-to-client streaming RPC.\n\nObtains the Features available within the given Rectangle. Results are\nstreamed rather than returned at once (e.g. in a response message with a\nrepeated field), as the rectangle may cover a large area and contain a\nhuge number of features.\n\n\n\nRequest Message: `routeguide.Rectangle`\n\nResponse Message: `routeguide.Feature`\n",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/grpc+json"
},
{
"key": "Grpc-Insecure",
"value": "{{grpcInsecure}}"
}
],
"url": {
"raw": "{{baseURL}}/routeguide.RouteGuide/ListFeatures",
"host": [
"{{baseURL}}"
],
"path": [
"routeguide.RouteGuide",
"ListFeatures"
]
},
"body": {
"mode": "raw",
"raw": "{\n \"lo\": {},\n \"hi\": {}\n}"
}
}
},
{
"name": "RecordRoute",
"request": {
"description": "A client-to-server streaming RPC.\n\nAccepts a stream of Points on a route being traversed, returning a\nRouteSummary when traversal is completed.\n\n\n\nRequest Message: `routeguide.Point`\n\nResponse Message: `routeguide.RouteSummary`\n",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/grpc+json"
},
{
"key": "Grpc-Insecure",
"value": "{{grpcInsecure}}"
}
],
"url": {
"raw": "{{baseURL}}/routeguide.RouteGuide/RecordRoute",
"host": [
"{{baseURL}}"
],
"path": [
"routeguide.RouteGuide",
"RecordRoute"
]
},
"body": {
"mode": "raw",
"raw": "{\n \"latitude\": 0,\n \"longitude\": 0\n}"
}
}
},
{
"name": "RouteChat",
"request": {
"description": "A Bidirectional streaming RPC.\n\nAccepts a stream of RouteNotes sent while a route is being traversed,\nwhile receiving other RouteNotes (e.g. from other users).\n\n\n\nRequest Message: `routeguide.RouteNote`\n\nResponse Message: `routeguide.RouteNote`\n",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/grpc+json"
},
{
"key": "Grpc-Insecure",
"value": "{{grpcInsecure}}"
}
],
"url": {
"raw": "{{baseURL}}/routeguide.RouteGuide/RouteChat",
"host": [
"{{baseURL}}"
],
"path": [
"routeguide.RouteGuide",
"RouteChat"
]
},
"body": {
"mode": "raw",
"raw": "{\n \"location\": {},\n \"message\": \"\"\n}"
}
}
}
]
}
]
}
]
}
@kevinswiber
Copy link
Author

This method relies on the documentation generator for protoc: https://github.com/pseudomuto/protoc-gen-doc

Install with:

go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc

Download the postman.tmpl file locally.

Building the collection for helloworld.proto:

POSTMAN_COLLECTION_NAME="Hello World gRPC JSON API" protoc \
--doc_out=./ \
--doc_opt=./postman.tmpl,helloworld_collection.json \
helloworld.proto

Try importing one of these example collections into Postman. View the documentation and requests to see all populated fields.

Hello World Collection: https://gist.githubusercontent.com/kevinswiber/867699e4efe57ccde83fb510c9de6075/raw/b082f5505b22112d5f989dae2765a065de2e53fd/zz_helloworld_collection.json

Route Guide Collection: https://gist.githubusercontent.com/kevinswiber/867699e4efe57ccde83fb510c9de6075/raw/b082f5505b22112d5f989dae2765a065de2e53fd/zz_route_guide.proto

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment