diff --git a/examples/internal/clients/abe/api/swagger.yaml b/examples/internal/clients/abe/api/swagger.yaml index 4b13f6e3ec8..4fb27195667 100644 --- a/examples/internal/clients/abe/api/swagger.yaml +++ b/examples/internal/clients/abe/api/swagger.yaml @@ -11,6 +11,15 @@ info: name: "BSD 3-Clause License" url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt" x-something-something: "yadda" +tags: +- name: "ABitOfEverythingService" + description: "ABitOfEverythingService description -- which should not be used in\ + \ place of the documentation comment!" + externalDocs: + description: "Find out more about EchoService" + url: "https://github.com/grpc-ecosystem/grpc-gateway" +- name: "camelCaseServiceName" +- name: "AnotherServiceWithNoBindings" schemes: - "http" - "https" diff --git a/examples/internal/clients/echo/api/swagger.yaml b/examples/internal/clients/echo/api/swagger.yaml index cbda5f0f24b..5e6fe7beead 100644 --- a/examples/internal/clients/echo/api/swagger.yaml +++ b/examples/internal/clients/echo/api/swagger.yaml @@ -4,6 +4,8 @@ info: description: "Echo Service API consists of a single service which returns\na message." version: "version not set" title: "Echo Service" +tags: +- name: "EchoService" consumes: - "application/json" produces: diff --git a/examples/internal/clients/generateunboundmethods/api/swagger.yaml b/examples/internal/clients/generateunboundmethods/api/swagger.yaml index 1bc4221a97f..c64662ab118 100644 --- a/examples/internal/clients/generateunboundmethods/api/swagger.yaml +++ b/examples/internal/clients/generateunboundmethods/api/swagger.yaml @@ -6,6 +6,8 @@ info: \ Methods Echo Service API consists of a single service which returns\na message." version: "version not set" title: "examples/internal/proto/examplepb/generate_unbound_methods.proto" +tags: +- name: "GenerateUnboundMethodsEchoService" consumes: - "application/json" produces: diff --git a/examples/internal/clients/responsebody/api/swagger.yaml b/examples/internal/clients/responsebody/api/swagger.yaml index 187a8ba5c1c..b1dd9042485 100644 --- a/examples/internal/clients/responsebody/api/swagger.yaml +++ b/examples/internal/clients/responsebody/api/swagger.yaml @@ -3,6 +3,8 @@ swagger: "2.0" info: version: "version not set" title: "examples/internal/proto/examplepb/response_body_service.proto" +tags: +- name: "ResponseBodyService" consumes: - "application/json" produces: diff --git a/examples/internal/clients/unannotatedecho/api/swagger.yaml b/examples/internal/clients/unannotatedecho/api/swagger.yaml index 17bbc115235..efbd60c6f3b 100644 --- a/examples/internal/clients/unannotatedecho/api/swagger.yaml +++ b/examples/internal/clients/unannotatedecho/api/swagger.yaml @@ -15,6 +15,8 @@ info: name: "BSD 3-Clause License" url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt" x-something-something: "yadda" +tags: +- name: "UnannotatedEchoService" schemes: - "http" - "https" diff --git a/examples/internal/proto/examplepb/a_bit_of_everything.swagger.json b/examples/internal/proto/examplepb/a_bit_of_everything.swagger.json index 0c4845e849a..abc379edaa0 100644 --- a/examples/internal/proto/examplepb/a_bit_of_everything.swagger.json +++ b/examples/internal/proto/examplepb/a_bit_of_everything.swagger.json @@ -14,6 +14,22 @@ }, "x-something-something": "yadda" }, + "tags": [ + { + "name": "ABitOfEverythingService", + "description": "ABitOfEverythingService description -- which should not be used in place of the documentation comment!", + "externalDocs": { + "description": "Find out more about EchoService", + "url": "https://github.com/grpc-ecosystem/grpc-gateway" + } + }, + { + "name": "camelCaseServiceName" + }, + { + "name": "AnotherServiceWithNoBindings" + } + ], "schemes": [ "http", "https", diff --git a/examples/internal/proto/examplepb/echo_service.swagger.json b/examples/internal/proto/examplepb/echo_service.swagger.json index 80deb2a9a29..e9f9bc7a501 100644 --- a/examples/internal/proto/examplepb/echo_service.swagger.json +++ b/examples/internal/proto/examplepb/echo_service.swagger.json @@ -5,6 +5,11 @@ "description": "Echo Service API consists of a single service which returns\na message.", "version": "version not set" }, + "tags": [ + { + "name": "EchoService" + } + ], "consumes": [ "application/json" ], diff --git a/examples/internal/proto/examplepb/generate_unbound_methods.swagger.json b/examples/internal/proto/examplepb/generate_unbound_methods.swagger.json index 4dd494af6bd..4adb8a74a2a 100644 --- a/examples/internal/proto/examplepb/generate_unbound_methods.swagger.json +++ b/examples/internal/proto/examplepb/generate_unbound_methods.swagger.json @@ -5,6 +5,11 @@ "description": "Generate Unannotated Methods Echo Service\nSimilar to echo_service.proto but without annotations and without external configuration.\n\nGenerate Unannotated Methods Echo Service API consists of a single service which returns\na message.", "version": "version not set" }, + "tags": [ + { + "name": "GenerateUnboundMethodsEchoService" + } + ], "consumes": [ "application/json" ], diff --git a/examples/internal/proto/examplepb/openapi_merge.swagger.json b/examples/internal/proto/examplepb/openapi_merge.swagger.json index abb0ce7e5ba..9b504281867 100644 --- a/examples/internal/proto/examplepb/openapi_merge.swagger.json +++ b/examples/internal/proto/examplepb/openapi_merge.swagger.json @@ -5,6 +5,17 @@ "description": "This is an example of merging two proto files.", "version": "version not set" }, + "tags": [ + { + "name": "ServiceA" + }, + { + "name": "ServiceC" + }, + { + "name": "ServiceB" + } + ], "consumes": [ "application/json" ], diff --git a/examples/internal/proto/examplepb/response_body_service.swagger.json b/examples/internal/proto/examplepb/response_body_service.swagger.json index 60c3c7b5e16..76adbe8d61a 100644 --- a/examples/internal/proto/examplepb/response_body_service.swagger.json +++ b/examples/internal/proto/examplepb/response_body_service.swagger.json @@ -4,6 +4,11 @@ "title": "examples/internal/proto/examplepb/response_body_service.proto", "version": "version not set" }, + "tags": [ + { + "name": "ResponseBodyService" + } + ], "consumes": [ "application/json" ], diff --git a/examples/internal/proto/examplepb/stream.swagger.json b/examples/internal/proto/examplepb/stream.swagger.json index 1d5d518e195..95e5f6adbff 100644 --- a/examples/internal/proto/examplepb/stream.swagger.json +++ b/examples/internal/proto/examplepb/stream.swagger.json @@ -4,6 +4,11 @@ "title": "examples/internal/proto/examplepb/stream.proto", "version": "version not set" }, + "tags": [ + { + "name": "StreamService" + } + ], "consumes": [ "application/json" ], diff --git a/examples/internal/proto/examplepb/unannotated_echo_service.swagger.json b/examples/internal/proto/examplepb/unannotated_echo_service.swagger.json index cf92646c793..727dfc149ce 100644 --- a/examples/internal/proto/examplepb/unannotated_echo_service.swagger.json +++ b/examples/internal/proto/examplepb/unannotated_echo_service.swagger.json @@ -15,6 +15,11 @@ }, "x-something-something": "yadda" }, + "tags": [ + { + "name": "UnannotatedEchoService" + } + ], "schemes": [ "http", "https", diff --git a/examples/internal/proto/examplepb/use_go_template.swagger.json b/examples/internal/proto/examplepb/use_go_template.swagger.json index 0cb788b68fc..21b322bab2e 100644 --- a/examples/internal/proto/examplepb/use_go_template.swagger.json +++ b/examples/internal/proto/examplepb/use_go_template.swagger.json @@ -4,6 +4,11 @@ "title": "examples/internal/proto/examplepb/use_go_template.proto", "version": "version not set" }, + "tags": [ + { + "name": "LoginService" + } + ], "consumes": [ "application/json" ], diff --git a/examples/internal/proto/examplepb/wrappers.swagger.json b/examples/internal/proto/examplepb/wrappers.swagger.json index 153034cd21c..adf850c9c38 100644 --- a/examples/internal/proto/examplepb/wrappers.swagger.json +++ b/examples/internal/proto/examplepb/wrappers.swagger.json @@ -4,6 +4,11 @@ "title": "examples/internal/proto/examplepb/wrappers.proto", "version": "version not set" }, + "tags": [ + { + "name": "WrappersService" + } + ], "consumes": [ "application/json" ], diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go index ed70ac898a0..1c0793917b8 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template.go @@ -782,6 +782,33 @@ func isResourceName(prefix string) bool { return field == "parent" || field == "name" } +func renderServiceTags(services []*descriptor.Service) []openapiTagObject { + var tags []openapiTagObject + for _, svc := range services { + tag := openapiTagObject{ + Name: *svc.Name, + } + if proto.HasExtension(svc.Options, openapi_options.E_Openapiv2Tag) { + ext := proto.GetExtension(svc.Options, openapi_options.E_Openapiv2Tag) + opts, ok := ext.(*openapi_options.Tag) + if !ok { + glog.Errorf("extension is %T; want an OpenAPI Tag object", ext) + return nil + } + + tag.Description = opts.Description + if opts.ExternalDocs != nil { + tag.ExternalDocs = &openapiExternalDocumentationObject{ + Description: opts.ExternalDocs.Description, + URL: opts.ExternalDocs.Url, + } + } + } + tags = append(tags, tag) + } + return tags +} + func renderServices(services []*descriptor.Service, paths openapiPathsObject, reg *descriptor.Registry, requestResponseRefs, customRefs refMap, msgs []*descriptor.Message) error { // Correctness of svcIdx and methIdx depends on 'services' containing the services in the same order as the 'file.Service' array. svcBaseIdx := 0 @@ -1199,6 +1226,7 @@ func applyTemplate(p param) (*openapiSwaggerObject, error) { if err := renderServices(p.Services, s.Paths, p.reg, requestResponseRefs, customRefs, p.Messages); err != nil { panic(err) } + s.Tags = append(s.Tags, renderServiceTags(p.Services)...) messages := messageMap{} streamingMessages := messageMap{} diff --git a/protoc-gen-openapiv2/internal/genopenapi/types.go b/protoc-gen-openapiv2/internal/genopenapi/types.go index 8b5d6a59588..505cfc686e8 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/types.go +++ b/protoc-gen-openapiv2/internal/genopenapi/types.go @@ -26,6 +26,13 @@ type openapiInfoObject struct { extensions []extension } +// https://swagger.io/specification/#tagObject +type openapiTagObject struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + ExternalDocs *openapiExternalDocumentationObject `json:"externalDocs,omitempty"` +} + // http://swagger.io/specification/#contactObject type openapiContactObject struct { Name string `json:"name,omitempty"` @@ -54,6 +61,7 @@ type extension struct { type openapiSwaggerObject struct { Swagger string `json:"swagger"` Info openapiInfoObject `json:"info"` + Tags []openapiTagObject `json:"tags,omitempty"` Host string `json:"host,omitempty"` BasePath string `json:"basePath,omitempty"` Schemes []string `json:"schemes,omitempty"`