From a9c77a373a58cbb4761f46ee953ec9bf6ac5a741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Vu=C4=8Dica?= Date: Sat, 7 May 2016 23:14:41 +0100 Subject: [PATCH] Apply additional Swagger properties using JSON. Depends on PR #134. Reverts 562956fa74bfa71588576449ff0e3fa9c0b49ca0. --- examples/examplepb/echo_service.pb.go | 46 ++++++++++++++++++++ examples/examplepb/echo_service.proto | 37 ++++++++++++++++ examples/examplepb/echo_service.swagger.json | 14 +++++- protoc-gen-swagger/genswagger/template.go | 39 ++++++++++++++++- 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/examples/examplepb/echo_service.pb.go b/examples/examplepb/echo_service.pb.go index a7e19fdd932..c181ae2f587 100644 --- a/examples/examplepb/echo_service.pb.go +++ b/examples/examplepb/echo_service.pb.go @@ -10,6 +10,25 @@ Echo Service Echo Service API consists of a single service which returns a message. + + It is generated from these files: examples/examplepb/echo_service.proto examples/examplepb/a_bit_of_everything.proto @@ -49,6 +68,15 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package // SimpleMessage represents a simple message sent to the Echo service. +// +// type SimpleMessage struct { // Id represents the message identifier. Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` @@ -85,6 +113,15 @@ type EchoServiceClient interface { // // The message posted as the id parameter will also be // returned. + // + // Echo(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error) // EchoBody method receives a simple message and returns it. EchoBody(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error) @@ -123,6 +160,15 @@ type EchoServiceServer interface { // // The message posted as the id parameter will also be // returned. + // + // Echo(context.Context, *SimpleMessage) (*SimpleMessage, error) // EchoBody method receives a simple message and returns it. EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error) diff --git a/examples/examplepb/echo_service.proto b/examples/examplepb/echo_service.proto index 44555e8553f..85d34a3a273 100644 --- a/examples/examplepb/echo_service.proto +++ b/examples/examplepb/echo_service.proto @@ -5,11 +5,39 @@ option go_package = "examplepb"; // // Echo Service API consists of a single service which returns // a message. +// +// package grpc.gateway.examples.examplepb; import "google/api/annotations.proto"; // SimpleMessage represents a simple message sent to the Echo service. +// +// message SimpleMessage { // Id represents the message identifier. string id = 1; @@ -21,6 +49,15 @@ service EchoService { // // The message posted as the id parameter will also be // returned. + // + // rpc Echo(SimpleMessage) returns (SimpleMessage) { option (google.api.http) = { post: "/v1/example/echo/{id}" diff --git a/examples/examplepb/echo_service.swagger.json b/examples/examplepb/echo_service.swagger.json index 9380472a3f9..a727824e8ae 100644 --- a/examples/examplepb/echo_service.swagger.json +++ b/examples/examplepb/echo_service.swagger.json @@ -3,8 +3,14 @@ "info": { "title": "Echo Service", "description": "Echo Service API consists of a single service which returns\na message.", - "version": "version not set" + "version": "1.0", + "contact": { + "name": "gRPC-Gateway project", + "url": "https://github.com/gengo/grpc-gateway", + "email": "none@example.com" + } }, + "host": "localhost", "schemes": [ "http", "https" @@ -39,7 +45,11 @@ ], "tags": [ "EchoService" - ] + ], + "externalDocs": { + "description": "Find out more about EchoService", + "url": "http://github.com/gengo/grpc-gateway" + } } }, "/v1/example/echo_body": { diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index 022b649fb9b..9c9183ac183 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -13,6 +13,8 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor" ) +var swaggerExtrasRegexp = regexp.MustCompile(`(?s)^(.*[^\s])[\s]*[\s]*(.*)$`) + func listEnumNames(enum *descriptor.Enum) (names []string) { for _, value := range enum.GetValue() { names = append(names, value.GetName()) @@ -606,20 +608,55 @@ func applyTemplate(p param) (string, error) { // updateSwaggerDataFromComments updates a Swagger object based on a comment // from the proto file. // +// As a first step, a section matching: +// +// +// +// where .* contains valid JSON will be stored for later processing, and then +// removed from the passed string. +// (Implementation note: Currently, the JSON gets immediately applied and +// thus cannot override summary and description.) +// // First paragraph of a comment is used for summary. Remaining paragraphs of a // comment are used for description. If 'Summary' field is not present on the // passed swaggerObject, the summary and description are joined by \n\n. // // If there is a field named 'Info', its 'Summary' and 'Description' fields -// will be updated instead. +// will be updated instead. (JSON always gets applied directly to the passed +// object.) // // If there is no 'Summary', the same behavior will be attempted on 'Title', // but only if the last character is not a period. +// +// To apply additional Swagger properties, one can pass valid JSON as described +// before. This JSON gets parsed and applied to the passed swaggerObject +// directly. This lets users easily apply custom properties such as contact +// details, API base path, et al. func updateSwaggerDataFromComments(swaggerObject interface{}, comment string) error { if len(comment) == 0 { return nil } + // Find a section containing additional Swagger metadata. + matches := swaggerExtrasRegexp.FindStringSubmatch(comment) + + if len(matches) > 0 { + // If found, before further processing, replace the + // comment with a version that does not contain the + // extras. + comment = matches[1] + if len(matches[3]) > 0 { + comment += "\n\n" + matches[3] + } + + // Parse the JSON and apply it. + // TODO(ivucica): apply extras /after/ applying summary + // and description. + if err := json.Unmarshal([]byte(matches[2]), swaggerObject); err != nil { + return fmt.Errorf("error: %s, parsing: %s", err.Error(), matches[2]) + } + } + // Figure out what to apply changes to. swaggerObjectValue := reflect.ValueOf(swaggerObject) infoObjectValue := swaggerObjectValue.Elem().FieldByName("Info")