Skip to content

Commit

Permalink
Swagger: Replaced '<!-- ... -->' for raw JSON with last paragraph 'Sw…
Browse files Browse the repository at this point in the history
…agger: '.

Per discussion in grpc-ecosystem#134.
  • Loading branch information
ivucica committed May 7, 2016
1 parent c605892 commit 6d157cc
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 63 deletions.
16 changes: 4 additions & 12 deletions examples/examplepb/echo_service.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 3 additions & 9 deletions examples/examplepb/echo_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ option go_package = "examplepb";
// Echo Service API consists of a single service which returns
// a message.
//
// <!-- swagger extras start
// {
// Swagger: {
// "info": {
// "title": "Echo Service",
// "version": "1.0",
Expand All @@ -23,21 +22,18 @@ option go_package = "examplepb";
// "description": "More about gRPC-Gateway"
// }
// }
// swagger extras end -->
package gengo.grpc.gateway.examples.examplepb;

import "google/api/annotations.proto";

// SimpleMessage represents a simple message sent to the Echo service.
//
// <!-- swagger extras start
// {
// Swagger: {
// "externalDocs": {
// "url": "http://github.com/gengo/grpc-gateway",
// "description": "Find out more about EchoService"
// }
// }
// swagger extras end -->
message SimpleMessage {
// Id represents the message identifier.
string id = 1;
Expand All @@ -50,14 +46,12 @@ service EchoService {
// The message posted as the id parameter will also be
// returned.
//
// <!-- swagger extras start
// {
// Swagger: {
// "externalDocs": {
// "url": "http://github.com/gengo/grpc-gateway",
// "description": "Find out more about EchoService"
// }
// }
// swagger extras end -->
rpc Echo(SimpleMessage) returns (SimpleMessage) {
option (google.api.http) = {
post: "/v1/example/echo/{id}"
Expand Down
63 changes: 21 additions & 42 deletions protoc-gen-swagger/genswagger/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
pbdescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
)

var swaggerExtrasRegexp = regexp.MustCompile(`(?s)^(.*[^\s])[\s]*<!-- swagger extras start(.*)swagger extras end -->[\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 {
Expand Down Expand Up @@ -541,55 +539,27 @@ 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:
//
// <!-- swagger extras start.*swagger extras end-->
//
// 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.
// 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. (JSON always gets applied directly to the passed
// object.)
// object, never to 'Info'.)
//
// 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.
// To apply additional Swagger properties, one can pass valid JSON
// in the last paragraph of the comment. The last paragraph needs to start
// with the string 'Swagger: '. This JSON gets parsed and applied to
// the passed swaggerObject directly. This lets developers 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")
Expand All @@ -608,11 +578,20 @@ func updateSwaggerDataFromComments(swaggerObject interface{}, comment string) er
usingTitle = true
}

// Parse the JSON and apply it.
// TODO(ivucica): apply extras /after/ applying summary
// and description.
paragraphs := strings.Split(comment, "\n\n")
if len(paragraphs) > 0 && strings.HasPrefix(strings.TrimLeft(paragraphs[len(paragraphs)-1], " "), "Swagger: ") {
if err := json.Unmarshal([]byte(strings.TrimLeft(paragraphs[len(paragraphs)-1], " "))[len("Swagger: "):], swaggerObject); err != nil {
return fmt.Errorf("error: %s, parsing: %s", err.Error(), paragraphs[len(paragraphs)-1])
}
paragraphs = paragraphs[:len(paragraphs)-1]
}

// If there is a summary (or summary-equivalent), use the first
// paragraph as summary, and the rest as description.
if summaryValue.CanSet() {
paragraphs := strings.Split(comment, "\n\n")

summary := strings.TrimSpace(paragraphs[0])
description := strings.TrimSpace(strings.Join(paragraphs[1:], "\n\n"))
if !usingTitle || summary[len(summary)-1] != '.' {
Expand All @@ -632,7 +611,7 @@ func updateSwaggerDataFromComments(swaggerObject interface{}, comment string) er
// There was no summary field on the swaggerObject. Try to apply the
// whole comment into description.
if descriptionValue.CanSet() {
descriptionValue.Set(reflect.ValueOf(comment))
descriptionValue.Set(reflect.ValueOf(strings.Join(paragraphs, "\n\n")))
return nil
}

Expand Down

0 comments on commit 6d157cc

Please sign in to comment.