Repository: grpc-ecosystem/grpc-gateway

Issue:
protoc-gen-openapiv2: support deprecated field option
## 🚀 Feature

When generating an openapiv2 definition, any [protobuf `deprecated` field options](https://developers.google.com/protocol-buffers/docs/proto3#options) are currently ignored. It would be nice if the generator automatically set the [deprecated field on the resulting parameter object](https://swagger.io/specification/#parameter-object) accordingly.

Note that the deprecated field on [_operation_ objects](https://swagger.io/specification/#operation-object) is already supported, though this is handled through a custom annotation instead of the official `deprecated` option. This was already touched on in #2856, but in my opinion it makes sense to support the official option ([`deprecated = true` can be set on `rpc`, `message`, `enum`, and `field`](https://github.com/protocolbuffers/protobuf/issues/1734#issuecomment-234681729)) wherever possible instead of requiring the use of custom annotations.

Discussion:
@johanbrandhorst: Hi, thanks for your issue! I'm surprised we didn't already have an issue for this, I agree that it makes sense to automatically apply this. Furthermore, if both an annotation and a protobuf level option is set, the annotation should take precedence (so that it's always possible to change the output of the openapi generator without changing the protobuf definition).@denisdubovitskiy: @johanbrandhorst i think that this annotation should also add some context value to indicate that the method is deprecated. Having this, we can use an interceptor to collect a metric and notify clients about new implementation. What do you think?@johanbrandhorst: I don't think that should be necessary - with APIv2 it should be possible to tell already through reflection whether a method is deprecated. I would consider this ask outside the scope of the gateway.@ankur-kalita: can you please assign this issue to me ?
@johanbrandhorst: Please just submit a PR if you want to work on this

Solution patch:
diff --git a/WORKSPACE b/WORKSPACE
index bbc260b0..ba28a9b1 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -20,10 +20,10 @@ http_archive(
 
 http_archive(
     name = "googleapis",
-    sha256 = "ed4c21a5f898c7196bcbdbcdb4f5026eae3d3cf399b8fe918531209c8c9afb67",
-    strip_prefix = "googleapis-14eabb106ea15d25e7086bda4b0247b296c9502d",
+    sha256 = "92b5712b8606938c6fe4eaf0a026450e0ae22da2918a452a6cd6d1df8e479b78",
+    strip_prefix = "googleapis-f65ad5f87d253c02e1d854aa48c096acc404215f",
     urls = [
-        "https://github.com/googleapis/googleapis/archive/14eabb106ea15d25e7086bda4b0247b296c9502d.zip",
+        "https://github.com/googleapis/googleapis/archive/f65ad5f87d253c02e1d854aa48c096acc404215f.zip",
     ],
 )
 
@@ -35,10 +35,10 @@ switched_rules_by_language(
 
 http_archive(
     name = "bazel_skylib",
-    sha256 = "9f38886a40548c6e96c106b752f242130ee11aaa068a56ba7e56f4511f33e4f2",
+    sha256 = "d00f1389ee20b60018e92644e0948e16e350a7707219e7a390fb0a99b6ec9262",
     urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.6.1/bazel-skylib-1.6.1.tar.gz",
-        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.6.1/bazel-skylib-1.6.1.tar.gz",
+        "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.0/bazel-skylib-1.7.0.tar.gz",
+        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.0/bazel-skylib-1.7.0.tar.gz",
     ],
 )
 
diff --git a/docs/docs/mapping/customizing_openapi_output.md b/docs/docs/mapping/customizing_openapi_output.md
index 98aaf255..b2bd2d9c 100644
--- a/docs/docs/mapping/customizing_openapi_output.md
+++ b/docs/docs/mapping/customizing_openapi_output.md
@@ -934,4 +934,113 @@ plugins:
       - preserve_rpc_order=true
 ```
 
+### Enable RPC deprecation
+
+With `enable_rpc_deprecation` option you can deprecate openapi method using standard method's option. Allowed values are: `true`, `false`.
+
+For example, if you are using `buf`:
+
+```yaml
+version: v1
+plugins:
+  - name: openapiv2
+    out: .
+    opt:
+      - enable_rpc_deprecation=true
+```
+
+or with `protoc`
+
+```sh
+protoc --openapiv2_out=. --openapiv2_opt=enable_rpc_deprecation=true ./path/to/file.proto
+```
+
+Input example:
+
+```protobuf
+syntax = "proto3";
+
+package helloproto.v1;
+
+import "google/api/annotations.proto";
+
+option go_package = "helloproto/v1;helloproto";
+
+service EchoService {
+  rpc Hello(HelloReq) returns (HelloResp) {
+    option deprecated = true;
+    option (google.api.http) = {get: "/api/hello"};
+  }
+}
+
+message HelloReq {
+  string name = 1;
+}
+
+message HelloResp {
+  string message = 1;
+}
+```
+
+Output:
+
+```yaml
+swagger: "2.0"
+info:
+  title: helloproto/v1/example.proto
+  version: version not set
+tags:
+  - name: EchoService
+consumes:
+  - application/json
+produces:
+  - application/json
+paths:
+  /api/hello:
+    get:
+      operationId: EchoService_Hello
+      responses:
+        "200":
+          description: A successful response.
+          schema:
+            $ref: '#/definitions/v1HelloResp'
+        default:
+          description: An unexpected error response.
+          schema:
+            $ref: '#/definitions/rpcStatus'
+      parameters:
+        - name: name
+          in: query
+          required: false
+          type: string
+      tags:
+        - EchoService
+      deprecated: true
+definitions:
+  protobufAny:
+    type: object
+    properties:
+      '@type':
+        type: string
+    additionalProperties: {}
+  rpcStatus:
+    type: object
+    properties:
+      code:
+        type: integer
+        format: int32
+      message:
+        type: string
+      details:
+        type: array
+        items:
+          type: object
+          $ref: '#/definitions/protobufAny'
+  v1HelloResp:
+    type: object
+    properties:
+      message:
+        type: string
+```
+
 {% endraw %}
diff --git a/go.mod b/go.mod
index fd149a0d..3d3b4bc1 100644
--- a/go.mod
+++ b/go.mod
@@ -8,8 +8,8 @@ require (
 	github.com/rogpeppe/fastuuid v1.2.0
 	golang.org/x/oauth2 v0.20.0
 	golang.org/x/text v0.15.0
-	google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e
+	google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157
 	google.golang.org/grpc v1.64.0
 	google.golang.org/protobuf v1.34.1
 	gopkg.in/yaml.v3 v3.0.1
diff --git a/go.sum b/go.sum
index f3418f46..3bc88196 100644
--- a/go.sum
+++ b/go.sum
@@ -23,10 +23,10 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
 golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=
-google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e/go.mod h1:LweJcLbyVij6rCex8YunD8DYR5VDonap/jYl3ZRxcIU=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
+google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
 google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
 google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
 google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
diff --git a/internal/descriptor/registry.go b/internal/descriptor/registry.go
index 421c0089..684fc5b6 100644
--- a/internal/descriptor/registry.go
+++ b/internal/descriptor/registry.go
@@ -160,6 +160,9 @@ type Registry struct {
 	// preserveRPCOrder, if true, will ensure the order of paths emitted in openapi swagger files mirror
 	// the order of RPC methods found in proto files. If false, emitted paths will be ordered alphabetically.
 	preserveRPCOrder bool
+
+	// enableRpcDeprecation whether to process grpc method's deprecated option
+	enableRpcDeprecation bool
 }
 
 type repeatedFieldSeparator struct {
@@ -875,3 +878,13 @@ func (r *Registry) SetPreserveRPCOrder(preserve bool) {
 func (r *Registry) IsPreserveRPCOrder() bool {
 	return r.preserveRPCOrder
 }
+
+// SetEnableRpcDeprecation sets enableRpcDeprecation
+func (r *Registry) SetEnableRpcDeprecation(enable bool) {
+	r.enableRpcDeprecation = enable
+}
+
+// GetEnableRpcDeprecation returns enableRpcDeprecation
+func (r *Registry) GetEnableRpcDeprecation() bool {
+	return r.enableRpcDeprecation
+}
diff --git a/protoc-gen-grpc-gateway/main.go b/protoc-gen-grpc-gateway/main.go
index 503f15c5..086a4624 100644
--- a/protoc-gen-grpc-gateway/main.go
+++ b/protoc-gen-grpc-gateway/main.go
@@ -54,6 +54,7 @@ func main() {
 		if commit == "unknown" {
 			buildInfo, ok := debug.ReadBuildInfo()
 			if ok {
+				version = buildInfo.Main.Version
 				for _, setting := range buildInfo.Settings {
 					if setting.Key == "vcs.revision" {
 						commit = setting.Value
@@ -62,8 +63,6 @@ func main() {
 						date = setting.Value
 					}
 				}
-				fmt.Printf("commit %v, built at %v\n", commit, date)
-				os.Exit(0)
 			}
 		}
 		fmt.Printf("Version %v, commit %v, built at %v\n", version, commit, date)
diff --git a/protoc-gen-openapiv2/defs.bzl b/protoc-gen-openapiv2/defs.bzl
index 941a29c0..0c09e80f 100644
--- a/protoc-gen-openapiv2/defs.bzl
+++ b/protoc-gen-openapiv2/defs.bzl
@@ -74,7 +74,8 @@ def _run_proto_gen_openapi(
         generate_unbound_methods,
         visibility_restriction_selectors,
         use_allof_for_refs,
-        disable_default_responses):
+        disable_default_responses,
+        enable_rpc_deprecation):
     args = actions.args()
 
     args.add("--plugin", "protoc-gen-openapiv2=%s" % protoc_gen_openapiv2.path)
@@ -148,6 +149,9 @@ def _run_proto_gen_openapi(
     if disable_default_responses:
         args.add("--openapiv2_opt", "disable_default_responses=true")
 
+    if enable_rpc_deprecation:
+        args.add("--openapiv2_opt", "enable_rpc_deprecation=true")
+
     args.add("--openapiv2_opt", "repeated_path_param_separator=%s" % repeated_path_param_separator)
 
     proto_file_infos = _direct_source_infos(proto_info)
@@ -255,6 +259,7 @@ def _proto_gen_openapi_impl(ctx):
                     visibility_restriction_selectors = ctx.attr.visibility_restriction_selectors,
                     use_allof_for_refs = ctx.attr.use_allof_for_refs,
                     disable_default_responses = ctx.attr.disable_default_responses,
+                    enable_rpc_deprecation = ctx.attr.enable_rpc_deprecation,
                 ),
             ),
         ),
@@ -311,9 +316,9 @@ protoc_gen_openapiv2 = rule(
         "openapi_naming_strategy": attr.string(
             default = "",
             mandatory = False,
-            values = ["", "simple", "legacy", "fqn"],
+            values = ["", "simple", "package", "legacy", "fqn"],
             doc = "configures how OpenAPI names are determined." +
-                  " Allowed values are `` (empty), `simple`, `legacy` and `fqn`." +
+                  " Allowed values are `` (empty), `simple`, `package`, `legacy` and `fqn`." +
                   " If unset, either `legacy` or `fqn` are selected, depending" +
                   " on the value of the `fqn_for_openapi_name` setting",
         ),
@@ -410,6 +415,11 @@ protoc_gen_openapiv2 = rule(
                   " if you have to support custom response codes that are" +
                   " not 200.",
         ),
+        "enable_rpc_deprecation": attr.bool(
+            default = False,
+            mandatory = False,
+            doc = "whether to process grpc method's deprecated option.",
+        ),
         "_protoc": attr.label(
             default = "@com_google_protobuf//:protoc",
             executable = True,
diff --git a/protoc-gen-openapiv2/internal/genopenapi/naming.go b/protoc-gen-openapiv2/internal/genopenapi/naming.go
index 338ea2dc..2a9ac069 100644
--- a/protoc-gen-openapiv2/internal/genopenapi/naming.go
+++ b/protoc-gen-openapiv2/internal/genopenapi/naming.go
@@ -2,6 +2,7 @@ package genopenapi
 
 import (
 	"reflect"
+	"regexp"
 	"strings"
 )
 
@@ -17,6 +18,8 @@ func LookupNamingStrategy(strategyName string) func([]string) map[string]string
 		return resolveNamesLegacy
 	case "simple":
 		return resolveNamesSimple
+	case "package":
+		return resolveNamesPackage
 	}
 	return nil
 }
@@ -41,7 +44,7 @@ func resolveNamesFQN(messages []string) map[string]string {
 // E.g., if the fully qualified name is `.a.b.C.D`, and there are other messages with fully
 // qualified names ending in `.D` but not in `.C.D`, it assigns the unique name `bCD`.
 func resolveNamesLegacy(messages []string) map[string]string {
-	return resolveNamesUniqueWithContext(messages, 1, "")
+	return resolveNamesUniqueWithContext(messages, 1, "", false)
 }
 
 // resolveNamesSimple takes the names of all proto messages and generates unique references by using a simple
@@ -52,20 +55,48 @@ func resolveNamesLegacy(messages []string) map[string]string {
 // E.g., if the fully qualified name is `.a.b.C.D`, and there are other messages with
 // fully qualified names ending in `.D` but not in `.C.D`, it assigns the unique name `C.D`.
 func resolveNamesSimple(messages []string) map[string]string {
-	return resolveNamesUniqueWithContext(messages, 0, ".")
+	return resolveNamesUniqueWithContext(messages, 0, ".", false)
 }
 
+// resolveNamesPackage takes the names of all proto messages and generates unique references by
+// starting with the package-scoped name (with nested message types qualified by their containing
+// "parent" types), and then following the "simple" heuristic above to add package name components
+// until each message has a unique name with a "." between each component.
+//
+// E.g., if the fully qualified name is `.a.b.C.D`, the name is `C.D` unless there is another
+// package-scoped name ending in "C.D", in  which case it would be `b.C.D` (unless that also
+// conflicted, in which case the name would be the fully-qualified `a.b.C`).
+func resolveNamesPackage(messages []string) map[string]string {
+	return resolveNamesUniqueWithContext(messages, 0, ".", true)
+}
+
+// For the "package" naming strategy, we rely on the convention that package names are lowercase
+// but message names are capitalized.
+var pkgEndRegexp = regexp.MustCompile(`\.[A-Z]`)
+
 // Take the names of every proto message and generates a unique reference by:
 // first, separating each message name into its components by splitting at dots. Then,
 // take the shortest suffix slice from each components slice that is unique among all
 // messages, and convert it into a component name by taking extraContext additional
 // components into consideration and joining all components with componentSeparator.
-func resolveNamesUniqueWithContext(messages []string, extraContext int, componentSeparator string) map[string]string {
+func resolveNamesUniqueWithContext(messages []string, extraContext int, componentSeparator string, qualifyNestedMessages bool) map[string]string {
 	packagesByDepth := make(map[int][][]string)
 	uniqueNames := make(map[string]string)
 
 	hierarchy := func(pkg string) []string {
-		return strings.Split(pkg, ".")
+		if !qualifyNestedMessages {
+			return strings.Split(pkg, ".")
+		}
+		pkgEnd := pkgEndRegexp.FindStringIndex(pkg)
+		if pkgEnd == nil {
+			// Fall back to non-qualified behavior if search based on convention fails.
+			return strings.Split(pkg, ".")
+		}
+		// Return each package component as an element, followed by the full message name
+		// (potentially qualified, if nested) as a single element.
+		qualifiedPkgName := pkg[:pkgEnd[0]]
+		nestedTypeName := pkg[pkgEnd[0]+1:]
+		return append(strings.Split(qualifiedPkgName, "."), nestedTypeName)
 	}
 
 	for _, p := range messages {
diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go
index cc223c19..4a018fa3 100644
--- a/protoc-gen-openapiv2/internal/genopenapi/template.go
+++ b/protoc-gen-openapiv2/internal/genopenapi/template.go
@@ -1163,6 +1163,8 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r
 				continue
 			}
 
+			deprecated := reg.GetEnableRpcDeprecation() && meth.GetOptions().GetDeprecated()
+
 			for bIdx, b := range meth.Bindings {
 				operationFunc := operationForMethod(b.HTTPMethod)
 				// Iterate over all the OpenAPI parameters
@@ -1508,6 +1510,7 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r
 				operationObject := &openapiOperationObject{
 					Parameters: parameters,
 					Responses:  openapiResponsesObject{},
+					Deprecated: deprecated,
 				}
 
 				if !reg.GetDisableDefaultResponses() {
@@ -1566,14 +1569,17 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r
 					grpclog.Error(err)
 					return err
 				}
+
 				opts, err := getMethodOpenAPIOption(reg, meth)
 				if opts != nil {
 					if err != nil {
 						panic(err)
 					}
 					operationObject.ExternalDocs = protoExternalDocumentationToOpenAPIExternalDocumentation(opts.ExternalDocs, reg, meth)
-					// TODO(ivucica): this would be better supported by looking whether the method is deprecated in the proto file
-					operationObject.Deprecated = opts.Deprecated
+
+					if opts.Deprecated {
+						operationObject.Deprecated = true
+					}
 
 					if opts.Summary != "" {
 						operationObject.Summary = opts.Summary
diff --git a/protoc-gen-openapiv2/main.go b/protoc-gen-openapiv2/main.go
index 958b77e8..58d19f17 100644
--- a/protoc-gen-openapiv2/main.go
+++ b/protoc-gen-openapiv2/main.go
@@ -29,7 +29,7 @@ var (
 	_                              = flag.Bool("allow_repeated_fields_in_body", true, "allows to use repeated field in `body` and `response_body` field of `google.api.http` annotation option. DEPRECATED: the value is ignored and always behaves as `true`.")
 	includePackageInTags           = flag.Bool("include_package_in_tags", false, "if unset, the gRPC service name is added to the `Tags` field of each operation. If set and the `package` directive is shown in the proto file, the package name will be prepended to the service name")
 	useFQNForOpenAPIName           = flag.Bool("fqn_for_openapi_name", false, "if set, the object's OpenAPI names will use the fully qualified names from the proto definition (ie my.package.MyMessage.MyInnerMessage). DEPRECATED: prefer `openapi_naming_strategy=fqn`")
-	openAPINamingStrategy          = flag.String("openapi_naming_strategy", "", "use the given OpenAPI naming strategy. Allowed values are `legacy`, `fqn`, `simple`. If unset, either `legacy` or `fqn` are selected, depending on the value of the `fqn_for_openapi_name` flag")
+	openAPINamingStrategy          = flag.String("openapi_naming_strategy", "", "use the given OpenAPI naming strategy. Allowed values are `legacy`, `fqn`, `simple`, `package`. If unset, either `legacy` or `fqn` are selected, depending on the value of the `fqn_for_openapi_name` flag")
 	useGoTemplate                  = flag.Bool("use_go_templates", false, "if set, you can use Go templates in protofile comments")
 	goTemplateArgs                 = utilities.StringArrayFlag(flag.CommandLine, "go_template_args", "provide a custom value that can override a key in the Go template. Requires the `use_go_templates` option to be set")
 	ignoreComments                 = flag.Bool("ignore_comments", false, "if set, all protofile comments are excluded from output")
@@ -49,6 +49,7 @@ var (
 	useAllOfForRefs                = flag.Bool("use_allof_for_refs", false, "if set, will use allOf as container for $ref to preserve same-level properties.")
 	allowPatchFeature              = flag.Bool("allow_patch_feature", true, "whether to hide update_mask fields in PATCH requests from the generated swagger file.")
 	preserveRPCOrder               = flag.Bool("preserve_rpc_order", false, "if true, will ensure the order of paths emitted in openapi swagger files mirror the order of RPC methods found in proto files. If false, emitted paths will be ordered alphabetically.")
+	enableRpcDeprecation           = flag.Bool("enable_rpc_deprecation", false, "whether to process grpc method's deprecated option.")
 
 	_ = flag.Bool("logtostderr", false, "Legacy glog compatibility. This flag is a no-op, you can safely remove it")
 )
@@ -67,6 +68,7 @@ func main() {
 		if commit == "unknown" {
 			buildInfo, ok := debug.ReadBuildInfo()
 			if ok {
+				version = buildInfo.Main.Version
 				for _, setting := range buildInfo.Settings {
 					if setting.Key == "vcs.revision" {
 						commit = setting.Value
@@ -75,8 +77,6 @@ func main() {
 						date = setting.Value
 					}
 				}
-				fmt.Printf("commit %v, built at %v\n", commit, date)
-				os.Exit(0)
 			}
 		}
 		fmt.Printf("Version %v, commit %v, built at %v\n", version, commit, date)
@@ -172,6 +172,8 @@ func main() {
 	reg.SetUseAllOfForRefs(*useAllOfForRefs)
 	reg.SetAllowPatchFeature(*allowPatchFeature)
 	reg.SetPreserveRPCOrder(*preserveRPCOrder)
+	reg.SetEnableRpcDeprecation(*enableRpcDeprecation)
+
 	if err := reg.SetRepeatedPathParamSeparator(*repeatedPathParamSeparator); err != nil {
 		emitError(err)
 		return
diff --git a/repositories.bzl b/repositories.bzl
index a6ddca32..3fe7e0c6 100644
--- a/repositories.bzl
+++ b/repositories.bzl
@@ -1787,14 +1787,14 @@ def go_repositories():
     go_repository(
         name = "org_golang_google_genproto_googleapis_api",
         importpath = "google.golang.org/genproto/googleapis/api",
-        sum = "h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=",
-        version = "v0.0.0-20240521202816-d264139d666e",
+        sum = "h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=",
+        version = "v0.0.0-20240528184218-531527333157",
     )
     go_repository(
         name = "org_golang_google_genproto_googleapis_rpc",
         importpath = "google.golang.org/genproto/googleapis/rpc",
-        sum = "h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8=",
-        version = "v0.0.0-20240521202816-d264139d666e",
+        sum = "h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=",
+        version = "v0.0.0-20240528184218-531527333157",
     )
     go_repository(
         name = "org_golang_google_grpc",



Test patch:
diff --git a/protoc-gen-openapiv2/internal/genopenapi/naming_test.go b/protoc-gen-openapiv2/internal/genopenapi/naming_test.go
index c039510f..22fa5ae9 100644
--- a/protoc-gen-openapiv2/internal/genopenapi/naming_test.go
+++ b/protoc-gen-openapiv2/internal/genopenapi/naming_test.go
@@ -4,15 +4,15 @@ import "testing"
 
 func TestNaming(t *testing.T) {
 	type expectedNames struct {
-		fqn, legacy, simple string
+		fqn, legacy, simple, pkg string
 	}
 	messageNameToExpected := map[string]expectedNames{
-		".A":     {"A", "A", "A"},
-		".a.B.C": {"a.B.C", "aBC", "B.C"},
-		".a.D.C": {"a.D.C", "aDC", "D.C"},
-		".a.E.F": {"a.E.F", "aEF", "a.E.F"},
-		".b.E.F": {"b.E.F", "bEF", "b.E.F"},
-		".c.G.H": {"c.G.H", "GH", "H"},
+		".A":     {"A", "A", "A", "A"},
+		".a.B.C": {"a.B.C", "aBC", "B.C", "B.C"},
+		".a.D.C": {"a.D.C", "aDC", "D.C", "D.C"},
+		".a.E.F": {"a.E.F", "aEF", "a.E.F", "a.E.F"},
+		".b.E.F": {"b.E.F", "bEF", "b.E.F", "b.E.F"},
+		".c.G.H": {"c.G.H", "GH", "H", "G.H"},
 	}
 
 	allMessageNames := make([]string, 0, len(messageNameToExpected))
@@ -50,4 +50,14 @@ func TestNaming(t *testing.T) {
 			}
 		}
 	})
+	t.Run("package", func(t *testing.T) {
+		uniqueNames := resolveNamesPackage(allMessageNames)
+		for _, msgName := range allMessageNames {
+			expected := messageNameToExpected[msgName].pkg
+			actual := uniqueNames[msgName]
+			if expected != actual {
+				t.Errorf("package unique name %q does not match expected name %q", actual, expected)
+			}
+		}
+	})
 }
diff --git a/protoc-gen-openapiv2/internal/genopenapi/template_test.go b/protoc-gen-openapiv2/internal/genopenapi/template_test.go
index 3046e7aa..24faac30 100644
--- a/protoc-gen-openapiv2/internal/genopenapi/template_test.go
+++ b/protoc-gen-openapiv2/internal/genopenapi/template_test.go
@@ -10789,3 +10789,127 @@ func TestApiVisibilityOption(t *testing.T) {
 		t.Fatal("Definition should be excluded by api visibility option")
 	}
 }
+
+func TestRenderServicesOptionDeprecated(t *testing.T) {
+	testCases := [...]struct {
+		testName           string
+		methodOptions      descriptorpb.MethodOptions
+		openapiOperation   *openapi_options.Operation
+		expectedDeprecated bool
+	}{
+		{
+			testName: "method option",
+			methodOptions: descriptorpb.MethodOptions{
+				Deprecated: proto.Bool(true),
+			},
+			expectedDeprecated: true,
+		},
+		{
+			testName: "openapi option",
+			openapiOperation: &openapi_options.Operation{
+				Deprecated: true,
+			},
+			expectedDeprecated: true,
+		},
+		{
+			testName: "empty openapi doesn't override method option",
+			methodOptions: descriptorpb.MethodOptions{
+				Deprecated: proto.Bool(true),
+			},
+			openapiOperation: &openapi_options.Operation{},
+			expectedDeprecated: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+
+		t.Run(tc.testName, func(t *testing.T) {
+			msgdesc := &descriptorpb.DescriptorProto{
+				Name: proto.String("ExampleMessage"),
+			}
+
+			meth := &descriptorpb.MethodDescriptorProto{
+				Name:       proto.String("Example"),
+				InputType:  proto.String("ExampleMessage"),
+				OutputType: proto.String("ExampleMessage"),
+				Options:    &tc.methodOptions,
+			}
+
+			svc := &descriptorpb.ServiceDescriptorProto{
+				Name:   proto.String("ExampleService"),
+				Method: []*descriptorpb.MethodDescriptorProto{meth},
+			}
+
+			msg := &descriptor.Message{
+				DescriptorProto: msgdesc,
+			}
+
+			file := descriptor.File{
+				FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+					SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+					Name:           proto.String("example.proto"),
+					Package:        proto.String("example"),
+					MessageType:    []*descriptorpb.DescriptorProto{msgdesc},
+					Service:        []*descriptorpb.ServiceDescriptorProto{svc},
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
+					},
+				},
+				GoPkg: descriptor.GoPackage{
+					Path: "example.com/path/to/example/example.pb",
+					Name: "example_pb",
+				},
+				Messages: []*descriptor.Message{msg},
+				Services: []*descriptor.Service{
+					{
+						ServiceDescriptorProto: svc,
+						Methods: []*descriptor.Method{
+							{
+								MethodDescriptorProto: meth,
+								RequestType:           msg,
+								ResponseType:          msg,
+								Bindings: []*descriptor.Binding{
+									{
+										HTTPMethod: "GET",
+										PathTmpl: httprule.Template{
+											Version:  1,
+											OpCodes:  []int{0, 0},
+											Template: "/v1/echo",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			}
+
+			if tc.openapiOperation != nil {
+				proto.SetExtension(
+					proto.Message(file.Services[0].Methods[0].Options),
+					openapi_options.E_Openapiv2Operation,
+					tc.openapiOperation,
+				)
+			}
+
+			reg := descriptor.NewRegistry()
+			reg.SetEnableRpcDeprecation(true)
+			fileCL := crossLinkFixture(&file)
+
+			if err := reg.Load(reqFromFile(fileCL)); err != nil {
+				t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
+			}
+
+			result, err := applyTemplate(param{File: fileCL, reg: reg})
+			if err != nil {
+				t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
+			}
+
+			got := result.getPathItemObject("/v1/echo").Get.Deprecated
+			if got != tc.expectedDeprecated {
+				t.Fatalf("Wrong deprecated field, got %v want %v", got, tc.expectedDeprecated)
+			}
+		})
+	}
+}