diff --git a/examples/examplepb/echo_service.pb.go b/examples/examplepb/echo_service.pb.go index fb676c649f4..d191846eaec 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 @@ -47,6 +66,15 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion1 // 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"` @@ -76,6 +104,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) @@ -114,6 +151,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 0573f29f052..4baeddad06c 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 gengo.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 94e1abde9ac..dbc9ece0833 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" @@ -40,7 +46,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 88cb584715a..538376cd6de 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -13,6 +13,8 @@ import ( pbdescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" ) +var swaggerExtrasRegexp = regexp.MustCompile(`(?s)^(.*[^\s])[\s]*[\s]*(.*)$`) + // findServicesMessagesAndEnumerations discovers all messages and enums defined in the RPC methods of the service. func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descriptor.Registry, m messageMap, e enumMap) { for _, svc := range s { @@ -539,20 +541,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")