dcsunny 3 سال پیش
کامیت
bc2a853b13
75فایلهای تغییر یافته به همراه16474 افزوده شده و 0 حذف شده
  1. 21 0
      .gitignore
  2. 21 0
      LICENSE
  3. 36 0
      README.md
  4. 18 0
      go.mod
  5. 851 0
      go.sum
  6. 74 0
      openapiv2/handler.go
  7. 15 0
      openapiv2/options.go
  8. 63 0
      openapiv2/service.go
  9. BIN
      openapiv2/swagger_ui/dist/favicon-16x16.png
  10. BIN
      openapiv2/swagger_ui/dist/favicon-32x32.png
  11. 72 0
      openapiv2/swagger_ui/dist/index.html
  12. 75 0
      openapiv2/swagger_ui/dist/oauth2-redirect.html
  13. 1 0
      openapiv2/swagger_ui/dist/swagger-ui-bundle.js
  14. 0 0
      openapiv2/swagger_ui/dist/swagger-ui-bundle.js.map
  15. 1 0
      openapiv2/swagger_ui/dist/swagger-ui-es-bundle-core.js
  16. 0 0
      openapiv2/swagger_ui/dist/swagger-ui-es-bundle-core.js.map
  17. 1 0
      openapiv2/swagger_ui/dist/swagger-ui-es-bundle.js
  18. 0 0
      openapiv2/swagger_ui/dist/swagger-ui-es-bundle.js.map
  19. 1 0
      openapiv2/swagger_ui/dist/swagger-ui-standalone-preset.js
  20. 0 0
      openapiv2/swagger_ui/dist/swagger-ui-standalone-preset.js.map
  21. 1 0
      openapiv2/swagger_ui/dist/swagger-ui.css
  22. 0 0
      openapiv2/swagger_ui/dist/swagger-ui.css.map
  23. 1 0
      openapiv2/swagger_ui/dist/swagger-ui.js
  24. 0 0
      openapiv2/swagger_ui/dist/swagger-ui.js.map
  25. 3 0
      openapiv2/swagger_ui/generate.go
  26. 9 0
      openapiv2/swagger_ui/statik/statik.go
  27. 74 0
      protoc-gen-openapiv2/generator/generator.go
  28. 56 0
      protoc-gen-openapiv2/generator/generator_test.go
  29. 38 0
      protoc-gen-openapiv2/generator/option.go
  30. BIN
      protoc-gen-openapiv2/generator/req.bin
  31. 14 0
      protoc-gen-openapiv2/internal/casing/BUILD.bazel
  32. 28 0
      protoc-gen-openapiv2/internal/casing/LICENSE.md
  33. 5 0
      protoc-gen-openapiv2/internal/casing/README.md
  34. 63 0
      protoc-gen-openapiv2/internal/casing/camel.go
  35. 36 0
      protoc-gen-openapiv2/internal/codegenerator/BUILD.bazel
  36. 4 0
      protoc-gen-openapiv2/internal/codegenerator/doc.go
  37. 23 0
      protoc-gen-openapiv2/internal/codegenerator/parse_req.go
  38. 70 0
      protoc-gen-openapiv2/internal/codegenerator/parse_req_test.go
  39. 24 0
      protoc-gen-openapiv2/internal/codegenerator/supported_features.go
  40. 60 0
      protoc-gen-openapiv2/internal/descriptor/BUILD.bazel
  41. 35 0
      protoc-gen-openapiv2/internal/descriptor/apiconfig/BUILD.bazel
  42. 165 0
      protoc-gen-openapiv2/internal/descriptor/apiconfig/apiconfig.pb.go
  43. 21 0
      protoc-gen-openapiv2/internal/descriptor/apiconfig/apiconfig.proto
  44. 46 0
      protoc-gen-openapiv2/internal/descriptor/apiconfig/apiconfig.swagger.json
  45. 71 0
      protoc-gen-openapiv2/internal/descriptor/grpc_api_configuration.go
  46. 149 0
      protoc-gen-openapiv2/internal/descriptor/grpc_api_configuration_test.go
  47. 58 0
      protoc-gen-openapiv2/internal/descriptor/openapi_configuration.go
  48. 114 0
      protoc-gen-openapiv2/internal/descriptor/openapi_configuration_test.go
  49. 683 0
      protoc-gen-openapiv2/internal/descriptor/openapiconfig/openapiconfig.pb.go
  50. 51 0
      protoc-gen-openapiv2/internal/descriptor/openapiconfig/openapiconfig.proto
  51. 46 0
      protoc-gen-openapiv2/internal/descriptor/openapiconfig/openapiconfig.swagger.json
  52. 702 0
      protoc-gen-openapiv2/internal/descriptor/registry.go
  53. 718 0
      protoc-gen-openapiv2/internal/descriptor/registry_test.go
  54. 347 0
      protoc-gen-openapiv2/internal/descriptor/services.go
  55. 1447 0
      protoc-gen-openapiv2/internal/descriptor/services_test.go
  56. 538 0
      protoc-gen-openapiv2/internal/descriptor/types.go
  57. 269 0
      protoc-gen-openapiv2/internal/descriptor/types_test.go
  58. 12 0
      protoc-gen-openapiv2/internal/generator/generator.go
  59. 41 0
      protoc-gen-openapiv2/internal/genopenapi/cycle_test.go
  60. 2 0
      protoc-gen-openapiv2/internal/genopenapi/doc.go
  61. 247 0
      protoc-gen-openapiv2/internal/genopenapi/generator.go
  62. 10 0
      protoc-gen-openapiv2/internal/genopenapi/helpers.go
  63. 10 0
      protoc-gen-openapiv2/internal/genopenapi/helpers_go111_old.go
  64. 2454 0
      protoc-gen-openapiv2/internal/genopenapi/template.go
  65. 4635 0
      protoc-gen-openapiv2/internal/genopenapi/template_test.go
  66. 282 0
      protoc-gen-openapiv2/internal/genopenapi/types.go
  67. 121 0
      protoc-gen-openapiv2/internal/httprule/compile.go
  68. 129 0
      protoc-gen-openapiv2/internal/httprule/compile_test.go
  69. 12 0
      protoc-gen-openapiv2/internal/httprule/fuzz.go
  70. 369 0
      protoc-gen-openapiv2/internal/httprule/parse.go
  71. 366 0
      protoc-gen-openapiv2/internal/httprule/parse_test.go
  72. 60 0
      protoc-gen-openapiv2/internal/httprule/types.go
  73. 91 0
      protoc-gen-openapiv2/internal/httprule/types_test.go
  74. 228 0
      protoc-gen-openapiv2/main.go
  75. 186 0
      protoc-gen-openapiv2/main_test.go

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+settings.json
+.DS_Store
+examples/helloworld/server/server
+examples/helloworld/server/dist
+.idea

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Kratos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 36 - 0
README.md

@@ -0,0 +1,36 @@
+# swagger-api
+## Quick Start
+
+在项目中引入openapiv2
+
+```go
+import	"github.com/go-kratos/swagger-api/openapiv2"
+
+h := openapiv2.NewHandler()
+//将/q/路由放在最前匹配
+httpSrv.HandlePrefix("/q/", h)
+```
+
+支持generator进行自定义配置
+```go
+h := openapiv2.NewHandler(openapiv2.WithGeneratorOptions(generator.UseJSONNamesForFields(true), generator.EnumsAsInts(true)))
+```
+更多配置参见 https://github.com/go-kratos/grpc-gateway/blob/master/protoc-gen-openapiv2/generator/option.go
+
+
+启动应用后,在浏览器中输入 [http://\<ip>:\<port>/q/services](http://ip:port/q/services),在顶栏右侧选框选取希望查看的服务名,即可浏览接口文档。
+![select service](/img/swagger.png)
+
+## FAQ
+#### 1. 如果启动时顶栏选框未显示可选的服务名,或访问/q/services出现报错,`failed to decompress enc: bad gzipped descriptor: EOF`的报错说明部分依赖的proto文件生成的路径不对导致的,
+比如:
+- api/basedata/tag/v1/tag.proto
+- api/basedata/article/v1/article.proto
+
+当 **api/basedata/article/v1/article.proto** import "api/basedata/tag/v1/tag.proto"时 ,生成的依赖为api/basedata/tag/v1/tag.proto 
+但是 tag.proto 生成的tag.pb.go文件中source是tag.proto(漏掉了api/basedata/tag/v1/)导致了依赖未找到
+
+此时需要生成tag.pb.go时kratos proto client api/basedata/tag/v1/tag.proto 补全proto的路径,这样生成的tag.pb.go文件中source就是正确的(api/basedata/tag/v1/tag.proto)
+
+#### 2. 给swagger api添加说明、examples
+请参考[a_bit_of_everything](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto),同时注意需要将[annotations.proto](https://github.com/go-kratos/kratos/blob/main/third_party/protoc-gen-openapiv2/options/annotations.proto)和[openapiv2.proto](https://github.com/go-kratos/kratos/blob/main/third_party/protoc-gen-openapiv2/options/openapiv2.proto)这两个proto文件复制到third_party/protoc-gen-openapiv2/options目录下

+ 18 - 0
go.mod

@@ -0,0 +1,18 @@
+module git.ikuban.com/server/swagger-api
+
+go 1.15
+
+require (
+	github.com/ghodss/yaml v1.0.0
+	github.com/go-kratos/kratos/v2 v2.0.3
+	github.com/go-kratos/swagger-api v1.0.1
+	github.com/golang/glog v1.0.0
+	github.com/golang/protobuf v1.5.2
+	github.com/google/go-cmp v0.5.9
+	github.com/gorilla/mux v1.8.0
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0
+	github.com/rakyll/statik v0.1.7
+	google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a
+	google.golang.org/grpc v1.50.1
+	google.golang.org/protobuf v1.28.1
+)

+ 851 - 0
go.sum

@@ -0,0 +1,851 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
+cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
+cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
+cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
+cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
+cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
+cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
+cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
+cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
+cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
+cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
+cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
+cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
+cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
+cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
+cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
+cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
+cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
+cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
+cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
+cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
+cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
+cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
+cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
+cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
+cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
+cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
+cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
+cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
+cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
+cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
+cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
+cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
+cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
+cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
+cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
+cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
+cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
+cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
+cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
+cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
+cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
+cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
+cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
+cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
+cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
+cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
+cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
+cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
+cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
+cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
+cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
+cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
+cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
+cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
+cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
+cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
+cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
+cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
+cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
+cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
+cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
+cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
+cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
+cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
+cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
+cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
+cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
+cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
+cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
+cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
+cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
+cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
+cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
+cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
+cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
+cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
+cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
+cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
+cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
+cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
+cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
+cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
+cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
+cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
+cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
+cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
+cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
+cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
+cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
+cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
+cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
+cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
+cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
+cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
+cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
+cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
+cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
+cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
+cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
+cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
+cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
+cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
+cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
+cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
+cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
+cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
+cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
+cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
+cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
+cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
+cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kratos/grpc-gateway/v2 v2.5.1-0.20210811062259-c92d36e434b1/go.mod h1:qV5/s5A7wfY7GkNzptcUFRGAG4Y8H4mlXpSrOkDEIk0=
+github.com/go-kratos/kratos/v2 v2.0.3 h1:Q8kGPSMnxdMrZ1dBD6He+3Dr54qsN3MVYMPHdiy4+tM=
+github.com/go-kratos/kratos/v2 v2.0.3/go.mod h1:Hgl0YPry9YyLtwTTfwLfowPKg+YS0dgZ06O5NHqz5hE=
+github.com/go-kratos/swagger-api v1.0.1 h1:zBagw2tWA+icYrRPFqM3jFL60nO743ZiIvuiI6CpV6E=
+github.com/go-kratos/swagger-api v1.0.1/go.mod h1:KMYylgeNaApBgPYIF6kIP1kjGFgI4aDQNbQNBTqaewI=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
+github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
+github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
+github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
+github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 h1:kr3j8iIMR4ywO/O0rvksXaJvauGGCMg2zAZIiNZ9uIQ=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0/go.mod h1:ummNFgdgLhhX7aIiy35vVmQNS0rWXknfPE0qe6fmFXg=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
+github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc=
+go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
+go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
+go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8=
+go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58=
+go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
+google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
+google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
+google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
+google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
+google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
+google.golang.org/genproto v0.0.0-20210701191553-46259e63a0a9/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
+google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
+google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
+google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a h1:GH6UPn3ixhWcKDhpnEC55S75cerLPdpp3hrhfKYjZgw=
+google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
+google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

+ 74 - 0
openapiv2/handler.go

@@ -0,0 +1,74 @@
+package openapiv2
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/url"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/generator"
+	"github.com/go-kratos/kratos/v2/api/metadata"
+	"github.com/go-kratos/kratos/v2/transport/http/binding"
+	_ "github.com/go-kratos/swagger-api/openapiv2/swagger_ui/statik" // import statik static files
+	"github.com/gorilla/mux"
+	"github.com/rakyll/statik/fs"
+)
+
+func NewHandler(handlerOpts ...HandlerOption) http.Handler {
+	opts := &options{
+		// Compatible with default UseJSONNamesForFields is true
+		generatorOptions: []generator.Option{generator.UseJSONNamesForFields(true)},
+	}
+
+	for _, o := range handlerOpts {
+		o(opts)
+	}
+
+	service := New(nil, opts.generatorOptions...)
+	r := mux.NewRouter()
+
+	r.HandleFunc("/q/services", func(w http.ResponseWriter, r *http.Request) {
+		services, err := service.ListServices(r.Context(), &metadata.ListServicesRequest{})
+		if err != nil {
+			w.WriteHeader(500)
+			w.Write([]byte(err.Error()))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(200)
+		json.NewEncoder(w).Encode(services)
+	}).Methods("GET")
+
+	r.HandleFunc("/q/service/{name}", func(w http.ResponseWriter, r *http.Request) {
+		raws := mux.Vars(r)
+		vars := make(url.Values, len(raws))
+		for k, v := range raws {
+			vars[k] = []string{v}
+		}
+		var in metadata.GetServiceDescRequest
+		if err := binding.BindQuery(vars, &in); err != nil {
+			w.WriteHeader(400)
+			w.Write([]byte(err.Error()))
+			return
+		}
+
+		content, err := service.GetServiceOpenAPI(r.Context(), &in, false)
+		if err != nil {
+			w.WriteHeader(500)
+			w.Write([]byte(err.Error()))
+			return
+		}
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(200)
+		w.Write([]byte(content))
+	}).Methods("GET")
+
+	statikFS, err := fs.New()
+	if err != nil {
+		panic(err)
+	}
+	staticServer := http.FileServer(statikFS)
+	sh := http.StripPrefix("/q/swagger-ui", staticServer)
+	r.PathPrefix("/q/swagger-ui").Handler(sh)
+	return r
+}

+ 15 - 0
openapiv2/options.go

@@ -0,0 +1,15 @@
+package openapiv2
+
+import "git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/generator"
+
+type options struct {
+	generatorOptions []generator.Option
+}
+
+type HandlerOption func(opt *options)
+
+func WithGeneratorOptions(opts ...generator.Option) HandlerOption {
+	return func(opt *options) {
+		opt.generatorOptions = opts
+	}
+}

+ 63 - 0
openapiv2/service.go

@@ -0,0 +1,63 @@
+package openapiv2
+
+import (
+	"context"
+	"fmt"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/generator"
+	"github.com/go-kratos/kratos/v2/api/metadata"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+// Service is service
+type Service struct {
+	ser  *metadata.Server
+	opts []generator.Option
+}
+
+// New service
+func New(srv *grpc.Server, opts ...generator.Option) *Service {
+	return &Service{
+		ser:  metadata.NewServer(srv),
+		opts: opts,
+	}
+}
+
+// ListServices list services
+func (s *Service) ListServices(ctx context.Context, in *metadata.ListServicesRequest) (*metadata.ListServicesReply, error) {
+	return s.ser.ListServices(ctx, &metadata.ListServicesRequest{})
+}
+
+// GetServiceOpenAPI get service open api
+func (s *Service) GetServiceOpenAPI(ctx context.Context, in *metadata.GetServiceDescRequest, onlyRPC bool) (string, error) {
+	protoSet, err := s.ser.GetServiceDesc(ctx, in)
+	if err != nil {
+		return "", err
+	}
+	files := protoSet.FileDescSet.File
+	var target string
+	if len(files) == 0 {
+		return "", fmt.Errorf("proto file is empty")
+	}
+	if files[len(files)-1].Name == nil {
+		return "", fmt.Errorf("proto file's name is null")
+	}
+	target = *files[len(files)-1].Name
+
+	req := new(pluginpb.CodeGeneratorRequest)
+	req.FileToGenerate = []string{target}
+	var para = ""
+	req.Parameter = &para
+	req.ProtoFile = files
+
+	g := generator.NewGenerator(s.opts...)
+	resp, err := g.Gen(req, onlyRPC)
+	if err != nil {
+		return "", err
+	}
+	if len(resp.File) == 0 {
+		return "{}", nil
+	}
+	return *resp.File[0].Content, nil
+}

BIN
openapiv2/swagger_ui/dist/favicon-16x16.png


BIN
openapiv2/swagger_ui/dist/favicon-32x32.png


+ 72 - 0
openapiv2/swagger_ui/dist/index.html

@@ -0,0 +1,72 @@
+<!-- HTML for static distribution bundle build -->
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>Swagger UI</title>
+	<link rel="stylesheet" type="text/css" href="/q/swagger-ui/swagger-ui.css"/>
+	<link rel="icon" type="image/png" href="/q/swagger-ui/favicon-32x32.png" sizes="32x32"/>
+	<link rel="icon" type="image/png" href="/q/swagger-ui/favicon-16x16.png" sizes="16x16"/>
+	<style>
+		html {
+			box-sizing: border-box;
+			overflow: -moz-scrollbars-vertical;
+			overflow-y: scroll;
+		}
+
+		*,
+		*:before,
+		*:after {
+			box-sizing: inherit;
+		}
+
+		body {
+			margin: 0;
+			background: #fafafa;
+		}
+	</style>
+</head>
+
+<body>
+<div id="swagger-ui"></div>
+
+<script src="/q/swagger-ui/swagger-ui-bundle.js" charset="UTF-8"></script>
+<script src="/q/swagger-ui/swagger-ui-standalone-preset.js" charset="UTF-8"></script>
+<script>
+    window.onload = function () {
+        const servicesUrl = new URL("/q/services", window.location.href);
+        fetch(servicesUrl.toString())
+            .then(response => response.json())
+            .then(data => {
+                const urls = data.services.filter((x) => [
+                    "grpc.health.v1.Health",
+                    "kratos.api.Metadata",
+                    "grpc.reflection.v1alpha.ServerReflection",
+                ].indexOf(x) === -1).map((x) => {
+                    const url = new URL("/q/service/" + x, window.location.href);
+                    return {url: url.toString(), name: x}
+                });
+                // Begin Swagger UI call region
+                const ui = SwaggerUIBundle({
+                    urls: urls,
+                    dom_id: '#swagger-ui',
+                    deepLinking: true,
+                    presets: [
+                        SwaggerUIBundle.presets.apis,
+                        SwaggerUIStandalonePreset,
+                    ],
+                    plugins: [
+                        SwaggerUIBundle.plugins.Topbar,
+                        SwaggerUIBundle.plugins.DownloadUrl,
+                    ],
+                    layout: "StandaloneLayout"
+                });
+                // End Swagger UI call region
+
+                window.ui = ui;
+            });
+
+    };
+</script>
+</body>
+</html>

+ 75 - 0
openapiv2/swagger_ui/dist/oauth2-redirect.html

@@ -0,0 +1,75 @@
+<!doctype html>
+<html lang="en-US">
+<head>
+    <title>Swagger UI: OAuth2 Redirect</title>
+</head>
+<body>
+<script>
+    'use strict';
+    function run () {
+        var oauth2 = window.opener.swaggerUIRedirectOauth2;
+        var sentState = oauth2.state;
+        var redirectUrl = oauth2.redirectUrl;
+        var isValid, qp, arr;
+
+        if (/code|token|error/.test(window.location.hash)) {
+            qp = window.location.hash.substring(1);
+        } else {
+            qp = location.search.substring(1);
+        }
+
+        arr = qp.split("&");
+        arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
+        qp = qp ? JSON.parse('{' + arr.join() + '}',
+                function (key, value) {
+                    return key === "" ? value : decodeURIComponent(value);
+                }
+        ) : {};
+
+        isValid = qp.state === sentState;
+
+        if ((
+          oauth2.auth.schema.get("flow") === "accessCode" ||
+          oauth2.auth.schema.get("flow") === "authorizationCode" ||
+          oauth2.auth.schema.get("flow") === "authorization_code"
+        ) && !oauth2.auth.code) {
+            if (!isValid) {
+                oauth2.errCb({
+                    authId: oauth2.auth.name,
+                    source: "auth",
+                    level: "warning",
+                    message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
+                });
+            }
+
+            if (qp.code) {
+                delete oauth2.state;
+                oauth2.auth.code = qp.code;
+                oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
+            } else {
+                let oauthErrorMsg;
+                if (qp.error) {
+                    oauthErrorMsg = "["+qp.error+"]: " +
+                        (qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
+                        (qp.error_uri ? "More info: "+qp.error_uri : "");
+                }
+
+                oauth2.errCb({
+                    authId: oauth2.auth.name,
+                    source: "auth",
+                    level: "error",
+                    message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
+                });
+            }
+        } else {
+            oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
+        }
+        window.close();
+    }
+
+    window.addEventListener('DOMContentLoaded', function () {
+      run();
+    });
+</script>
+</body>
+</html>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
openapiv2/swagger_ui/dist/swagger-ui-bundle.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
openapiv2/swagger_ui/dist/swagger-ui-bundle.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
openapiv2/swagger_ui/dist/swagger-ui-es-bundle-core.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
openapiv2/swagger_ui/dist/swagger-ui-es-bundle-core.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
openapiv2/swagger_ui/dist/swagger-ui-es-bundle.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
openapiv2/swagger_ui/dist/swagger-ui-es-bundle.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
openapiv2/swagger_ui/dist/swagger-ui-standalone-preset.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
openapiv2/swagger_ui/dist/swagger-ui-standalone-preset.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
openapiv2/swagger_ui/dist/swagger-ui.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
openapiv2/swagger_ui/dist/swagger-ui.css.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
openapiv2/swagger_ui/dist/swagger-ui.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
openapiv2/swagger_ui/dist/swagger-ui.js.map


+ 3 - 0
openapiv2/swagger_ui/generate.go

@@ -0,0 +1,3 @@
+//go:generate statik -m -src=./dist
+
+package swagger_ui

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 9 - 0
openapiv2/swagger_ui/statik/statik.go


+ 74 - 0
protoc-gen-openapiv2/generator/generator.go

@@ -0,0 +1,74 @@
+package generator
+
+import (
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/codegenerator"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/genopenapi"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+// Generator is openapi v2 generator
+type Generator struct {
+	reg *descriptor.Registry
+}
+
+// NewGenerator return Generator
+func NewGenerator(options ...Option) *Generator {
+	gen := &Generator{
+		reg: descriptor.NewRegistry(),
+	}
+	gen.reg.SetUseJSONNamesForFields(true)
+	gen.reg.SetRecursiveDepth(1024)
+	gen.reg.SetMergeFileName("apidocs")
+	gen.reg.SetDisableDefaultErrors(true)
+	for _, o := range options {
+		o(gen)
+	}
+	return gen
+}
+
+// Gen generates openapi v2 json content
+func (g *Generator) Gen(req *pluginpb.CodeGeneratorRequest, onlyRPC bool) (*pluginpb.CodeGeneratorResponse, error) {
+	reg := g.reg
+	if reg == nil {
+		reg = NewGenerator().reg
+	}
+	reg.SetGenerateRPCMethods(onlyRPC)
+	if err := reg.SetRepeatedPathParamSeparator("csv"); err != nil {
+		return nil, err
+	}
+
+	gen := genopenapi.New(reg)
+
+	if err := genopenapi.AddErrorDefs(reg); err != nil {
+		return nil, err
+	}
+
+	if err := reg.Load(req); err != nil {
+		return nil, err
+	}
+	var targets []*descriptor.File
+	for _, target := range req.FileToGenerate {
+		f, err := reg.LookupFile(target)
+		if err != nil {
+			return nil, err
+		}
+		targets = append(targets, f)
+	}
+
+	out, err := gen.Generate(targets)
+	if err != nil {
+		return nil, err
+	}
+	return emitFiles(out), nil
+}
+
+func emitFiles(out []*descriptor.ResponseFile) *pluginpb.CodeGeneratorResponse {
+	files := make([]*pluginpb.CodeGeneratorResponse_File, len(out))
+	for idx, item := range out {
+		files[idx] = item.CodeGeneratorResponse_File
+	}
+	resp := &pluginpb.CodeGeneratorResponse{File: files}
+	codegenerator.SetSupportedFeaturesOnCodeGeneratorResponse(resp)
+	return resp
+}

+ 56 - 0
protoc-gen-openapiv2/generator/generator_test.go

@@ -0,0 +1,56 @@
+package generator
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/codegenerator"
+)
+
+func Test_Gen(t *testing.T) {
+	var err error
+	f, err := os.Open("./req.bin")
+	if err != nil {
+		t.Fatal(err)
+	}
+	req, err := codegenerator.ParseRequest(f)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var g Generator
+
+	resp, err := g.Gen(req, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, file := range resp.File {
+		fmt.Println(*file.Content)
+	}
+}
+func Test_NewGenerator(t *testing.T) {
+	var err error
+	f, err := os.Open("./req.bin")
+	if err != nil {
+		t.Fatal(err)
+	}
+	req, err := codegenerator.ParseRequest(f)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	g := NewGenerator(
+		DisableDefaultErrors(true),
+		UseJSONNamesForFields(false),
+		EnumsAsInts(true),
+		RecursiveDepth(2048),
+	)
+
+	resp, err := g.Gen(req, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, file := range resp.File {
+		fmt.Println(*file.Content)
+	}
+}

+ 38 - 0
protoc-gen-openapiv2/generator/option.go

@@ -0,0 +1,38 @@
+package generator
+
+type Option func(gen *Generator)
+
+// UseJSONNamesForFields. if disabled, the original proto name will be used for generating OpenAPI definitions
+func UseJSONNamesForFields(b bool) Option {
+	return func(gen *Generator) {
+		gen.reg.SetUseJSONNamesForFields(b)
+	}
+}
+
+// RecursiveDepth. maximum recursion count allowed for a field type
+func RecursiveDepth(depth int) Option {
+	return func(gen *Generator) {
+		gen.reg.SetRecursiveDepth(depth)
+	}
+}
+
+// EnumsAsInts. whether to render enum values as integers, as opposed to string values
+func EnumsAsInts(b bool) Option {
+	return func(gen *Generator) {
+		gen.reg.SetEnumsAsInts(b)
+	}
+}
+
+// MergeFileName. target OpenAPI file name prefix after merge
+func MergeFileName(name string) Option {
+	return func(gen *Generator) {
+		gen.reg.SetMergeFileName(name)
+	}
+}
+
+// DisableDefaultErrors. if set, disables generation of default errors. This is useful if you have defined custom error handling
+func DisableDefaultErrors(b bool) Option {
+	return func(gen *Generator) {
+		gen.reg.SetDisableDefaultErrors(b)
+	}
+}

BIN
protoc-gen-openapiv2/generator/req.bin


+ 14 - 0
protoc-gen-openapiv2/internal/casing/BUILD.bazel

@@ -0,0 +1,14 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "casing",
+    srcs = ["camel.go"],
+    importpath = "github.com/go-kratos/grpc-gateway/v2/internal/casing",
+    visibility = ["//:__subpackages__"],
+)
+
+alias(
+    name = "go_default_library",
+    actual = ":casing",
+    visibility = ["//:__subpackages__"],
+)

+ 28 - 0
protoc-gen-openapiv2/internal/casing/LICENSE.md

@@ -0,0 +1,28 @@
+Copyright 2010 The Go Authors.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+

+ 5 - 0
protoc-gen-openapiv2/internal/casing/README.md

@@ -0,0 +1,5 @@
+# Case conversion
+
+This package contains a single function, copied from the
+`github.com/golang/protobuf/protoc-gen-go/generator` package. That
+modules LICENSE is referenced in its entirety in this package.

+ 63 - 0
protoc-gen-openapiv2/internal/casing/camel.go

@@ -0,0 +1,63 @@
+package casing
+
+// Camel returns the CamelCased name.
+//
+// This was moved from the now deprecated github.com/golang/protobuf/protoc-gen-go/generator package
+//
+// If there is an interior underscore followed by a lower case letter,
+// drop the underscore and convert the letter to upper case.
+// There is a remote possibility of this rewrite causing a name collision,
+// but it's so remote we're prepared to pretend it's nonexistent - since the
+// C++ generator lowercases names, it's extremely unlikely to have two fields
+// with different capitalizations.
+// In short, _my_field_name_2 becomes XMyFieldName_2.
+func Camel(s string) string {
+	if s == "" {
+		return ""
+	}
+	t := make([]byte, 0, 32)
+	i := 0
+	if s[0] == '_' {
+		// Need a capital letter; drop the '_'.
+		t = append(t, 'X')
+		i++
+	}
+	// Invariant: if the next letter is lower case, it must be converted
+	// to upper case.
+	// That is, we process a word at a time, where words are marked by _ or
+	// upper case letter. Digits are treated as words.
+	for ; i < len(s); i++ {
+		c := s[i]
+		if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
+			continue // Skip the underscore in s.
+		}
+		if isASCIIDigit(c) {
+			t = append(t, c)
+			continue
+		}
+		// Assume we have a letter now - if not, it's a bogus identifier.
+		// The next word is a sequence of characters that must start upper case.
+		if isASCIILower(c) {
+			c ^= ' ' // Make it a capital letter.
+		}
+		t = append(t, c) // Guaranteed not lower case.
+		// Accept lower case sequence that follows.
+		for i+1 < len(s) && isASCIILower(s[i+1]) {
+			i++
+			t = append(t, s[i])
+		}
+	}
+	return string(t)
+}
+
+// And now lots of helper functions.
+
+// Is c an ASCII lower-case letter?
+func isASCIILower(c byte) bool {
+	return 'a' <= c && c <= 'z'
+}
+
+// Is c an ASCII digit?
+func isASCIIDigit(c byte) bool {
+	return '0' <= c && c <= '9'
+}

+ 36 - 0
protoc-gen-openapiv2/internal/codegenerator/BUILD.bazel

@@ -0,0 +1,36 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+package(default_visibility = ["//visibility:public"])
+
+go_library(
+    name = "codegenerator",
+    srcs = [
+        "doc.go",
+        "parse_req.go",
+        "supported_features.go",
+    ],
+    importpath = "github.com/go-kratos/grpc-gateway/v2/internal/codegenerator",
+    deps = [
+        "@org_golang_google_protobuf//compiler/protogen",
+        "@org_golang_google_protobuf//proto",
+        "@org_golang_google_protobuf//types/pluginpb",
+    ],
+)
+
+go_test(
+    name = "codegenerator_test",
+    srcs = ["parse_req_test.go"],
+    embed = [":codegenerator"],
+    deps = [
+        "@com_github_google_go_cmp//cmp",
+        "@org_golang_google_protobuf//proto",
+        "@org_golang_google_protobuf//testing/protocmp",
+        "@org_golang_google_protobuf//types/pluginpb",
+    ],
+)
+
+alias(
+    name = "go_default_library",
+    actual = ":codegenerator",
+    visibility = ["//:__subpackages__"],
+)

+ 4 - 0
protoc-gen-openapiv2/internal/codegenerator/doc.go

@@ -0,0 +1,4 @@
+/*
+Package codegenerator contains reusable functions used by the code generators.
+*/
+package codegenerator

+ 23 - 0
protoc-gen-openapiv2/internal/codegenerator/parse_req.go

@@ -0,0 +1,23 @@
+package codegenerator
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+// ParseRequest parses a code generator request from a proto Message.
+func ParseRequest(r io.Reader) (*pluginpb.CodeGeneratorRequest, error) {
+	input, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read code generator request: %v", err)
+	}
+	req := new(pluginpb.CodeGeneratorRequest)
+	if err = proto.Unmarshal(input, req); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal code generator request: %v", err)
+	}
+	return req, nil
+}

+ 70 - 0
protoc-gen-openapiv2/internal/codegenerator/parse_req_test.go

@@ -0,0 +1,70 @@
+package codegenerator_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strings"
+	"testing"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/codegenerator"
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/testing/protocmp"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+var parseReqTests = []struct {
+	name      string
+	in        io.Reader
+	out       *pluginpb.CodeGeneratorRequest
+	expectErr bool
+}{
+	{
+		"Empty input should produce empty output",
+		mustGetReader(&pluginpb.CodeGeneratorRequest{}),
+		&pluginpb.CodeGeneratorRequest{},
+		false,
+	},
+	{
+		"Invalid reader should produce error",
+		&invalidReader{},
+		nil,
+		true,
+	},
+	{
+		"Invalid proto message should produce error",
+		strings.NewReader("{}"),
+		nil,
+		true,
+	},
+}
+
+func TestParseRequest(t *testing.T) {
+	for _, tt := range parseReqTests {
+		t.Run(tt.name, func(t *testing.T) {
+			out, err := codegenerator.ParseRequest(tt.in)
+			if tt.expectErr && err == nil {
+				t.Error("did not error as expected")
+			}
+			if diff := cmp.Diff(out, tt.out, protocmp.Transform()); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
+
+func mustGetReader(pb proto.Message) io.Reader {
+	b, err := proto.Marshal(pb)
+	if err != nil {
+		panic(err)
+	}
+	return bytes.NewBuffer(b)
+}
+
+type invalidReader struct {
+}
+
+func (*invalidReader) Read(p []byte) (int, error) {
+	return 0, fmt.Errorf("invalid reader")
+}

+ 24 - 0
protoc-gen-openapiv2/internal/codegenerator/supported_features.go

@@ -0,0 +1,24 @@
+package codegenerator
+
+import (
+	"google.golang.org/protobuf/compiler/protogen"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+func supportedCodeGeneratorFeatures() uint64 {
+	// Enable support for optional keyword in proto3.
+	return uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
+}
+
+// SetSupportedFeaturesOnPluginGen sets supported proto3 features
+// on protogen.Plugin.
+func SetSupportedFeaturesOnPluginGen(gen *protogen.Plugin) {
+	gen.SupportedFeatures = supportedCodeGeneratorFeatures()
+}
+
+// SetSupportedFeaturesOnCodeGeneratorResponse sets supported proto3 features
+// on pluginpb.CodeGeneratorResponse.
+func SetSupportedFeaturesOnCodeGeneratorResponse(resp *pluginpb.CodeGeneratorResponse) {
+	sf := supportedCodeGeneratorFeatures()
+	resp.SupportedFeatures = &sf
+}

+ 60 - 0
protoc-gen-openapiv2/internal/descriptor/BUILD.bazel

@@ -0,0 +1,60 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+package(default_visibility = ["//visibility:public"])
+
+go_library(
+    name = "descriptor",
+    srcs = [
+        "grpc_api_configuration.go",
+        "openapi_configuration.go",
+        "registry.go",
+        "services.go",
+        "types.go",
+    ],
+    importpath = "github.com/go-kratos/grpc-gateway/v2/internal/descriptor",
+    deps = [
+        "//internal/casing",
+        "//internal/codegenerator",
+        "//internal/descriptor/apiconfig",
+        "//internal/descriptor/openapiconfig",
+        "//internal/httprule",
+        "//protoc-gen-openapiv2/options",
+        "@com_github_ghodss_yaml//:yaml",
+        "@com_github_golang_glog//:glog",
+        "@go_googleapis//google/api:annotations_go_proto",
+        "@org_golang_google_protobuf//compiler/protogen",
+        "@org_golang_google_protobuf//encoding/protojson",
+        "@org_golang_google_protobuf//proto",
+        "@org_golang_google_protobuf//types/descriptorpb",
+        "@org_golang_google_protobuf//types/pluginpb",
+    ],
+)
+
+go_test(
+    name = "descriptor_test",
+    size = "small",
+    srcs = [
+        "grpc_api_configuration_test.go",
+        "openapi_configuration_test.go",
+        "registry_test.go",
+        "services_test.go",
+        "types_test.go",
+    ],
+    embed = [":descriptor"],
+    deps = [
+        "//internal/descriptor/openapiconfig",
+        "//internal/httprule",
+        "//protoc-gen-openapiv2/options",
+        "@org_golang_google_protobuf//compiler/protogen",
+        "@org_golang_google_protobuf//encoding/prototext",
+        "@org_golang_google_protobuf//proto",
+        "@org_golang_google_protobuf//types/descriptorpb",
+        "@org_golang_google_protobuf//types/pluginpb",
+    ],
+)
+
+alias(
+    name = "go_default_library",
+    actual = ":descriptor",
+    visibility = ["//:__subpackages__"],
+)

+ 35 - 0
protoc-gen-openapiv2/internal/descriptor/apiconfig/BUILD.bazel

@@ -0,0 +1,35 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+package(default_visibility = ["//visibility:public"])
+
+proto_library(
+    name = "apiconfig_proto",
+    srcs = [
+        "apiconfig.proto",
+    ],
+    deps = [
+        "@go_googleapis//google/api:annotations_proto",
+    ],
+)
+
+go_proto_library(
+    name = "apiconfig_go_proto",
+    compilers = ["//:go_apiv2"],
+    importpath = "github.com/go-kratos/grpc-gateway/v2/internal/descriptor/apiconfig",
+    proto = ":apiconfig_proto",
+    deps = ["@go_googleapis//google/api:annotations_go_proto"],
+)
+
+go_library(
+    name = "apiconfig",
+    embed = [":apiconfig_go_proto"],
+    importpath = "github.com/go-kratos/grpc-gateway/v2/internal/descriptor/apiconfig",
+)
+
+alias(
+    name = "go_default_library",
+    actual = ":apiconfig",
+    visibility = ["//:__subpackages__"],
+)

+ 165 - 0
protoc-gen-openapiv2/internal/descriptor/apiconfig/apiconfig.pb.go

@@ -0,0 +1,165 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.15.2
+// source: internal/descriptor/apiconfig/apiconfig.proto
+
+package apiconfig
+
+import (
+	annotations "google.golang.org/genproto/googleapis/api/annotations"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// GrpcAPIService represents a stripped down version of google.api.Service .
+// Compare to https://github.com/googleapis/googleapis/blob/master/google/api/service.proto
+// The original imports 23 other protobuf files we are not interested in. If a significant
+// subset (>50%) of these start being reproduced in this file we should swap to using the
+// full generated version instead.
+//
+// For the purposes of the gateway generator we only consider a small subset of all
+// available features google supports in their service descriptions. Thanks to backwards
+// compatibility guarantees by protobuf it is safe for us to remove the other fields.
+type GrpcAPIService struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Http Rule.
+	Http *annotations.Http `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"`
+}
+
+func (x *GrpcAPIService) Reset() {
+	*x = GrpcAPIService{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_apiconfig_apiconfig_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GrpcAPIService) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GrpcAPIService) ProtoMessage() {}
+
+func (x *GrpcAPIService) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_apiconfig_apiconfig_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GrpcAPIService.ProtoReflect.Descriptor instead.
+func (*GrpcAPIService) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_apiconfig_apiconfig_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GrpcAPIService) GetHttp() *annotations.Http {
+	if x != nil {
+		return x.Http
+	}
+	return nil
+}
+
+var File_internal_descriptor_apiconfig_apiconfig_proto protoreflect.FileDescriptor
+
+var file_internal_descriptor_apiconfig_apiconfig_proto_rawDesc = []byte{
+	0x0a, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72,
+	0x69, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f,
+	0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+	0x2a, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x69, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f,
+	0x72, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x15, 0x67, 0x6f, 0x6f,
+	0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x22, 0x36, 0x0a, 0x0e, 0x47, 0x72, 0x70, 0x63, 0x41, 0x50, 0x49, 0x53, 0x65, 0x72,
+	0x76, 0x69, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x68, 0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x10, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e,
+	0x48, 0x74, 0x74, 0x70, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x42, 0x49, 0x5a, 0x47, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, 0x63,
+	0x6f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74,
+	0x65, 0x77, 0x61, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+	0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_internal_descriptor_apiconfig_apiconfig_proto_rawDescOnce sync.Once
+	file_internal_descriptor_apiconfig_apiconfig_proto_rawDescData = file_internal_descriptor_apiconfig_apiconfig_proto_rawDesc
+)
+
+func file_internal_descriptor_apiconfig_apiconfig_proto_rawDescGZIP() []byte {
+	file_internal_descriptor_apiconfig_apiconfig_proto_rawDescOnce.Do(func() {
+		file_internal_descriptor_apiconfig_apiconfig_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_descriptor_apiconfig_apiconfig_proto_rawDescData)
+	})
+	return file_internal_descriptor_apiconfig_apiconfig_proto_rawDescData
+}
+
+var file_internal_descriptor_apiconfig_apiconfig_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_internal_descriptor_apiconfig_apiconfig_proto_goTypes = []interface{}{
+	(*GrpcAPIService)(nil),   // 0: grpc.gateway.internal.descriptor.apiconfig.GrpcAPIService
+	(*annotations.Http)(nil), // 1: google.api.Http
+}
+var file_internal_descriptor_apiconfig_apiconfig_proto_depIdxs = []int32{
+	1, // 0: grpc.gateway.internal.descriptor.apiconfig.GrpcAPIService.http:type_name -> google.api.Http
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_internal_descriptor_apiconfig_apiconfig_proto_init() }
+func file_internal_descriptor_apiconfig_apiconfig_proto_init() {
+	if File_internal_descriptor_apiconfig_apiconfig_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_internal_descriptor_apiconfig_apiconfig_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GrpcAPIService); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_internal_descriptor_apiconfig_apiconfig_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_internal_descriptor_apiconfig_apiconfig_proto_goTypes,
+		DependencyIndexes: file_internal_descriptor_apiconfig_apiconfig_proto_depIdxs,
+		MessageInfos:      file_internal_descriptor_apiconfig_apiconfig_proto_msgTypes,
+	}.Build()
+	File_internal_descriptor_apiconfig_apiconfig_proto = out.File
+	file_internal_descriptor_apiconfig_apiconfig_proto_rawDesc = nil
+	file_internal_descriptor_apiconfig_apiconfig_proto_goTypes = nil
+	file_internal_descriptor_apiconfig_apiconfig_proto_depIdxs = nil
+}

+ 21 - 0
protoc-gen-openapiv2/internal/descriptor/apiconfig/apiconfig.proto

@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+package grpc.gateway.internal.descriptor.apiconfig;
+
+option go_package = "github.com/go-kratos/grpc-gateway/v2/internal/descriptor/apiconfig";
+
+import "google/api/http.proto";
+
+// GrpcAPIService represents a stripped down version of google.api.Service .
+// Compare to https://github.com/googleapis/googleapis/blob/master/google/api/service.proto
+// The original imports 23 other protobuf files we are not interested in. If a significant
+// subset (>50%) of these start being reproduced in this file we should swap to using the
+// full generated version instead.
+//
+// For the purposes of the gateway generator we only consider a small subset of all
+// available features google supports in their service descriptions. Thanks to backwards
+// compatibility guarantees by protobuf it is safe for us to remove the other fields.
+message GrpcAPIService {
+	// Http Rule.
+    google.api.Http http = 1;
+}

+ 46 - 0
protoc-gen-openapiv2/internal/descriptor/apiconfig/apiconfig.swagger.json

@@ -0,0 +1,46 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "internal/descriptor/apiconfig/apiconfig.proto",
+    "version": "version not set"
+  },
+  "consumes": [
+    "application/json"
+  ],
+  "produces": [
+    "application/json"
+  ],
+  "paths": {},
+  "definitions": {
+    "protobufAny": {
+      "type": "object",
+      "properties": {
+        "typeUrl": {
+          "type": "string"
+        },
+        "value": {
+          "type": "string",
+          "format": "byte"
+        }
+      }
+    },
+    "rpcStatus": {
+      "type": "object",
+      "properties": {
+        "code": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "message": {
+          "type": "string"
+        },
+        "details": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/protobufAny"
+          }
+        }
+      }
+    }
+  }
+}

+ 71 - 0
protoc-gen-openapiv2/internal/descriptor/grpc_api_configuration.go

@@ -0,0 +1,71 @@
+package descriptor
+
+import (
+	"fmt"
+	"io/ioutil"
+	"strings"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor/apiconfig"
+	"github.com/ghodss/yaml"
+	"google.golang.org/protobuf/encoding/protojson"
+)
+
+func loadGrpcAPIServiceFromYAML(yamlFileContents []byte, yamlSourceLogName string) (*apiconfig.GrpcAPIService, error) {
+	jsonContents, err := yaml.YAMLToJSON(yamlFileContents)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert gRPC API Configuration from YAML in '%v' to JSON: %v", yamlSourceLogName, err)
+	}
+
+	// As our GrpcAPIService is incomplete, accept unknown fields.
+	unmarshaler := protojson.UnmarshalOptions{
+		DiscardUnknown: true,
+	}
+
+	serviceConfiguration := apiconfig.GrpcAPIService{}
+	if err := unmarshaler.Unmarshal(jsonContents, &serviceConfiguration); err != nil {
+		return nil, fmt.Errorf("failed to parse gRPC API Configuration from YAML in '%v': %v", yamlSourceLogName, err)
+	}
+
+	return &serviceConfiguration, nil
+}
+
+func registerHTTPRulesFromGrpcAPIService(registry *Registry, service *apiconfig.GrpcAPIService, sourceLogName string) error {
+	if service.Http == nil {
+		// Nothing to do
+		return nil
+	}
+
+	for _, rule := range service.Http.GetRules() {
+		selector := "." + strings.Trim(rule.GetSelector(), " ")
+		if strings.ContainsAny(selector, "*, ") {
+			return fmt.Errorf("selector '%v' in %v must specify a single service method without wildcards", rule.GetSelector(), sourceLogName)
+		}
+
+		registry.AddExternalHTTPRule(selector, rule)
+	}
+
+	return nil
+}
+
+// LoadGrpcAPIServiceFromYAML loads a gRPC API Configuration from the given YAML file
+// and registers the HttpRule descriptions contained in it as externalHTTPRules in
+// the given registry. This must be done before loading the proto file.
+//
+// You can learn more about gRPC API Service descriptions from google's documentation
+// at https://cloud.google.com/endpoints/docs/grpc/grpc-service-config
+//
+// Note that for the purposes of the gateway generator we only consider a subset of all
+// available features google supports in their service descriptions.
+func (r *Registry) LoadGrpcAPIServiceFromYAML(yamlFile string) error {
+	yamlFileContents, err := ioutil.ReadFile(yamlFile)
+	if err != nil {
+		return fmt.Errorf("failed to read gRPC API Configuration description from '%v': %v", yamlFile, err)
+	}
+
+	service, err := loadGrpcAPIServiceFromYAML(yamlFileContents, yamlFile)
+	if err != nil {
+		return err
+	}
+
+	return registerHTTPRulesFromGrpcAPIService(r, service, yamlFile)
+}

+ 149 - 0
protoc-gen-openapiv2/internal/descriptor/grpc_api_configuration_test.go

@@ -0,0 +1,149 @@
+package descriptor
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestLoadGrpcAPIServiceFromYAMLInvalidType(t *testing.T) {
+	// Ideally this would fail but for now this test documents that it doesn't
+	service, err := loadGrpcAPIServiceFromYAML([]byte(`type: not.the.right.type`), "invalidtype")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if service == nil {
+		t.Fatal("No service returned")
+	}
+}
+
+func TestLoadGrpcAPIServiceFromYAMLSingleRule(t *testing.T) {
+	service, err := loadGrpcAPIServiceFromYAML([]byte(`
+type: google.api.Service
+config_version: 3
+
+http:
+ rules:
+ - selector: grpctest.YourService.Echo
+   post: /v1/myecho
+   body: "*"
+`), "example")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if service.Http == nil {
+		t.Fatal("HTTP is empty")
+	}
+
+	if len(service.Http.GetRules()) != 1 {
+		t.Fatalf("Have %v rules instead of one. Got: %v", len(service.Http.GetRules()), service.Http.GetRules())
+	}
+
+	rule := service.Http.GetRules()[0]
+	if rule.GetSelector() != "grpctest.YourService.Echo" {
+		t.Errorf("Rule has unexpected selector '%v'", rule.GetSelector())
+	}
+	if rule.GetPost() != "/v1/myecho" {
+		t.Errorf("Rule has unexpected post '%v'", rule.GetPost())
+	}
+	if rule.GetBody() != "*" {
+		t.Errorf("Rule has unexpected body '%v'", rule.GetBody())
+	}
+}
+
+func TestLoadGrpcAPIServiceFromYAMLRejectInvalidYAML(t *testing.T) {
+	service, err := loadGrpcAPIServiceFromYAML([]byte(`
+type: google.api.Service
+config_version: 3
+
+http:
+ rules:
+ - selector: grpctest.YourService.Echo
+   - post: thislinebreakstheselectorblockabovewiththeleadingdash
+   body: "*"
+`), "invalidyaml")
+	if err == nil {
+		t.Fatal(err)
+	}
+
+	if !strings.Contains(err.Error(), "line 7") {
+		t.Errorf("Expected yaml error to be detected in line 7. Got other error: %v", err)
+	}
+
+	if service != nil {
+		t.Fatal("Service returned")
+	}
+}
+
+func TestLoadGrpcAPIServiceFromYAMLMultipleWithAdditionalBindings(t *testing.T) {
+	service, err := loadGrpcAPIServiceFromYAML([]byte(`
+type: google.api.Service
+config_version: 3
+
+http:
+ rules:
+ - selector: first.selector
+   post: /my/post/path
+   body: "*"
+   additional_bindings:
+   - post: /additional/post/path
+   - put: /additional/put/{value}/path
+   - delete: "{value}"
+   - patch: "/additional/patch/{value}"
+ - selector: some.other.service
+   delete: foo
+`), "example")
+	if err != nil {
+		t.Fatalf("Failed to load service description from YAML: %v", err)
+	}
+
+	if service == nil {
+		t.Fatal("No service returned")
+	}
+
+	if service.Http == nil {
+		t.Fatal("HTTP is empty")
+	}
+
+	if len(service.Http.GetRules()) != 2 {
+		t.Fatalf("%v service(s) returned when two were expected. Got: %v", len(service.Http.GetRules()), service.Http)
+	}
+
+	first := service.Http.GetRules()[0]
+	if first.GetSelector() != "first.selector" {
+		t.Errorf("first.selector has unexpected selector '%v'", first.GetSelector())
+	}
+	if first.GetBody() != "*" {
+		t.Errorf("first.selector has unexpected body '%v'", first.GetBody())
+	}
+	if first.GetPost() != "/my/post/path" {
+		t.Errorf("first.selector has unexpected post '%v'", first.GetPost())
+	}
+	if len(first.GetAdditionalBindings()) != 4 {
+		t.Fatalf("first.selector has unexpected number of bindings %v instead of four. Got: %v", len(first.GetAdditionalBindings()), first.GetAdditionalBindings())
+	}
+	if first.GetAdditionalBindings()[0].GetPost() != "/additional/post/path" {
+		t.Errorf("first.selector additional binding 0 has unexpected post '%v'", first.GetAdditionalBindings()[0].GetPost())
+	}
+	if first.GetAdditionalBindings()[1].GetPut() != "/additional/put/{value}/path" {
+		t.Errorf("first.selector additional binding 1 has unexpected put '%v'", first.GetAdditionalBindings()[0].GetPost())
+	}
+	if first.GetAdditionalBindings()[2].GetDelete() != "{value}" {
+		t.Errorf("first.selector additional binding 2 has unexpected delete '%v'", first.GetAdditionalBindings()[0].GetPost())
+	}
+	if first.GetAdditionalBindings()[3].GetPatch() != "/additional/patch/{value}" {
+		t.Errorf("first.selector additional binding 3 has unexpected patch '%v'", first.GetAdditionalBindings()[0].GetPost())
+	}
+
+	second := service.Http.GetRules()[1]
+	if second.GetSelector() != "some.other.service" {
+		t.Errorf("some.other.service has unexpected selector '%v'", second.GetSelector())
+	}
+	if second.GetDelete() != "foo" {
+		t.Errorf("some.other.service has unexpected delete '%v'", second.GetDelete())
+	}
+	if len(second.GetAdditionalBindings()) != 0 {
+		t.Errorf("some.other.service has %v additional bindings when it should not have any. Got: %v", len(second.GetAdditionalBindings()), second.GetAdditionalBindings())
+	}
+}

+ 58 - 0
protoc-gen-openapiv2/internal/descriptor/openapi_configuration.go

@@ -0,0 +1,58 @@
+package descriptor
+
+import (
+	"fmt"
+	"io/ioutil"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor/openapiconfig"
+	"github.com/ghodss/yaml"
+	"google.golang.org/protobuf/encoding/protojson"
+)
+
+func loadOpenAPIConfigFromYAML(yamlFileContents []byte, yamlSourceLogName string) (*openapiconfig.OpenAPIConfig, error) {
+	jsonContents, err := yaml.YAMLToJSON(yamlFileContents)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert OpenAPI Configuration from YAML in '%v' to JSON: %v", yamlSourceLogName, err)
+	}
+
+	// Reject unknown fields because OpenAPIConfig is only used here
+	unmarshaler := protojson.UnmarshalOptions{
+		DiscardUnknown: false,
+	}
+
+	openapiConfiguration := openapiconfig.OpenAPIConfig{}
+	if err := unmarshaler.Unmarshal(jsonContents, &openapiConfiguration); err != nil {
+		return nil, fmt.Errorf("failed to parse gRPC API Configuration from YAML in '%v': %v", yamlSourceLogName, err)
+	}
+
+	return &openapiConfiguration, nil
+}
+
+func registerOpenAPIOptions(registry *Registry, openAPIConfig *openapiconfig.OpenAPIConfig, yamlSourceLogName string) error {
+	if openAPIConfig.OpenapiOptions == nil {
+		// Nothing to do
+		return nil
+	}
+
+	if err := registry.RegisterOpenAPIOptions(openAPIConfig.OpenapiOptions); err != nil {
+		return fmt.Errorf("failed to register option in %s: %s", yamlSourceLogName, err)
+	}
+	return nil
+}
+
+// LoadOpenAPIConfigFromYAML loads an  OpenAPI Configuration from the given YAML file
+// and registers the OpenAPI options the given registry.
+// This must be done after loading the proto file.
+func (r *Registry) LoadOpenAPIConfigFromYAML(yamlFile string) error {
+	yamlFileContents, err := ioutil.ReadFile(yamlFile)
+	if err != nil {
+		return fmt.Errorf("failed to read gRPC API Configuration description from '%v': %v", yamlFile, err)
+	}
+
+	config, err := loadOpenAPIConfigFromYAML(yamlFileContents, yamlFile)
+	if err != nil {
+		return err
+	}
+
+	return registerOpenAPIOptions(r, config, yamlFile)
+}

+ 114 - 0
protoc-gen-openapiv2/internal/descriptor/openapi_configuration_test.go

@@ -0,0 +1,114 @@
+package descriptor
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
+)
+
+func TestLoadOpenAPIConfigFromYAMLRejectInvalidYAML(t *testing.T) {
+	config, err := loadOpenAPIConfigFromYAML([]byte(`
+openapiOptions:
+file:
+- file: test.proto
+  - option:
+      schemes:
+        - HTTP
+        - HTTPS
+        - WSS
+      securityDefinitions:
+        security:
+          ApiKeyAuth:
+            type: TYPE_API_KEY
+            in: IN_HEADER
+            name: "X-API-Key"
+`), "invalidyaml")
+	if err == nil {
+		t.Fatal(err)
+	}
+
+	if !strings.Contains(err.Error(), "line 4") {
+		t.Errorf("Expected yaml error to be detected in line 4. Got other error: %v", err)
+	}
+
+	if config != nil {
+		t.Fatal("Config returned")
+	}
+}
+
+func TestLoadOpenAPIConfigFromYAML(t *testing.T) {
+	config, err := loadOpenAPIConfigFromYAML([]byte(`
+openapiOptions:
+  file:
+  - file: test.proto
+    option:
+      schemes:
+      - HTTP
+      - HTTPS
+      - WSS
+      securityDefinitions:
+        security:
+          ApiKeyAuth:
+            type: TYPE_API_KEY
+            in: IN_HEADER
+            name: "X-API-Key"
+`), "openapi_options")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if config.OpenapiOptions == nil {
+		t.Fatal("OpenAPIOptions is empty")
+	}
+
+	opts := config.OpenapiOptions
+	if numFileOpts := len(opts.File); numFileOpts != 1 {
+		t.Fatalf("expected 1 file option but got %d", numFileOpts)
+	}
+
+	fileOpt := opts.File[0]
+
+	if fileOpt.File != "test.proto" {
+		t.Fatalf("file option has unexpected binding %s", fileOpt.File)
+	}
+
+	swaggerOpt := fileOpt.Option
+
+	if swaggerOpt == nil {
+		t.Fatal("expected option to be set")
+	}
+
+	if numSchemes := len(swaggerOpt.Schemes); numSchemes != 3 {
+		t.Fatalf("expected 3 schemes but got %d", numSchemes)
+	}
+	if swaggerOpt.Schemes[0] != options.Scheme_HTTP {
+		t.Fatalf("expected first scheme to be HTTP but got %s", swaggerOpt.Schemes[0])
+	}
+	if swaggerOpt.Schemes[1] != options.Scheme_HTTPS {
+		t.Fatalf("expected second scheme to be HTTPS but got %s", swaggerOpt.Schemes[1])
+	}
+	if swaggerOpt.Schemes[2] != options.Scheme_WSS {
+		t.Fatalf("expected third scheme to be WSS but got %s", swaggerOpt.Schemes[2])
+	}
+
+	if swaggerOpt.SecurityDefinitions == nil {
+		t.Fatal("expected securityDefinitions to be set")
+	}
+	if numSecOpts := len(swaggerOpt.SecurityDefinitions.Security); numSecOpts != 1 {
+		t.Fatalf("expected 1 security option but got %d", numSecOpts)
+	}
+	secOpt, ok := swaggerOpt.SecurityDefinitions.Security["ApiKeyAuth"]
+	if !ok {
+		t.Fatal("no SecurityScheme for key \"ApiKeyAuth\"")
+	}
+	if secOpt.Type != options.SecurityScheme_TYPE_API_KEY {
+		t.Fatalf("expected scheme type to be TYPE_API_KEY but got %s", secOpt.Type)
+	}
+	if secOpt.In != options.SecurityScheme_IN_HEADER {
+		t.Fatalf("expected scheme  in to be IN_HEADER but got %s", secOpt.In)
+	}
+	if secOpt.Name != "X-API-Key" {
+		t.Fatalf("expected name to be X-API-Key but got %s", secOpt.Name)
+	}
+}

+ 683 - 0
protoc-gen-openapiv2/internal/descriptor/openapiconfig/openapiconfig.pb.go

@@ -0,0 +1,683 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.15.2
+// source: internal/descriptor/openapiconfig/openapiconfig.proto
+
+package openapiconfig
+
+import (
+	options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// OpenAPIFileOption represents OpenAPI options on a file
+type OpenAPIFileOption struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	File   string           `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
+	Option *options.Swagger `protobuf:"bytes,2,opt,name=option,proto3" json:"option,omitempty"`
+}
+
+func (x *OpenAPIFileOption) Reset() {
+	*x = OpenAPIFileOption{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenAPIFileOption) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenAPIFileOption) ProtoMessage() {}
+
+func (x *OpenAPIFileOption) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use OpenAPIFileOption.ProtoReflect.Descriptor instead.
+func (*OpenAPIFileOption) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *OpenAPIFileOption) GetFile() string {
+	if x != nil {
+		return x.File
+	}
+	return ""
+}
+
+func (x *OpenAPIFileOption) GetOption() *options.Swagger {
+	if x != nil {
+		return x.Option
+	}
+	return nil
+}
+
+// OpenAPIMethodOption represents OpenAPI options on a method
+type OpenAPIMethodOption struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Method string             `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+	Option *options.Operation `protobuf:"bytes,2,opt,name=option,proto3" json:"option,omitempty"`
+}
+
+func (x *OpenAPIMethodOption) Reset() {
+	*x = OpenAPIMethodOption{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenAPIMethodOption) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenAPIMethodOption) ProtoMessage() {}
+
+func (x *OpenAPIMethodOption) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use OpenAPIMethodOption.ProtoReflect.Descriptor instead.
+func (*OpenAPIMethodOption) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *OpenAPIMethodOption) GetMethod() string {
+	if x != nil {
+		return x.Method
+	}
+	return ""
+}
+
+func (x *OpenAPIMethodOption) GetOption() *options.Operation {
+	if x != nil {
+		return x.Option
+	}
+	return nil
+}
+
+// OpenAPIMessageOption represents OpenAPI options on a message
+type OpenAPIMessageOption struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Message string          `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	Option  *options.Schema `protobuf:"bytes,2,opt,name=option,proto3" json:"option,omitempty"`
+}
+
+func (x *OpenAPIMessageOption) Reset() {
+	*x = OpenAPIMessageOption{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenAPIMessageOption) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenAPIMessageOption) ProtoMessage() {}
+
+func (x *OpenAPIMessageOption) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use OpenAPIMessageOption.ProtoReflect.Descriptor instead.
+func (*OpenAPIMessageOption) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *OpenAPIMessageOption) GetMessage() string {
+	if x != nil {
+		return x.Message
+	}
+	return ""
+}
+
+func (x *OpenAPIMessageOption) GetOption() *options.Schema {
+	if x != nil {
+		return x.Option
+	}
+	return nil
+}
+
+// OpenAPIServiceOption represents OpenAPI options on a service
+type OpenAPIServiceOption struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Service string       `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` // ex: Service
+	Option  *options.Tag `protobuf:"bytes,2,opt,name=option,proto3" json:"option,omitempty"`
+}
+
+func (x *OpenAPIServiceOption) Reset() {
+	*x = OpenAPIServiceOption{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenAPIServiceOption) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenAPIServiceOption) ProtoMessage() {}
+
+func (x *OpenAPIServiceOption) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use OpenAPIServiceOption.ProtoReflect.Descriptor instead.
+func (*OpenAPIServiceOption) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *OpenAPIServiceOption) GetService() string {
+	if x != nil {
+		return x.Service
+	}
+	return ""
+}
+
+func (x *OpenAPIServiceOption) GetOption() *options.Tag {
+	if x != nil {
+		return x.Option
+	}
+	return nil
+}
+
+// OpenAPIFieldOption represents OpenAPI options on a field
+type OpenAPIFieldOption struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Field  string              `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"`
+	Option *options.JSONSchema `protobuf:"bytes,2,opt,name=option,proto3" json:"option,omitempty"`
+}
+
+func (x *OpenAPIFieldOption) Reset() {
+	*x = OpenAPIFieldOption{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenAPIFieldOption) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenAPIFieldOption) ProtoMessage() {}
+
+func (x *OpenAPIFieldOption) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use OpenAPIFieldOption.ProtoReflect.Descriptor instead.
+func (*OpenAPIFieldOption) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *OpenAPIFieldOption) GetField() string {
+	if x != nil {
+		return x.Field
+	}
+	return ""
+}
+
+func (x *OpenAPIFieldOption) GetOption() *options.JSONSchema {
+	if x != nil {
+		return x.Option
+	}
+	return nil
+}
+
+// OpenAPIOptions represents OpenAPI protobuf options
+type OpenAPIOptions struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	File    []*OpenAPIFileOption    `protobuf:"bytes,1,rep,name=file,proto3" json:"file,omitempty"`
+	Method  []*OpenAPIMethodOption  `protobuf:"bytes,2,rep,name=method,proto3" json:"method,omitempty"`
+	Message []*OpenAPIMessageOption `protobuf:"bytes,3,rep,name=message,proto3" json:"message,omitempty"`
+	Service []*OpenAPIServiceOption `protobuf:"bytes,4,rep,name=service,proto3" json:"service,omitempty"`
+	Field   []*OpenAPIFieldOption   `protobuf:"bytes,5,rep,name=field,proto3" json:"field,omitempty"`
+}
+
+func (x *OpenAPIOptions) Reset() {
+	*x = OpenAPIOptions{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenAPIOptions) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenAPIOptions) ProtoMessage() {}
+
+func (x *OpenAPIOptions) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use OpenAPIOptions.ProtoReflect.Descriptor instead.
+func (*OpenAPIOptions) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *OpenAPIOptions) GetFile() []*OpenAPIFileOption {
+	if x != nil {
+		return x.File
+	}
+	return nil
+}
+
+func (x *OpenAPIOptions) GetMethod() []*OpenAPIMethodOption {
+	if x != nil {
+		return x.Method
+	}
+	return nil
+}
+
+func (x *OpenAPIOptions) GetMessage() []*OpenAPIMessageOption {
+	if x != nil {
+		return x.Message
+	}
+	return nil
+}
+
+func (x *OpenAPIOptions) GetService() []*OpenAPIServiceOption {
+	if x != nil {
+		return x.Service
+	}
+	return nil
+}
+
+func (x *OpenAPIOptions) GetField() []*OpenAPIFieldOption {
+	if x != nil {
+		return x.Field
+	}
+	return nil
+}
+
+// OpenAPIConfig represents a set of OpenAPI options
+type OpenAPIConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	OpenapiOptions *OpenAPIOptions `protobuf:"bytes,1,opt,name=openapi_options,json=openapiOptions,proto3" json:"openapi_options,omitempty"`
+}
+
+func (x *OpenAPIConfig) Reset() {
+	*x = OpenAPIConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenAPIConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenAPIConfig) ProtoMessage() {}
+
+func (x *OpenAPIConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use OpenAPIConfig.ProtoReflect.Descriptor instead.
+func (*OpenAPIConfig) Descriptor() ([]byte, []int) {
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *OpenAPIConfig) GetOpenapiOptions() *OpenAPIOptions {
+	if x != nil {
+		return x.OpenapiOptions
+	}
+	return nil
+}
+
+var File_internal_descriptor_openapiconfig_openapiconfig_proto protoreflect.FileDescriptor
+
+var file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDesc = []byte{
+	0x0a, 0x35, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72,
+	0x69, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61,
+	0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64,
+	0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70,
+	0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d,
+	0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x73, 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49,
+	0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69,
+	0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x4a,
+	0x0a, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32,
+	0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x63, 0x5f, 0x67, 0x65, 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69,
+	0x76, 0x32, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x77, 0x61, 0x67, 0x67,
+	0x65, 0x72, 0x52, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7b, 0x0a, 0x13, 0x4f, 0x70,
+	0x65, 0x6e, 0x41, 0x50, 0x49, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x4c, 0x0a, 0x06, 0x6f, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x67, 0x72, 0x70, 0x63,
+	0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x5f,
+	0x67, 0x65, 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2e, 0x6f, 0x70,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
+	0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7b, 0x0a, 0x14, 0x4f, 0x70, 0x65, 0x6e, 0x41,
+	0x50, 0x49, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,
+	0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x06, 0x6f, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x67, 0x72, 0x70, 0x63,
+	0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x5f,
+	0x67, 0x65, 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2e, 0x6f, 0x70,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x06, 0x6f, 0x70,
+	0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, 0x14, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x53,
+	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
+	0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73,
+	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x46, 0x0a, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61,
+	0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x5f, 0x67, 0x65, 0x6e,
+	0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x79,
+	0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70,
+	0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x4d, 0x0a, 0x06, 0x6f, 0x70,
+	0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x67, 0x72, 0x70,
+	0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
+	0x5f, 0x67, 0x65, 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2e, 0x6f,
+	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d,
+	0x61, 0x52, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xde, 0x03, 0x0a, 0x0e, 0x4f, 0x70,
+	0x65, 0x6e, 0x41, 0x50, 0x49, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x55, 0x0a, 0x04,
+	0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x67, 0x72, 0x70,
+	0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+	0x61, 0x6c, 0x2e, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x70,
+	0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4f, 0x70, 0x65, 0x6e,
+	0x41, 0x50, 0x49, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x66,
+	0x69, 0x6c, 0x65, 0x12, 0x5b, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77,
+	0x61, 0x79, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x73, 0x63,
+	0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x4d, 0x65, 0x74, 0x68,
+	0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
+	0x12, 0x5e, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x44, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
+	0x70, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+	0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x12, 0x5e, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x44, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
+	0x70, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+	0x12, 0x58, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x42, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x69,
+	0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x6f, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x78, 0x0a, 0x0d, 0x4f, 0x70,
+	0x65, 0x6e, 0x41, 0x50, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x67, 0x0a, 0x0f, 0x6f,
+	0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65,
+	0x77, 0x61, 0x79, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x73,
+	0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x4f, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x4f, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+	0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, 0x65,
+	0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2f, 0x76,
+	0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72,
+	0x69, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescOnce sync.Once
+	file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescData = file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDesc
+)
+
+func file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescGZIP() []byte {
+	file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescOnce.Do(func() {
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescData)
+	})
+	return file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDescData
+}
+
+var file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+var file_internal_descriptor_openapiconfig_openapiconfig_proto_goTypes = []interface{}{
+	(*OpenAPIFileOption)(nil),    // 0: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIFileOption
+	(*OpenAPIMethodOption)(nil),  // 1: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIMethodOption
+	(*OpenAPIMessageOption)(nil), // 2: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIMessageOption
+	(*OpenAPIServiceOption)(nil), // 3: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIServiceOption
+	(*OpenAPIFieldOption)(nil),   // 4: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIFieldOption
+	(*OpenAPIOptions)(nil),       // 5: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIOptions
+	(*OpenAPIConfig)(nil),        // 6: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIConfig
+	(*options.Swagger)(nil),      // 7: grpc.gateway.protoc_gen_openapiv2.options.Swagger
+	(*options.Operation)(nil),    // 8: grpc.gateway.protoc_gen_openapiv2.options.Operation
+	(*options.Schema)(nil),       // 9: grpc.gateway.protoc_gen_openapiv2.options.Schema
+	(*options.Tag)(nil),          // 10: grpc.gateway.protoc_gen_openapiv2.options.Tag
+	(*options.JSONSchema)(nil),   // 11: grpc.gateway.protoc_gen_openapiv2.options.JSONSchema
+}
+var file_internal_descriptor_openapiconfig_openapiconfig_proto_depIdxs = []int32{
+	7,  // 0: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIFileOption.option:type_name -> grpc.gateway.protoc_gen_openapiv2.options.Swagger
+	8,  // 1: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIMethodOption.option:type_name -> grpc.gateway.protoc_gen_openapiv2.options.Operation
+	9,  // 2: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIMessageOption.option:type_name -> grpc.gateway.protoc_gen_openapiv2.options.Schema
+	10, // 3: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIServiceOption.option:type_name -> grpc.gateway.protoc_gen_openapiv2.options.Tag
+	11, // 4: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIFieldOption.option:type_name -> grpc.gateway.protoc_gen_openapiv2.options.JSONSchema
+	0,  // 5: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIOptions.file:type_name -> grpc.gateway.internal.descriptor.openapiconfig.OpenAPIFileOption
+	1,  // 6: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIOptions.method:type_name -> grpc.gateway.internal.descriptor.openapiconfig.OpenAPIMethodOption
+	2,  // 7: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIOptions.message:type_name -> grpc.gateway.internal.descriptor.openapiconfig.OpenAPIMessageOption
+	3,  // 8: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIOptions.service:type_name -> grpc.gateway.internal.descriptor.openapiconfig.OpenAPIServiceOption
+	4,  // 9: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIOptions.field:type_name -> grpc.gateway.internal.descriptor.openapiconfig.OpenAPIFieldOption
+	5,  // 10: grpc.gateway.internal.descriptor.openapiconfig.OpenAPIConfig.openapi_options:type_name -> grpc.gateway.internal.descriptor.openapiconfig.OpenAPIOptions
+	11, // [11:11] is the sub-list for method output_type
+	11, // [11:11] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
+}
+
+func init() { file_internal_descriptor_openapiconfig_openapiconfig_proto_init() }
+func file_internal_descriptor_openapiconfig_openapiconfig_proto_init() {
+	if File_internal_descriptor_openapiconfig_openapiconfig_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenAPIFileOption); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenAPIMethodOption); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenAPIMessageOption); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenAPIServiceOption); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenAPIFieldOption); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenAPIOptions); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenAPIConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   7,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_internal_descriptor_openapiconfig_openapiconfig_proto_goTypes,
+		DependencyIndexes: file_internal_descriptor_openapiconfig_openapiconfig_proto_depIdxs,
+		MessageInfos:      file_internal_descriptor_openapiconfig_openapiconfig_proto_msgTypes,
+	}.Build()
+	File_internal_descriptor_openapiconfig_openapiconfig_proto = out.File
+	file_internal_descriptor_openapiconfig_openapiconfig_proto_rawDesc = nil
+	file_internal_descriptor_openapiconfig_openapiconfig_proto_goTypes = nil
+	file_internal_descriptor_openapiconfig_openapiconfig_proto_depIdxs = nil
+}

+ 51 - 0
protoc-gen-openapiv2/internal/descriptor/openapiconfig/openapiconfig.proto

@@ -0,0 +1,51 @@
+syntax = "proto3";
+
+package grpc.gateway.internal.descriptor.openapiconfig;
+
+option go_package = "github.com/go-kratos/grpc-gateway/v2/internal/descriptor/openapiconfig";
+
+import "protoc-gen-openapiv2/options/openapiv2.proto";
+
+// OpenAPIFileOption represents OpenAPI options on a file
+message OpenAPIFileOption {
+    string file = 1;
+    grpc.gateway.protoc_gen_openapiv2.options.Swagger option = 2;
+}
+
+// OpenAPIMethodOption represents OpenAPI options on a method
+message OpenAPIMethodOption {
+    string method = 1;
+    grpc.gateway.protoc_gen_openapiv2.options.Operation option = 2;
+}
+
+// OpenAPIMessageOption represents OpenAPI options on a message
+message OpenAPIMessageOption {
+    string message = 1;
+    grpc.gateway.protoc_gen_openapiv2.options.Schema option = 2;
+}
+
+// OpenAPIServiceOption represents OpenAPI options on a service
+message OpenAPIServiceOption {
+    string service = 1; // ex: Service
+    grpc.gateway.protoc_gen_openapiv2.options.Tag option = 2;
+}
+
+// OpenAPIFieldOption represents OpenAPI options on a field
+message OpenAPIFieldOption {
+    string field = 1;
+    grpc.gateway.protoc_gen_openapiv2.options.JSONSchema option = 2;
+}
+
+// OpenAPIOptions represents OpenAPI protobuf options
+message OpenAPIOptions {
+    repeated OpenAPIFileOption file = 1;
+    repeated OpenAPIMethodOption method = 2;
+    repeated OpenAPIMessageOption message = 3;
+    repeated OpenAPIServiceOption service = 4;
+    repeated OpenAPIFieldOption field = 5;
+}
+
+// OpenAPIConfig represents a set of OpenAPI options
+message OpenAPIConfig {
+    OpenAPIOptions openapi_options = 1;
+}

+ 46 - 0
protoc-gen-openapiv2/internal/descriptor/openapiconfig/openapiconfig.swagger.json

@@ -0,0 +1,46 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "internal/descriptor/openapiconfig/openapiconfig.proto",
+    "version": "version not set"
+  },
+  "consumes": [
+    "application/json"
+  ],
+  "produces": [
+    "application/json"
+  ],
+  "paths": {},
+  "definitions": {
+    "protobufAny": {
+      "type": "object",
+      "properties": {
+        "typeUrl": {
+          "type": "string"
+        },
+        "value": {
+          "type": "string",
+          "format": "byte"
+        }
+      }
+    },
+    "rpcStatus": {
+      "type": "object",
+      "properties": {
+        "code": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "message": {
+          "type": "string"
+        },
+        "details": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/protobufAny"
+          }
+        }
+      }
+    }
+  }
+}

+ 702 - 0
protoc-gen-openapiv2/internal/descriptor/registry.go

@@ -0,0 +1,702 @@
+package descriptor
+
+import (
+	"fmt"
+	"strings"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/codegenerator"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor/openapiconfig"
+	"github.com/golang/glog"
+	"github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
+	"google.golang.org/genproto/googleapis/api/annotations"
+	"google.golang.org/protobuf/compiler/protogen"
+	"google.golang.org/protobuf/types/descriptorpb"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+// Registry is a registry of information extracted from pluginpb.CodeGeneratorRequest.
+type Registry struct {
+	// msgs is a mapping from fully-qualified message name to descriptor
+	msgs map[string]*Message
+
+	// enums is a mapping from fully-qualified enum name to descriptor
+	enums map[string]*Enum
+
+	// files is a mapping from file path to descriptor
+	files map[string]*File
+
+	// prefix is a prefix to be inserted to golang package paths generated from proto package names.
+	prefix string
+
+	// pkgMap is a user-specified mapping from file path to proto package.
+	pkgMap map[string]string
+
+	// pkgAliases is a mapping from package aliases to package paths in go which are already taken.
+	pkgAliases map[string]string
+
+	// allowDeleteBody permits http delete methods to have a body
+	allowDeleteBody bool
+
+	// externalHttpRules is a mapping from fully qualified service method names to additional HttpRules applicable besides the ones found in annotations.
+	externalHTTPRules map[string][]*annotations.HttpRule
+
+	// allowMerge generation one OpenAPI file out of multiple protos
+	allowMerge bool
+
+	// mergeFileName target OpenAPI file name after merge
+	mergeFileName string
+
+	// allowRepeatedFieldsInBody permits repeated field in body field path of `google.api.http` annotation option
+	allowRepeatedFieldsInBody bool
+
+	// includePackageInTags controls whether the package name defined in the `package` directive
+	// in the proto file can be prepended to the gRPC service name in the `Tags` field of every operation.
+	includePackageInTags bool
+
+	// repeatedPathParamSeparator specifies how path parameter repeated fields are separated
+	repeatedPathParamSeparator repeatedFieldSeparator
+
+	// useJSONNamesForFields if true json tag name is used for generating fields in OpenAPI definitions,
+	// otherwise the original proto name is used. It's helpful for synchronizing the OpenAPI definition
+	// with gRPC-Gateway response, if it uses json tags for marshaling.
+	useJSONNamesForFields bool
+
+	// useFQNForOpenAPIName if true OpenAPI names will use the full qualified name (FQN) from proto definition,
+	// and generate a dot-separated OpenAPI name concatenating all elements from the proto FQN.
+	// If false, the default behavior is to concat the last 2 elements of the FQN if they are unique, otherwise concat
+	// all the elements of the FQN without any separator
+	useFQNForOpenAPIName bool
+
+	// useGoTemplate determines whether you want to use GO templates
+	// in your protofile comments
+	useGoTemplate bool
+
+	// enumsAsInts render enum as integer, as opposed to string
+	enumsAsInts bool
+
+	// disableDefaultErrors disables the generation of the default error types.
+	// This is useful for users who have defined custom error handling.
+	disableDefaultErrors bool
+
+	// simpleOperationIDs removes the service prefix from the generated
+	// operationIDs. This risks generating duplicate operationIDs.
+	simpleOperationIDs bool
+
+	standalone bool
+	// warnOnUnboundMethods causes the registry to emit warning logs if an RPC method
+	// has no HttpRule annotation.
+	warnOnUnboundMethods bool
+
+	// proto3OptionalNullable specifies whether Proto3 Optional fields should be marked as x-nullable.
+	proto3OptionalNullable bool
+
+	// fileOptions is a mapping of file name to additional OpenAPI file options
+	fileOptions map[string]*options.Swagger
+
+	// methodOptions is a mapping of fully-qualified method name to additional OpenAPI method options
+	methodOptions map[string]*options.Operation
+
+	// messageOptions is a mapping of fully-qualified message name to additional OpenAPI message options
+	messageOptions map[string]*options.Schema
+
+	//serviceOptions is a mapping of fully-qualified service name to additional OpenAPI service options
+	serviceOptions map[string]*options.Tag
+
+	// fieldOptions is a mapping of the fully-qualified name of the parent message concat
+	// field name and a period to additional OpenAPI field options
+	fieldOptions map[string]*options.JSONSchema
+
+	// generateUnboundMethods causes the registry to generate proxy methods even for
+	// RPC methods that have no HttpRule annotation.
+	generateUnboundMethods bool
+
+	generateRPCMethods bool
+
+	// omitPackageDoc, if false, causes a package comment to be included in the generated code.
+	omitPackageDoc bool
+
+	// recursiveDepth sets the maximum depth of a field parameter
+	recursiveDepth int
+}
+
+type repeatedFieldSeparator struct {
+	name string
+	sep  rune
+}
+
+// NewRegistry returns a new Registry.
+func NewRegistry() *Registry {
+	return &Registry{
+		msgs:              make(map[string]*Message),
+		enums:             make(map[string]*Enum),
+		files:             make(map[string]*File),
+		pkgMap:            make(map[string]string),
+		pkgAliases:        make(map[string]string),
+		externalHTTPRules: make(map[string][]*annotations.HttpRule),
+		repeatedPathParamSeparator: repeatedFieldSeparator{
+			name: "csv",
+			sep:  ',',
+		},
+		fileOptions:    make(map[string]*options.Swagger),
+		methodOptions:  make(map[string]*options.Operation),
+		messageOptions: make(map[string]*options.Schema),
+		serviceOptions: make(map[string]*options.Tag),
+		fieldOptions:   make(map[string]*options.JSONSchema),
+		recursiveDepth: 1000,
+	}
+}
+
+// Load loads definitions of services, methods, messages, enumerations and fields from "req".
+func (r *Registry) Load(req *pluginpb.CodeGeneratorRequest) error {
+	var profofiles []*descriptorpb.FileDescriptorProto
+	registry := map[string]struct{}{}
+	for _, f := range req.ProtoFile {
+		if _, ok := registry[f.GetName()]; !ok {
+			registry[f.GetName()] = struct{}{}
+			profofiles = append(profofiles, f)
+		}
+	}
+	req.ProtoFile = profofiles
+	gen, err := protogen.Options{}.New(req)
+	if err != nil {
+		fmt.Println("New err:", err)
+		return err
+	}
+	// Note: keep in mind that this might be not enough because
+	// protogen.Plugin is used only to load files here.
+	// The support for features must be set on the pluginpb.CodeGeneratorResponse.
+	codegenerator.SetSupportedFeaturesOnPluginGen(gen)
+	return r.load(gen)
+}
+
+func (r *Registry) LoadFromPlugin(gen *protogen.Plugin) error {
+	return r.load(gen)
+}
+
+func (r *Registry) load(gen *protogen.Plugin) error {
+	for filePath, f := range gen.FilesByPath {
+		r.loadFile(filePath, f)
+	}
+
+	for filePath, f := range gen.FilesByPath {
+		if !f.Generate {
+			continue
+		}
+		file := r.files[filePath]
+		if err := r.loadServices(file); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// loadFile loads messages, enumerations and fields from "file".
+// It does not loads services and methods in "file".  You need to call
+// loadServices after loadFiles is called for all files to load services and methods.
+func (r *Registry) loadFile(filePath string, file *protogen.File) {
+	pkg := GoPackage{
+		Path: string(file.GoImportPath),
+		Name: string(file.GoPackageName),
+	}
+	if r.standalone {
+		pkg.Alias = "ext" + strings.Title(pkg.Name)
+	}
+
+	if err := r.ReserveGoPackageAlias(pkg.Name, pkg.Path); err != nil {
+		for i := 0; ; i++ {
+			alias := fmt.Sprintf("%s_%d", pkg.Name, i)
+			if err := r.ReserveGoPackageAlias(alias, pkg.Path); err == nil {
+				pkg.Alias = alias
+				break
+			}
+		}
+	}
+	f := &File{
+		FileDescriptorProto:     file.Proto,
+		GoPkg:                   pkg,
+		GeneratedFilenamePrefix: file.GeneratedFilenamePrefix,
+	}
+
+	r.files[filePath] = f
+	r.registerMsg(f, nil, file.Proto.MessageType)
+	r.registerEnum(f, nil, file.Proto.EnumType)
+}
+
+func (r *Registry) registerMsg(file *File, outerPath []string, msgs []*descriptorpb.DescriptorProto) {
+	for i, md := range msgs {
+		m := &Message{
+			File:              file,
+			Outers:            outerPath,
+			DescriptorProto:   md,
+			Index:             i,
+			ForcePrefixedName: r.standalone,
+		}
+		for _, fd := range md.GetField() {
+			m.Fields = append(m.Fields, &Field{
+				Message:              m,
+				FieldDescriptorProto: fd,
+				ForcePrefixedName:    r.standalone,
+			})
+		}
+		file.Messages = append(file.Messages, m)
+		r.msgs[m.FQMN()] = m
+		glog.V(1).Infof("register name: %s", m.FQMN())
+
+		var outers []string
+		outers = append(outers, outerPath...)
+		outers = append(outers, m.GetName())
+		r.registerMsg(file, outers, m.GetNestedType())
+		r.registerEnum(file, outers, m.GetEnumType())
+	}
+}
+
+func (r *Registry) registerEnum(file *File, outerPath []string, enums []*descriptorpb.EnumDescriptorProto) {
+	for i, ed := range enums {
+		e := &Enum{
+			File:                file,
+			Outers:              outerPath,
+			EnumDescriptorProto: ed,
+			Index:               i,
+			ForcePrefixedName:   r.standalone,
+		}
+		file.Enums = append(file.Enums, e)
+		r.enums[e.FQEN()] = e
+		glog.V(1).Infof("register enum name: %s", e.FQEN())
+	}
+}
+
+// LookupMsg looks up a message type by "name".
+// It tries to resolve "name" from "location" if "name" is a relative message name.
+func (r *Registry) LookupMsg(location, name string) (*Message, error) {
+	glog.V(1).Infof("lookup %s from %s", name, location)
+	if strings.HasPrefix(name, ".") {
+		m, ok := r.msgs[name]
+		if !ok {
+			return nil, fmt.Errorf("no message found: %s", name)
+		}
+		return m, nil
+	}
+
+	if !strings.HasPrefix(location, ".") {
+		location = fmt.Sprintf(".%s", location)
+	}
+	components := strings.Split(location, ".")
+	for len(components) > 0 {
+		fqmn := strings.Join(append(components, name), ".")
+		if m, ok := r.msgs[fqmn]; ok {
+			return m, nil
+		}
+		components = components[:len(components)-1]
+	}
+	return nil, fmt.Errorf("no message found: %s", name)
+}
+
+// LookupEnum looks up a enum type by "name".
+// It tries to resolve "name" from "location" if "name" is a relative enum name.
+func (r *Registry) LookupEnum(location, name string) (*Enum, error) {
+	glog.V(1).Infof("lookup enum %s from %s", name, location)
+	if strings.HasPrefix(name, ".") {
+		e, ok := r.enums[name]
+		if !ok {
+			return nil, fmt.Errorf("no enum found: %s", name)
+		}
+		return e, nil
+	}
+
+	if !strings.HasPrefix(location, ".") {
+		location = fmt.Sprintf(".%s", location)
+	}
+	components := strings.Split(location, ".")
+	for len(components) > 0 {
+		fqen := strings.Join(append(components, name), ".")
+		if e, ok := r.enums[fqen]; ok {
+			return e, nil
+		}
+		components = components[:len(components)-1]
+	}
+	return nil, fmt.Errorf("no enum found: %s", name)
+}
+
+// LookupFile looks up a file by name.
+func (r *Registry) LookupFile(name string) (*File, error) {
+	f, ok := r.files[name]
+	if !ok {
+		return nil, fmt.Errorf("no such file given: %s", name)
+	}
+	return f, nil
+}
+
+// LookupExternalHTTPRules looks up external http rules by fully qualified service method name
+func (r *Registry) LookupExternalHTTPRules(qualifiedMethodName string) []*annotations.HttpRule {
+	return r.externalHTTPRules[qualifiedMethodName]
+}
+
+// AddExternalHTTPRule adds an external http rule for the given fully qualified service method name
+func (r *Registry) AddExternalHTTPRule(qualifiedMethodName string, rule *annotations.HttpRule) {
+	r.externalHTTPRules[qualifiedMethodName] = append(r.externalHTTPRules[qualifiedMethodName], rule)
+}
+
+// UnboundExternalHTTPRules returns the list of External HTTPRules
+// which does not have a matching method in the registry
+func (r *Registry) UnboundExternalHTTPRules() []string {
+	allServiceMethods := make(map[string]struct{})
+	for _, f := range r.files {
+		for _, s := range f.GetService() {
+			svc := &Service{File: f, ServiceDescriptorProto: s}
+			for _, m := range s.GetMethod() {
+				method := &Method{Service: svc, MethodDescriptorProto: m}
+				allServiceMethods[method.FQMN()] = struct{}{}
+			}
+		}
+	}
+
+	var missingMethods []string
+	for httpRuleMethod := range r.externalHTTPRules {
+		if _, ok := allServiceMethods[httpRuleMethod]; !ok {
+			missingMethods = append(missingMethods, httpRuleMethod)
+		}
+	}
+	return missingMethods
+}
+
+// AddPkgMap adds a mapping from a .proto file to proto package name.
+func (r *Registry) AddPkgMap(file, protoPkg string) {
+	r.pkgMap[file] = protoPkg
+}
+
+// SetPrefix registers the prefix to be added to go package paths generated from proto package names.
+func (r *Registry) SetPrefix(prefix string) {
+	r.prefix = prefix
+}
+
+// SetStandalone registers standalone flag to control package prefix
+func (r *Registry) SetStandalone(standalone bool) {
+	r.standalone = standalone
+}
+
+// SetRecursiveDepth records the max recursion count
+func (r *Registry) SetRecursiveDepth(count int) {
+	r.recursiveDepth = count
+}
+
+// GetRecursiveDepth returns the max recursion count
+func (r *Registry) GetRecursiveDepth() int {
+	return r.recursiveDepth
+}
+
+// ReserveGoPackageAlias reserves the unique alias of go package.
+// If succeeded, the alias will be never used for other packages in generated go files.
+// If failed, the alias is already taken by another package, so you need to use another
+// alias for the package in your go files.
+func (r *Registry) ReserveGoPackageAlias(alias, pkgpath string) error {
+	if taken, ok := r.pkgAliases[alias]; ok {
+		if taken == pkgpath {
+			return nil
+		}
+		return fmt.Errorf("package name %s is already taken. Use another alias", alias)
+	}
+	r.pkgAliases[alias] = pkgpath
+	return nil
+}
+
+// GetAllFQMNs returns a list of all FQMNs
+func (r *Registry) GetAllFQMNs() []string {
+	var keys []string
+	for k := range r.msgs {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
+// GetAllFQENs returns a list of all FQENs
+func (r *Registry) GetAllFQENs() []string {
+	var keys []string
+	for k := range r.enums {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
+// SetAllowDeleteBody controls whether http delete methods may have a
+// body or fail loading if encountered.
+func (r *Registry) SetAllowDeleteBody(allow bool) {
+	r.allowDeleteBody = allow
+}
+
+// SetAllowMerge controls whether generation one OpenAPI file out of multiple protos
+func (r *Registry) SetAllowMerge(allow bool) {
+	r.allowMerge = allow
+}
+
+// IsAllowMerge whether generation one OpenAPI file out of multiple protos
+func (r *Registry) IsAllowMerge() bool {
+	return r.allowMerge
+}
+
+// SetMergeFileName controls the target OpenAPI file name out of multiple protos
+func (r *Registry) SetMergeFileName(mergeFileName string) {
+	r.mergeFileName = mergeFileName
+}
+
+// SetAllowRepeatedFieldsInBody controls whether repeated field can be used
+// in `body` and `response_body` (`google.api.http` annotation option) field path or not
+func (r *Registry) SetAllowRepeatedFieldsInBody(allow bool) {
+	r.allowRepeatedFieldsInBody = allow
+}
+
+// IsAllowRepeatedFieldsInBody checks if repeated field can be used
+// in `body` and `response_body` (`google.api.http` annotation option) field path or not
+func (r *Registry) IsAllowRepeatedFieldsInBody() bool {
+	return r.allowRepeatedFieldsInBody
+}
+
+// SetIncludePackageInTags controls whether the package name defined in the `package` directive
+// in the proto file can be prepended to the gRPC service name in the `Tags` field of every operation.
+func (r *Registry) SetIncludePackageInTags(allow bool) {
+	r.includePackageInTags = allow
+}
+
+// IsIncludePackageInTags checks whether the package name defined in the `package` directive
+// in the proto file can be prepended to the gRPC service name in the `Tags` field of every operation.
+func (r *Registry) IsIncludePackageInTags() bool {
+	return r.includePackageInTags
+}
+
+// GetRepeatedPathParamSeparator returns a rune spcifying how
+// path parameter repeated fields are separated.
+func (r *Registry) GetRepeatedPathParamSeparator() rune {
+	return r.repeatedPathParamSeparator.sep
+}
+
+// GetRepeatedPathParamSeparatorName returns the name path parameter repeated
+// fields repeatedFieldSeparator. I.e. 'csv', 'pipe', 'ssv' or 'tsv'
+func (r *Registry) GetRepeatedPathParamSeparatorName() string {
+	return r.repeatedPathParamSeparator.name
+}
+
+// SetRepeatedPathParamSeparator sets how path parameter repeated fields are
+// separated. Allowed names are 'csv', 'pipe', 'ssv' and 'tsv'.
+func (r *Registry) SetRepeatedPathParamSeparator(name string) error {
+	var sep rune
+	switch name {
+	case "csv":
+		sep = ','
+	case "pipes":
+		sep = '|'
+	case "ssv":
+		sep = ' '
+	case "tsv":
+		sep = '\t'
+	default:
+		return fmt.Errorf("unknown repeated path parameter separator: %s", name)
+	}
+	r.repeatedPathParamSeparator = repeatedFieldSeparator{
+		name: name,
+		sep:  sep,
+	}
+	return nil
+}
+
+// SetUseJSONNamesForFields sets useJSONNamesForFields
+func (r *Registry) SetUseJSONNamesForFields(use bool) {
+	r.useJSONNamesForFields = use
+}
+
+// GetUseJSONNamesForFields returns useJSONNamesForFields
+func (r *Registry) GetUseJSONNamesForFields() bool {
+	return r.useJSONNamesForFields
+}
+
+// SetUseFQNForOpenAPIName sets useFQNForOpenAPIName
+func (r *Registry) SetUseFQNForOpenAPIName(use bool) {
+	r.useFQNForOpenAPIName = use
+}
+
+// GetUseFQNForOpenAPIName returns useFQNForOpenAPIName
+func (r *Registry) GetUseFQNForOpenAPIName() bool {
+	return r.useFQNForOpenAPIName
+}
+
+// GetMergeFileName return the target merge OpenAPI file name
+func (r *Registry) GetMergeFileName() string {
+	return r.mergeFileName
+}
+
+// SetUseGoTemplate sets useGoTemplate
+func (r *Registry) SetUseGoTemplate(use bool) {
+	r.useGoTemplate = use
+}
+
+// GetUseGoTemplate returns useGoTemplate
+func (r *Registry) GetUseGoTemplate() bool {
+	return r.useGoTemplate
+}
+
+// SetEnumsAsInts set enumsAsInts
+func (r *Registry) SetEnumsAsInts(enumsAsInts bool) {
+	r.enumsAsInts = enumsAsInts
+}
+
+// GetEnumsAsInts returns enumsAsInts
+func (r *Registry) GetEnumsAsInts() bool {
+	return r.enumsAsInts
+}
+
+// SetDisableDefaultErrors sets disableDefaultErrors
+func (r *Registry) SetDisableDefaultErrors(use bool) {
+	r.disableDefaultErrors = use
+}
+
+// GetDisableDefaultErrors returns disableDefaultErrors
+func (r *Registry) GetDisableDefaultErrors() bool {
+	return r.disableDefaultErrors
+}
+
+// SetSimpleOperationIDs sets simpleOperationIDs
+func (r *Registry) SetSimpleOperationIDs(use bool) {
+	r.simpleOperationIDs = use
+}
+
+// GetSimpleOperationIDs returns simpleOperationIDs
+func (r *Registry) GetSimpleOperationIDs() bool {
+	return r.simpleOperationIDs
+}
+
+// SetWarnOnUnboundMethods sets warnOnUnboundMethods
+func (r *Registry) SetWarnOnUnboundMethods(warn bool) {
+	r.warnOnUnboundMethods = warn
+}
+
+// SetGenerateUnboundMethods sets generateUnboundMethods
+func (r *Registry) SetGenerateUnboundMethods(generate bool) {
+	r.generateUnboundMethods = generate
+}
+
+// SetGenerateRPCMethods sets generateRPCMethods
+func (r *Registry) SetGenerateRPCMethods(rpc bool) {
+	r.generateRPCMethods = rpc
+}
+
+// SetOmitPackageDoc controls whether the generated code contains a package comment (if set to false, it will contain one)
+func (r *Registry) SetOmitPackageDoc(omit bool) {
+	r.omitPackageDoc = omit
+}
+
+// GetOmitPackageDoc returns whether a package comment will be omitted from the generated code
+func (r *Registry) GetOmitPackageDoc() bool {
+	return r.omitPackageDoc
+}
+
+// SetProto3OptionalNullable set proto3OtionalNullable
+func (r *Registry) SetProto3OptionalNullable(proto3OtionalNullable bool) {
+	r.proto3OptionalNullable = proto3OtionalNullable
+}
+
+// GetProto3OptionalNullable returns proto3OtionalNullable
+func (r *Registry) GetProto3OptionalNullable() bool {
+	return r.proto3OptionalNullable
+}
+
+// RegisterOpenAPIOptions registers OpenAPI options
+func (r *Registry) RegisterOpenAPIOptions(opts *openapiconfig.OpenAPIOptions) error {
+	if opts == nil {
+		return nil
+	}
+
+	for _, opt := range opts.File {
+		if _, ok := r.files[opt.File]; !ok {
+			return fmt.Errorf("no file %s found", opt.File)
+		}
+		r.fileOptions[opt.File] = opt.Option
+	}
+
+	// build map of all registered methods
+	methods := make(map[string]struct{})
+	services := make(map[string]struct{})
+	for _, f := range r.files {
+		for _, s := range f.Services {
+			services[s.FQSN()] = struct{}{}
+			for _, m := range s.Methods {
+				methods[m.FQMN()] = struct{}{}
+			}
+		}
+	}
+
+	for _, opt := range opts.Method {
+		qualifiedMethod := "." + opt.Method
+		if _, ok := methods[qualifiedMethod]; !ok {
+			return fmt.Errorf("no method %s found", opt.Method)
+		}
+		r.methodOptions[qualifiedMethod] = opt.Option
+	}
+
+	for _, opt := range opts.Message {
+		qualifiedMessage := "." + opt.Message
+		if _, ok := r.msgs[qualifiedMessage]; !ok {
+			return fmt.Errorf("no message %s found", opt.Message)
+		}
+		r.messageOptions[qualifiedMessage] = opt.Option
+	}
+
+	for _, opt := range opts.Service {
+		qualifiedService := "." + opt.Service
+		if _, ok := services[qualifiedService]; !ok {
+			return fmt.Errorf("no service %s found", opt.Service)
+		}
+		r.serviceOptions[qualifiedService] = opt.Option
+	}
+
+	// build map of all registered fields
+	fields := make(map[string]struct{})
+	for _, m := range r.msgs {
+		for _, f := range m.Fields {
+			fields[f.FQFN()] = struct{}{}
+		}
+	}
+	for _, opt := range opts.Field {
+		qualifiedField := "." + opt.Field
+		if _, ok := fields[qualifiedField]; !ok {
+			return fmt.Errorf("no field %s found", opt.Field)
+		}
+		r.fieldOptions[qualifiedField] = opt.Option
+	}
+	return nil
+}
+
+// GetOpenAPIFileOption returns a registered OpenAPI option for a file
+func (r *Registry) GetOpenAPIFileOption(file string) (*options.Swagger, bool) {
+	opt, ok := r.fileOptions[file]
+	return opt, ok
+}
+
+// GetOpenAPIMethodOption returns a registered OpenAPI option for a method
+func (r *Registry) GetOpenAPIMethodOption(qualifiedMethod string) (*options.Operation, bool) {
+	opt, ok := r.methodOptions[qualifiedMethod]
+	return opt, ok
+}
+
+// GetOpenAPIMessageOption returns a registered OpenAPI option for a message
+func (r *Registry) GetOpenAPIMessageOption(qualifiedMessage string) (*options.Schema, bool) {
+	opt, ok := r.messageOptions[qualifiedMessage]
+	return opt, ok
+}
+
+// GetOpenAPIServiceOption returns a registered OpenAPI option for a service
+func (r *Registry) GetOpenAPIServiceOption(qualifiedService string) (*options.Tag, bool) {
+	opt, ok := r.serviceOptions[qualifiedService]
+	return opt, ok
+}
+
+// GetOpenAPIFieldOption returns a registered OpenAPI option for a field
+func (r *Registry) GetOpenAPIFieldOption(qualifiedField string) (*options.JSONSchema, bool) {
+	opt, ok := r.fieldOptions[qualifiedField]
+	return opt, ok
+}
+
+func (r *Registry) FieldName(f *Field) string {
+	if r.useJSONNamesForFields {
+		return f.GetJsonName()
+	}
+	return f.GetName()
+}

+ 718 - 0
protoc-gen-openapiv2/internal/descriptor/registry_test.go

@@ -0,0 +1,718 @@
+package descriptor
+
+import (
+	"testing"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor/openapiconfig"
+	"google.golang.org/protobuf/compiler/protogen"
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/descriptorpb"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+func newGeneratorFromSources(req *pluginpb.CodeGeneratorRequest, sources ...string) (*protogen.Plugin, error) {
+	for _, src := range sources {
+		var fd descriptorpb.FileDescriptorProto
+		if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+			return nil, err
+		}
+		req.FileToGenerate = append(req.FileToGenerate, fd.GetName())
+		req.ProtoFile = append(req.ProtoFile, &fd)
+	}
+	return protogen.Options{}.New(req)
+}
+
+func loadFileWithCodeGeneratorRequest(t *testing.T, reg *Registry, req *pluginpb.CodeGeneratorRequest, sources ...string) []*descriptorpb.FileDescriptorProto {
+	t.Helper()
+	plugin, err := newGeneratorFromSources(req, sources...)
+	if err != nil {
+		t.Fatalf("failed to create a generator: %v", err)
+	}
+	err = reg.LoadFromPlugin(plugin)
+	if err != nil {
+		t.Fatalf("failed to Registry.LoadFromPlugin(): %v", err)
+	}
+	return plugin.Request.ProtoFile
+}
+
+func loadFile(t *testing.T, reg *Registry, src string) *descriptorpb.FileDescriptorProto {
+	t.Helper()
+	fds := loadFileWithCodeGeneratorRequest(t, reg, &pluginpb.CodeGeneratorRequest{}, src)
+	return fds[0]
+}
+
+func TestLoadFile(t *testing.T) {
+	reg := NewRegistry()
+	fd := loadFile(t, reg, `
+		name: 'example.proto'
+		package: 'example'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+		message_type <
+			name: 'ExampleMessage'
+			field <
+				name: 'str'
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+				number: 1
+			>
+		>
+	`)
+
+	file := reg.files["example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example", Name: "example"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+
+	msg, err := reg.LookupMsg("", ".example.ExampleMessage")
+	if err != nil {
+		t.Errorf("reg.LookupMsg(%q, %q)) failed with %v; want success", "", ".example.ExampleMessage", err)
+		return
+	}
+	if got, want := msg.DescriptorProto, fd.MessageType[0]; got != want {
+		t.Errorf("reg.lookupMsg(%q, %q).DescriptorProto = %#v; want %#v", "", ".example.ExampleMessage", got, want)
+	}
+	if got, want := msg.File, file; got != want {
+		t.Errorf("msg.File = %v; want %v", got, want)
+	}
+	if got := msg.Outers; got != nil {
+		t.Errorf("msg.Outers = %v; want %v", got, nil)
+	}
+	if got, want := len(msg.Fields), 1; got != want {
+		t.Errorf("len(msg.Fields) = %d; want %d", got, want)
+	} else if got, want := msg.Fields[0].FieldDescriptorProto, fd.MessageType[0].Field[0]; got != want {
+		t.Errorf("msg.Fields[0].FieldDescriptorProto = %v; want %v", got, want)
+	} else if got, want := msg.Fields[0].Message, msg; got != want {
+		t.Errorf("msg.Fields[0].Message = %v; want %v", got, want)
+	}
+
+	if got, want := len(file.Messages), 1; got != want {
+		t.Errorf("file.Meeesages = %#v; want %#v", file.Messages, []*Message{msg})
+	}
+	if got, want := file.Messages[0], msg; got != want {
+		t.Errorf("file.Meeesages[0] = %v; want %v", got, want)
+	}
+}
+
+func TestLoadFileNestedPackage(t *testing.T) {
+	reg := NewRegistry()
+	loadFile(t, reg, `
+		name: 'example.proto'
+		package: 'example.nested.nested2'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example.nested.nested2' >
+	`)
+
+	file := reg.files["example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example.nested.nested2", Name: "example_nested_nested2"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+func TestLoadFileWithDir(t *testing.T) {
+	reg := NewRegistry()
+	loadFile(t, reg, `
+		name: 'path/to/example.proto'
+		package: 'example'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+	`)
+
+	file := reg.files["path/to/example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example", Name: "example"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+func TestLoadFileWithoutPackage(t *testing.T) {
+	reg := NewRegistry()
+	loadFile(t, reg, `
+		name: 'path/to/example_file.proto'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example_file' >
+	`)
+
+	file := reg.files["path/to/example_file.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example_file", Name: "example_file"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+func TestLoadFileWithMapping(t *testing.T) {
+	reg := NewRegistry()
+	loadFileWithCodeGeneratorRequest(t, reg, &pluginpb.CodeGeneratorRequest{
+		Parameter: proto.String("Mpath/to/example.proto=example.com/proj/example/proto"),
+	}, `
+		name: 'path/to/example.proto'
+		package: 'example'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+	`)
+
+	file := reg.files["path/to/example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "example.com/proj/example/proto", Name: "example"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+func TestLoadFileWithPackageNameCollision(t *testing.T) {
+	reg := NewRegistry()
+	loadFile(t, reg, `
+		name: 'path/to/another.proto'
+		package: 'example'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+	`)
+	loadFile(t, reg, `
+		name: 'path/to/example.proto'
+		package: 'example'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+	`)
+	if err := reg.ReserveGoPackageAlias("ioutil", "io/ioutil"); err != nil {
+		t.Fatalf("reg.ReserveGoPackageAlias(%q) failed with %v; want success", "ioutil", err)
+	}
+	loadFile(t, reg, `
+		name: 'path/to/ioutil.proto'
+		package: 'ioutil'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/ioutil' >
+	`)
+
+	file := reg.files["path/to/another.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "path/to/another.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example", Name: "example"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+
+	file = reg.files["path/to/example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "path/to/example.proto")
+		return
+	}
+	wantPkg = GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example", Name: "example", Alias: ""}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+
+	file = reg.files["path/to/ioutil.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "path/to/ioutil.proto")
+		return
+	}
+	wantPkg = GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway/runtime/internal/ioutil", Name: "ioutil", Alias: "ioutil_0"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+func TestLoadFileWithIdenticalGoPkg(t *testing.T) {
+	reg := NewRegistry()
+	loadFileWithCodeGeneratorRequest(t, reg, &pluginpb.CodeGeneratorRequest{
+		Parameter: proto.String("Mpath/to/another.proto=example.com/example,Mpath/to/example.proto=example.com/example"),
+	}, `
+		name: 'path/to/another.proto'
+		package: 'example'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+	`, `
+		name: 'path/to/example.proto'
+		package: 'example'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+	`)
+
+	file := reg.files["path/to/example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "example.com/example", Name: "example"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+
+	file = reg.files["path/to/another.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg = GoPackage{Path: "example.com/example", Name: "example"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+// TestLookupMsgWithoutPackage tests a case when there is no "package" directive.
+// In Go, it is required to have a generated package so we rely on
+// google.golang.org/protobuf/compiler/protogen to provide it.
+func TestLookupMsgWithoutPackage(t *testing.T) {
+	reg := NewRegistry()
+	fd := loadFile(t, reg, `
+		name: 'example.proto'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+		message_type <
+			name: 'ExampleMessage'
+			field <
+				name: 'str'
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+				number: 1
+			>
+		>
+	`)
+
+	msg, err := reg.LookupMsg("", ".ExampleMessage")
+	if err != nil {
+		t.Errorf("reg.LookupMsg(%q, %q)) failed with %v; want success", "", ".ExampleMessage", err)
+		return
+	}
+	if got, want := msg.DescriptorProto, fd.MessageType[0]; got != want {
+		t.Errorf("reg.lookupMsg(%q, %q).DescriptorProto = %#v; want %#v", "", ".ExampleMessage", got, want)
+	}
+}
+
+func TestLookupMsgWithNestedPackage(t *testing.T) {
+	reg := NewRegistry()
+	fd := loadFile(t, reg, `
+		name: 'example.proto'
+		package: 'nested.nested2.mypackage'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+		message_type <
+			name: 'ExampleMessage'
+			field <
+				name: 'str'
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+				number: 1
+			>
+		>
+	`)
+
+	for _, name := range []string{
+		"nested.nested2.mypackage.ExampleMessage",
+		"nested2.mypackage.ExampleMessage",
+		"mypackage.ExampleMessage",
+		"ExampleMessage",
+	} {
+		msg, err := reg.LookupMsg("nested.nested2.mypackage", name)
+		if err != nil {
+			t.Errorf("reg.LookupMsg(%q, %q)) failed with %v; want success", ".nested.nested2.mypackage", name, err)
+			return
+		}
+		if got, want := msg.DescriptorProto, fd.MessageType[0]; got != want {
+			t.Errorf("reg.lookupMsg(%q, %q).DescriptorProto = %#v; want %#v", ".nested.nested2.mypackage", name, got, want)
+		}
+	}
+
+	for _, loc := range []string{
+		".nested.nested2.mypackage",
+		"nested.nested2.mypackage",
+		".nested.nested2",
+		"nested.nested2",
+		".nested",
+		"nested",
+		".",
+		"",
+		"somewhere.else",
+	} {
+		name := "nested.nested2.mypackage.ExampleMessage"
+		msg, err := reg.LookupMsg(loc, name)
+		if err != nil {
+			t.Errorf("reg.LookupMsg(%q, %q)) failed with %v; want success", loc, name, err)
+			return
+		}
+		if got, want := msg.DescriptorProto, fd.MessageType[0]; got != want {
+			t.Errorf("reg.lookupMsg(%q, %q).DescriptorProto = %#v; want %#v", loc, name, got, want)
+		}
+	}
+
+	for _, loc := range []string{
+		".nested.nested2.mypackage",
+		"nested.nested2.mypackage",
+		".nested.nested2",
+		"nested.nested2",
+		".nested",
+		"nested",
+	} {
+		name := "nested2.mypackage.ExampleMessage"
+		msg, err := reg.LookupMsg(loc, name)
+		if err != nil {
+			t.Errorf("reg.LookupMsg(%q, %q)) failed with %v; want success", loc, name, err)
+			return
+		}
+		if got, want := msg.DescriptorProto, fd.MessageType[0]; got != want {
+			t.Errorf("reg.lookupMsg(%q, %q).DescriptorProto = %#v; want %#v", loc, name, got, want)
+		}
+	}
+}
+
+func TestLoadWithInconsistentTargetPackage(t *testing.T) {
+	for _, spec := range []struct {
+		req        string
+		consistent bool
+	}{
+		// root package, explicit go package
+		{
+			req: `
+				file_to_generate: 'a.proto'
+				file_to_generate: 'b.proto'
+				proto_file <
+					name: 'a.proto'
+					options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example.foo' >
+					message_type < name: 'A' >
+					service <
+						name: "AService"
+						method <
+							name: "Meth"
+							input_type: "A"
+							output_type: "A"
+							options <
+								[google.api.http] < post: "/v1/a" body: "*" >
+							>
+						>
+					>
+				>
+				proto_file <
+					name: 'b.proto'
+					options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example.foo' >
+					message_type < name: 'B' >
+					service <
+						name: "BService"
+						method <
+							name: "Meth"
+							input_type: "B"
+							output_type: "B"
+							options <
+								[google.api.http] < post: "/v1/b" body: "*" >
+							>
+						>
+					>
+				>
+			`,
+			consistent: true,
+		},
+		// named package, explicit go package
+		{
+			req: `
+				file_to_generate: 'a.proto'
+				file_to_generate: 'b.proto'
+				proto_file <
+					name: 'a.proto'
+					package: 'example.foo'
+					options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example.foo' >
+					message_type < name: 'A' >
+					service <
+						name: "AService"
+						method <
+							name: "Meth"
+							input_type: "A"
+							output_type: "A"
+							options <
+								[google.api.http] < post: "/v1/a" body: "*" >
+							>
+						>
+					>
+				>
+				proto_file <
+					name: 'b.proto'
+					package: 'example.foo'
+					options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example.foo' >
+					message_type < name: 'B' >
+					service <
+						name: "BService"
+						method <
+							name: "Meth"
+							input_type: "B"
+							output_type: "B"
+							options <
+								[google.api.http] < post: "/v1/b" body: "*" >
+							>
+						>
+					>
+				>
+			`,
+			consistent: true,
+		},
+	} {
+		var req pluginpb.CodeGeneratorRequest
+		if err := prototext.Unmarshal([]byte(spec.req), &req); err != nil {
+			t.Fatalf("proto.UnmarshalText(%s, &file) failed with %v; want success", spec.req, err)
+		}
+		_, err := newGeneratorFromSources(&req)
+		if got, want := err == nil, spec.consistent; got != want {
+			if want {
+				t.Errorf("reg.Load(%s) failed with %v; want success", spec.req, err)
+				continue
+			}
+			t.Errorf("reg.Load(%s) succeeded; want an package inconsistency error", spec.req)
+		}
+	}
+}
+
+func TestLoadOverriddenPackageName(t *testing.T) {
+	reg := NewRegistry()
+	loadFile(t, reg, `
+		name: 'example.proto'
+		package: 'example'
+		options < go_package: 'example.com/xyz;pb' >
+	`)
+	file := reg.files["example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "example.com/xyz", Name: "pb"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+func TestLoadWithStandalone(t *testing.T) {
+	reg := NewRegistry()
+	reg.SetStandalone(true)
+	loadFile(t, reg, `
+		name: 'example.proto'
+		package: 'example'
+		options < go_package: 'example.com/xyz;pb' >
+	`)
+	file := reg.files["example.proto"]
+	if file == nil {
+		t.Errorf("reg.files[%q] = nil; want non-nil", "example.proto")
+		return
+	}
+	wantPkg := GoPackage{Path: "example.com/xyz", Name: "pb", Alias: "extPb"}
+	if got, want := file.GoPkg, wantPkg; got != want {
+		t.Errorf("file.GoPkg = %#v; want %#v", got, want)
+	}
+}
+
+func TestUnboundExternalHTTPRules(t *testing.T) {
+	reg := NewRegistry()
+	methodName := ".example.ExampleService.Echo"
+	reg.AddExternalHTTPRule(methodName, nil)
+	assertStringSlice(t, "unbound external HTTP rules", reg.UnboundExternalHTTPRules(), []string{methodName})
+	loadFile(t, reg, `
+		name: "path/to/example.proto",
+		package: "example"
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+		message_type <
+			name: "StringMessage"
+			field <
+				name: "string"
+				number: 1
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "StringMessage"
+				output_type: "StringMessage"
+			>
+		>
+	`)
+	assertStringSlice(t, "unbound external HTTP rules", reg.UnboundExternalHTTPRules(), []string{})
+}
+
+func TestRegisterOpenAPIOptions(t *testing.T) {
+	codeReqText := `file_to_generate: 'a.proto'
+	proto_file <
+		name: 'a.proto'
+		package: 'example.foo'
+		options < go_package: 'github.com/grpc-ecosystem/grpc-gateway/runtime/internal/example' >
+		message_type <
+			name: 'ExampleMessage'
+			field <
+				name: 'str'
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+				number: 1
+			>
+		>
+		service <
+			name: "AService"
+			method <
+				name: "Meth"
+				input_type: "ExampleMessage"
+				output_type: "ExampleMessage"
+				options <
+					[google.api.http] < post: "/v1/a" body: "*" >
+				>
+			>
+		>
+	>
+	`
+	var codeReq pluginpb.CodeGeneratorRequest
+	if err := prototext.Unmarshal([]byte(codeReqText), &codeReq); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &file) failed with %v; want success", codeReqText, err)
+	}
+
+	for _, tcase := range []struct {
+		options   *openapiconfig.OpenAPIOptions
+		shouldErr bool
+		desc      string
+	}{
+		{
+			desc: "handle nil options",
+		},
+		{
+			desc: "successfully add options if referenced entity exists",
+			options: &openapiconfig.OpenAPIOptions{
+				File: []*openapiconfig.OpenAPIFileOption{
+					{
+						File: "a.proto",
+					},
+				},
+				Method: []*openapiconfig.OpenAPIMethodOption{
+					{
+						Method: "example.foo.AService.Meth",
+					},
+				},
+				Message: []*openapiconfig.OpenAPIMessageOption{
+					{
+						Message: "example.foo.ExampleMessage",
+					},
+				},
+				Service: []*openapiconfig.OpenAPIServiceOption{
+					{
+						Service: "example.foo.AService",
+					},
+				},
+				Field: []*openapiconfig.OpenAPIFieldOption{
+					{
+						Field: "example.foo.ExampleMessage.str",
+					},
+				},
+			},
+		},
+		{
+			desc: "reject fully qualified names with leading \".\"",
+			options: &openapiconfig.OpenAPIOptions{
+				File: []*openapiconfig.OpenAPIFileOption{
+					{
+						File: "a.proto",
+					},
+				},
+				Method: []*openapiconfig.OpenAPIMethodOption{
+					{
+						Method: ".example.foo.AService.Meth",
+					},
+				},
+				Message: []*openapiconfig.OpenAPIMessageOption{
+					{
+						Message: ".example.foo.ExampleMessage",
+					},
+				},
+				Service: []*openapiconfig.OpenAPIServiceOption{
+					{
+						Service: ".example.foo.AService",
+					},
+				},
+				Field: []*openapiconfig.OpenAPIFieldOption{
+					{
+						Field: ".example.foo.ExampleMessage.str",
+					},
+				},
+			},
+			shouldErr: true,
+		},
+		{
+			desc: "error if file does not exist",
+			options: &openapiconfig.OpenAPIOptions{
+				File: []*openapiconfig.OpenAPIFileOption{
+					{
+						File: "b.proto",
+					},
+				},
+			},
+			shouldErr: true,
+		},
+		{
+			desc: "error if method does not exist",
+			options: &openapiconfig.OpenAPIOptions{
+				Method: []*openapiconfig.OpenAPIMethodOption{
+					{
+						Method: "example.foo.AService.Meth2",
+					},
+				},
+			},
+			shouldErr: true,
+		},
+		{
+			desc: "error if message does not exist",
+			options: &openapiconfig.OpenAPIOptions{
+				Message: []*openapiconfig.OpenAPIMessageOption{
+					{
+						Message: "example.foo.NonexistentMessage",
+					},
+				},
+			},
+			shouldErr: true,
+		},
+		{
+			desc: "error if service does not exist",
+			options: &openapiconfig.OpenAPIOptions{
+				Service: []*openapiconfig.OpenAPIServiceOption{
+					{
+						Service: "example.foo.AService1",
+					},
+				},
+			},
+			shouldErr: true,
+		},
+		{
+			desc: "error if field does not exist",
+			options: &openapiconfig.OpenAPIOptions{
+				Field: []*openapiconfig.OpenAPIFieldOption{
+					{
+						Field: "example.foo.ExampleMessage.str1",
+					},
+				},
+			},
+			shouldErr: true,
+		},
+	} {
+		t.Run(tcase.desc, func(t *testing.T) {
+			reg := NewRegistry()
+			loadFileWithCodeGeneratorRequest(t, reg, &codeReq)
+			err := reg.RegisterOpenAPIOptions(tcase.options)
+			if (err != nil) != tcase.shouldErr {
+				t.Fatalf("got unexpected error: %s", err)
+			}
+		})
+	}
+}
+
+func assertStringSlice(t *testing.T, message string, got, want []string) {
+	if len(got) != len(want) {
+		t.Errorf("%s = %#v len(%d); want %#v len(%d)", message, got, len(got), want, len(want))
+	}
+	for i := range want {
+		if got[i] != want[i] {
+			t.Errorf("%s[%d] = %#v; want %#v", message, i, got[i], want[i])
+		}
+	}
+}

+ 347 - 0
protoc-gen-openapiv2/internal/descriptor/services.go

@@ -0,0 +1,347 @@
+package descriptor
+
+import (
+	"fmt"
+	"strings"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/httprule"
+	"github.com/golang/glog"
+	options "google.golang.org/genproto/googleapis/api/annotations"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+// loadServices registers services and their methods from "targetFile" to "r".
+// It must be called after loadFile is called for all files so that loadServices
+// can resolve names of message types and their fields.
+func (r *Registry) loadServices(file *File) error {
+	glog.V(1).Infof("Loading services from %s", file.GetName())
+	var svcs []*Service
+	for _, sd := range file.GetService() {
+		glog.V(2).Infof("Registering %s", sd.GetName())
+		svc := &Service{
+			File:                   file,
+			ServiceDescriptorProto: sd,
+			ForcePrefixedName:      r.standalone,
+		}
+		for _, md := range sd.GetMethod() {
+			glog.V(2).Infof("Processing %s.%s", sd.GetName(), md.GetName())
+			opts, err := extractAPIOptions(md)
+			if err != nil {
+				glog.Errorf("Failed to extract HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
+				return err
+			}
+			var optsList []*options.HttpRule
+			if r.generateRPCMethods {
+				defaultOpts, err := defaultAPIOptions(svc, md)
+				if err != nil {
+					glog.Errorf("Failed to generate default HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
+					return err
+				}
+				optsList = append(optsList, defaultOpts)
+			} else {
+				optsList = r.LookupExternalHTTPRules((&Method{Service: svc, MethodDescriptorProto: md}).FQMN())
+				if opts != nil {
+					optsList = append(optsList, opts)
+				}
+			}
+			if len(optsList) == 0 {
+				if r.generateUnboundMethods {
+					defaultOpts, err := defaultAPIOptions(svc, md)
+					if err != nil {
+						glog.Errorf("Failed to generate default HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
+						return err
+					}
+					optsList = append(optsList, defaultOpts)
+				} else {
+					logFn := glog.V(1).Infof
+					if r.warnOnUnboundMethods {
+						logFn = glog.Warningf
+					}
+					logFn("No HttpRule found for method: %s.%s", svc.GetName(), md.GetName())
+				}
+			}
+			meth, err := r.newMethod(svc, md, optsList)
+			if err != nil {
+				return err
+			}
+			svc.Methods = append(svc.Methods, meth)
+		}
+		if len(svc.Methods) == 0 {
+			continue
+		}
+		glog.V(2).Infof("Registered %s with %d method(s)", svc.GetName(), len(svc.Methods))
+		svcs = append(svcs, svc)
+	}
+	file.Services = svcs
+	return nil
+}
+
+func (r *Registry) newMethod(svc *Service, md *descriptorpb.MethodDescriptorProto, optsList []*options.HttpRule) (*Method, error) {
+	requestType, err := r.LookupMsg(svc.File.GetPackage(), md.GetInputType())
+	if err != nil {
+		return nil, err
+	}
+	responseType, err := r.LookupMsg(svc.File.GetPackage(), md.GetOutputType())
+	if err != nil {
+		return nil, err
+	}
+	meth := &Method{
+		Service:               svc,
+		MethodDescriptorProto: md,
+		RequestType:           requestType,
+		ResponseType:          responseType,
+	}
+
+	newBinding := func(opts *options.HttpRule, idx int) (*Binding, error) {
+		var (
+			httpMethod   string
+			pathTemplate string
+		)
+		switch {
+		case opts.GetGet() != "":
+			httpMethod = "GET"
+			pathTemplate = opts.GetGet()
+			if opts.Body != "" {
+				return nil, fmt.Errorf("must not set request body when http method is GET: %s", md.GetName())
+			}
+
+		case opts.GetPut() != "":
+			httpMethod = "PUT"
+			pathTemplate = opts.GetPut()
+
+		case opts.GetPost() != "":
+			httpMethod = "POST"
+			pathTemplate = opts.GetPost()
+
+		case opts.GetDelete() != "":
+			httpMethod = "DELETE"
+			pathTemplate = opts.GetDelete()
+			if opts.Body != "" && !r.allowDeleteBody {
+				return nil, fmt.Errorf("must not set request body when http method is DELETE except allow_delete_body option is true: %s", md.GetName())
+			}
+
+		case opts.GetPatch() != "":
+			httpMethod = "PATCH"
+			pathTemplate = opts.GetPatch()
+
+		case opts.GetCustom() != nil:
+			custom := opts.GetCustom()
+			httpMethod = custom.Kind
+			pathTemplate = custom.Path
+
+		default:
+			glog.V(1).Infof("No pattern specified in google.api.HttpRule: %s", md.GetName())
+			return nil, nil
+		}
+
+		parsed, err := httprule.Parse(pathTemplate)
+		if err != nil {
+			return nil, err
+		}
+		tmpl := parsed.Compile()
+
+		if md.GetClientStreaming() && len(tmpl.Fields) > 0 {
+			return nil, fmt.Errorf("cannot use path parameter in client streaming")
+		}
+
+		b := &Binding{
+			Method:     meth,
+			Index:      idx,
+			PathTmpl:   tmpl,
+			HTTPMethod: httpMethod,
+		}
+
+		for _, f := range tmpl.Fields {
+			param, err := r.newParam(meth, f)
+			if err != nil {
+				return nil, err
+			}
+			b.PathParams = append(b.PathParams, param)
+		}
+
+		// TODO(yugui) Handle query params
+
+		b.Body, err = r.newBody(meth, opts.Body)
+		if err != nil {
+			return nil, err
+		}
+
+		b.ResponseBody, err = r.newResponse(meth, opts.ResponseBody)
+		if err != nil {
+			return nil, err
+		}
+
+		return b, nil
+	}
+
+	applyOpts := func(opts *options.HttpRule) error {
+		b, err := newBinding(opts, len(meth.Bindings))
+		if err != nil {
+			return err
+		}
+
+		if b != nil {
+			meth.Bindings = append(meth.Bindings, b)
+		}
+		for _, additional := range opts.GetAdditionalBindings() {
+			if len(additional.AdditionalBindings) > 0 {
+				return fmt.Errorf("additional_binding in additional_binding not allowed: %s.%s", svc.GetName(), meth.GetName())
+			}
+			b, err := newBinding(additional, len(meth.Bindings))
+			if err != nil {
+				return err
+			}
+			meth.Bindings = append(meth.Bindings, b)
+		}
+
+		return nil
+	}
+
+	for _, opts := range optsList {
+		if err := applyOpts(opts); err != nil {
+			return nil, err
+		}
+	}
+
+	return meth, nil
+}
+
+func extractAPIOptions(meth *descriptorpb.MethodDescriptorProto) (*options.HttpRule, error) {
+	if meth.Options == nil {
+		return nil, nil
+	}
+	if !proto.HasExtension(meth.Options, options.E_Http) {
+		return nil, nil
+	}
+	ext := proto.GetExtension(meth.Options, options.E_Http)
+	opts, ok := ext.(*options.HttpRule)
+	if !ok {
+		return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
+	}
+	return opts, nil
+}
+
+func defaultAPIOptions(svc *Service, md *descriptorpb.MethodDescriptorProto) (*options.HttpRule, error) {
+	// FQSN prefixes the service's full name with a '.', e.g.: '.example.ExampleService'
+	fqsn := strings.TrimPrefix(svc.FQSN(), ".")
+
+	// This generates an HttpRule that matches the gRPC mapping to HTTP/2 described in
+	// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
+	// i.e.:
+	//   * method is POST
+	//   * path is "/<service name>/<method name>"
+	//   * body should contain the serialized request message
+	rule := &options.HttpRule{
+		Pattern: &options.HttpRule_Post{
+			Post: fmt.Sprintf("/%s/%s", fqsn, md.GetName()),
+		},
+		Body: "*",
+	}
+	return rule, nil
+}
+
+func (r *Registry) newParam(meth *Method, path string) (Parameter, error) {
+	msg := meth.RequestType
+	fields, err := r.resolveFieldPath(msg, path, true)
+	if err != nil {
+		return Parameter{}, err
+	}
+	l := len(fields)
+	if l == 0 {
+		return Parameter{}, fmt.Errorf("invalid field access list for %s", path)
+	}
+	target := fields[l-1].Target
+	switch target.GetType() {
+	case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
+		glog.V(2).Infoln("found aggregate type:", target, target.TypeName)
+		if IsWellKnownType(*target.TypeName) {
+			glog.V(2).Infoln("found well known aggregate type:", target)
+		} else {
+			return Parameter{}, fmt.Errorf("%s.%s: %s is a protobuf message type. Protobuf message types cannot be used as path parameters, use a scalar value type (such as string) instead", meth.Service.GetName(), meth.GetName(), path)
+		}
+	}
+	return Parameter{
+		FieldPath: FieldPath(fields),
+		Method:    meth,
+		Target:    fields[l-1].Target,
+	}, nil
+}
+
+func (r *Registry) newBody(meth *Method, path string) (*Body, error) {
+	msg := meth.RequestType
+	switch path {
+	case "":
+		return nil, nil
+	case "*":
+		return &Body{FieldPath: nil}, nil
+	}
+	fields, err := r.resolveFieldPath(msg, path, false)
+	if err != nil {
+		return nil, err
+	}
+	return &Body{FieldPath: FieldPath(fields)}, nil
+}
+
+func (r *Registry) newResponse(meth *Method, path string) (*Body, error) {
+	msg := meth.ResponseType
+	switch path {
+	case "", "*":
+		return nil, nil
+	}
+	fields, err := r.resolveFieldPath(msg, path, false)
+	if err != nil {
+		return nil, err
+	}
+	return &Body{FieldPath: FieldPath(fields)}, nil
+}
+
+// lookupField looks up a field named "name" within "msg".
+// It returns nil if no such field found.
+func lookupField(msg *Message, name string) *Field {
+	for _, f := range msg.Fields {
+		if f.GetName() == name {
+			return f
+		}
+	}
+	return nil
+}
+
+// resolveFieldPath resolves "path" into a list of fieldDescriptor, starting from "msg".
+func (r *Registry) resolveFieldPath(msg *Message, path string, isPathParam bool) ([]FieldPathComponent, error) {
+	if path == "" {
+		return nil, nil
+	}
+
+	root := msg
+	var result []FieldPathComponent
+	for i, c := range strings.Split(path, ".") {
+		if i > 0 {
+			f := result[i-1].Target
+			switch f.GetType() {
+			case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
+				var err error
+				msg, err = r.LookupMsg(msg.FQMN(), f.GetTypeName())
+				if err != nil {
+					return nil, err
+				}
+			default:
+				return nil, fmt.Errorf("not an aggregate type: %s in %s", f.GetName(), path)
+			}
+		}
+
+		glog.V(2).Infof("Lookup %s in %s", c, msg.FQMN())
+		f := lookupField(msg, c)
+		if f == nil {
+			return nil, fmt.Errorf("no field %q found in %s", path, root.GetName())
+		}
+		if !(isPathParam || r.allowRepeatedFieldsInBody) && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
+			return nil, fmt.Errorf("repeated field not allowed in field path: %s in %s", f.GetName(), path)
+		}
+		if isPathParam && f.GetProto3Optional() {
+			return nil, fmt.Errorf("optional field not allowed in field path: %s in %s", f.GetName(), path)
+		}
+		result = append(result, FieldPathComponent{Name: c, Target: f})
+	}
+	return result, nil
+}

+ 1447 - 0
protoc-gen-openapiv2/internal/descriptor/services_test.go

@@ -0,0 +1,1447 @@
+package descriptor
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/httprule"
+	"google.golang.org/protobuf/compiler/protogen"
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+func compilePath(t *testing.T, path string) httprule.Template {
+	parsed, err := httprule.Parse(path)
+	if err != nil {
+		t.Fatalf("httprule.Parse(%q) failed with %v; want success", path, err)
+	}
+	return parsed.Compile()
+}
+
+func testExtractServices(t *testing.T, input []*descriptorpb.FileDescriptorProto, target string, wantSvcs []*Service) {
+	testExtractServicesWithRegistry(t, NewRegistry(), input, target, wantSvcs)
+}
+
+func testExtractServicesWithRegistry(t *testing.T, reg *Registry, input []*descriptorpb.FileDescriptorProto, target string, wantSvcs []*Service) {
+	for _, file := range input {
+		reg.loadFile(file.GetName(), &protogen.File{
+			Proto: file,
+		})
+	}
+	err := reg.loadServices(reg.files[target])
+	if err != nil {
+		t.Errorf("loadServices(%q) failed with %v; want success; files=%v", target, err, input)
+	}
+
+	file := reg.files[target]
+	svcs := file.Services
+	var i int
+	for i = 0; i < len(svcs) && i < len(wantSvcs); i++ {
+		svc, wantSvc := svcs[i], wantSvcs[i]
+		if got, want := svc.ServiceDescriptorProto, wantSvc.ServiceDescriptorProto; !proto.Equal(got, want) {
+			t.Errorf("svcs[%d].ServiceDescriptorProto = %v; want %v; input = %v", i, got, want, input)
+			continue
+		}
+		var j int
+		for j = 0; j < len(svc.Methods) && j < len(wantSvc.Methods); j++ {
+			meth, wantMeth := svc.Methods[j], wantSvc.Methods[j]
+			if got, want := meth.MethodDescriptorProto, wantMeth.MethodDescriptorProto; !proto.Equal(got, want) {
+				t.Errorf("svcs[%d].Methods[%d].MethodDescriptorProto = %v; want %v; input = %v", i, j, got, want, input)
+				continue
+			}
+			if got, want := meth.RequestType, wantMeth.RequestType; got.FQMN() != want.FQMN() {
+				t.Errorf("svcs[%d].Methods[%d].RequestType = %s; want %s; input = %v", i, j, got.FQMN(), want.FQMN(), input)
+			}
+			if got, want := meth.ResponseType, wantMeth.ResponseType; got.FQMN() != want.FQMN() {
+				t.Errorf("svcs[%d].Methods[%d].ResponseType = %s; want %s; input = %v", i, j, got.FQMN(), want.FQMN(), input)
+			}
+			var k int
+			for k = 0; k < len(meth.Bindings) && k < len(wantMeth.Bindings); k++ {
+				binding, wantBinding := meth.Bindings[k], wantMeth.Bindings[k]
+				if got, want := binding.Index, wantBinding.Index; got != want {
+					t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Index = %d; want %d; input = %v", i, j, k, got, want, input)
+				}
+				if got, want := binding.PathTmpl, wantBinding.PathTmpl; !reflect.DeepEqual(got, want) {
+					t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathTmpl = %#v; want %#v; input = %v", i, j, k, got, want, input)
+				}
+				if got, want := binding.HTTPMethod, wantBinding.HTTPMethod; got != want {
+					t.Errorf("svcs[%d].Methods[%d].Bindings[%d].HTTPMethod = %q; want %q; input = %v", i, j, k, got, want, input)
+				}
+
+				var l int
+				for l = 0; l < len(binding.PathParams) && l < len(wantBinding.PathParams); l++ {
+					param, wantParam := binding.PathParams[l], wantBinding.PathParams[l]
+					if got, want := param.FieldPath.String(), wantParam.FieldPath.String(); got != want {
+						t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d].FieldPath.String() = %q; want %q; input = %v", i, j, k, l, got, want, input)
+						continue
+					}
+					for m := 0; m < len(param.FieldPath) && m < len(wantParam.FieldPath); m++ {
+						field, wantField := param.FieldPath[m].Target, wantParam.FieldPath[m].Target
+						if got, want := field.FieldDescriptorProto, wantField.FieldDescriptorProto; !proto.Equal(got, want) {
+							t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d].FieldPath[%d].Target.FieldDescriptorProto = %v; want %v; input = %v", i, j, k, l, m, got, want, input)
+						}
+					}
+				}
+				for ; l < len(binding.PathParams); l++ {
+					got := binding.PathParams[l].FieldPath.String()
+					t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d] = %q; want it to be missing; input = %v", i, j, k, l, got, input)
+				}
+				for ; l < len(wantBinding.PathParams); l++ {
+					want := wantBinding.PathParams[l].FieldPath.String()
+					t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d] missing; want %q; input = %v", i, j, k, l, want, input)
+				}
+
+				if got, want := (binding.Body != nil), (wantBinding.Body != nil); got != want {
+					if got {
+						t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Body = %q; want it to be missing; input = %v", i, j, k, binding.Body.FieldPath.String(), input)
+					} else {
+						t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Body missing; want %q; input = %v", i, j, k, wantBinding.Body.FieldPath.String(), input)
+					}
+				} else if binding.Body != nil {
+					if got, want := binding.Body.FieldPath.String(), wantBinding.Body.FieldPath.String(); got != want {
+						t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Body = %q; want %q; input = %v", i, j, k, got, want, input)
+					}
+				}
+			}
+			for ; k < len(meth.Bindings); k++ {
+				got := meth.Bindings[k]
+				t.Errorf("svcs[%d].Methods[%d].Bindings[%d] = %v; want it to be missing; input = %v", i, j, k, got, input)
+			}
+			for ; k < len(wantMeth.Bindings); k++ {
+				want := wantMeth.Bindings[k]
+				t.Errorf("svcs[%d].Methods[%d].Bindings[%d] missing; want %v; input = %v", i, j, k, want, input)
+			}
+		}
+		for ; j < len(svc.Methods); j++ {
+			got := svc.Methods[j].MethodDescriptorProto
+			t.Errorf("svcs[%d].Methods[%d] = %v; want it to be missing; input = %v", i, j, got, input)
+		}
+		for ; j < len(wantSvc.Methods); j++ {
+			want := wantSvc.Methods[j].MethodDescriptorProto
+			t.Errorf("svcs[%d].Methods[%d] missing; want %v; input = %v", i, j, want, input)
+		}
+	}
+	for ; i < len(svcs); i++ {
+		got := svcs[i].ServiceDescriptorProto
+		t.Errorf("svcs[%d] = %v; want it to be missing; input = %v", i, got, input)
+	}
+	for ; i < len(wantSvcs); i++ {
+		want := wantSvcs[i].ServiceDescriptorProto
+		t.Errorf("svcs[%d] missing; want %v; input = %v", i, want, input)
+	}
+}
+
+func crossLinkFixture(f *File) *File {
+	for _, m := range f.Messages {
+		m.File = f
+		for _, f := range m.Fields {
+			f.Message = m
+		}
+	}
+	for _, svc := range f.Services {
+		svc.File = f
+		for _, m := range svc.Methods {
+			m.Service = svc
+			for _, b := range m.Bindings {
+				b.Method = m
+				for _, param := range b.PathParams {
+					param.Method = m
+				}
+			}
+		}
+	}
+	for _, e := range f.Enums {
+		e.File = f
+	}
+	return f
+}
+
+func TestExtractServicesSimple(t *testing.T) {
+	src := `
+		name: "path/to/example.proto",
+		package: "example"
+		message_type <
+			name: "StringMessage"
+			field <
+				name: "string"
+				number: 1
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "StringMessage"
+				output_type: "StringMessage"
+				options <
+					[google.api.http] <
+						post: "/v1/example/echo"
+						body: "*"
+					>
+				>
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+	msg := &Message{
+		DescriptorProto: fd.MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fd.MessageType[0].Field[0],
+			},
+		},
+	}
+	file := &File{
+		FileDescriptorProto: &fd,
+		GoPkg: GoPackage{
+			Path: "path/to/example.pb",
+			Name: "example_pb",
+		},
+		Messages: []*Message{msg},
+		Services: []*Service{
+			{
+				ServiceDescriptorProto: fd.Service[0],
+				Methods: []*Method{
+					{
+						MethodDescriptorProto: fd.Service[0].Method[0],
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*Binding{
+							{
+								PathTmpl:   compilePath(t, "/v1/example/echo"),
+								HTTPMethod: "POST",
+								Body:       &Body{FieldPath: nil},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	crossLinkFixture(file)
+	testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
+}
+
+func TestExtractServicesWithoutAnnotation(t *testing.T) {
+	src := `
+		name: "path/to/example.proto",
+		package: "example"
+		message_type <
+			name: "StringMessage"
+			field <
+				name: "string"
+				number: 1
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "StringMessage"
+				output_type: "StringMessage"
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+	msg := &Message{
+		DescriptorProto: fd.MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fd.MessageType[0].Field[0],
+			},
+		},
+	}
+	file := &File{
+		FileDescriptorProto: &fd,
+		GoPkg: GoPackage{
+			Path: "path/to/example.pb",
+			Name: "example_pb",
+		},
+		Messages: []*Message{msg},
+		Services: []*Service{
+			{
+				ServiceDescriptorProto: fd.Service[0],
+				Methods: []*Method{
+					{
+						MethodDescriptorProto: fd.Service[0].Method[0],
+						RequestType:           msg,
+						ResponseType:          msg,
+					},
+				},
+			},
+		},
+	}
+
+	crossLinkFixture(file)
+	testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
+}
+
+func TestExtractServicesGenerateUnboundMethods(t *testing.T) {
+	src := `
+		name: "path/to/example.proto",
+		package: "example"
+		message_type <
+			name: "StringMessage"
+			field <
+				name: "string"
+				number: 1
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "StringMessage"
+				output_type: "StringMessage"
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("prototext.Unmarshal (%s, &fd) failed with %v; want success", src, err)
+	}
+	msg := &Message{
+		DescriptorProto: fd.MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fd.MessageType[0].Field[0],
+			},
+		},
+	}
+	file := &File{
+		FileDescriptorProto: &fd,
+		GoPkg: GoPackage{
+			Path: "path/to/example.pb",
+			Name: "example_pb",
+		},
+		Messages: []*Message{msg},
+		Services: []*Service{
+			{
+				ServiceDescriptorProto: fd.Service[0],
+				Methods: []*Method{
+					{
+						MethodDescriptorProto: fd.Service[0].Method[0],
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*Binding{
+							{
+								PathTmpl:   compilePath(t, "/example.ExampleService/Echo"),
+								HTTPMethod: "POST",
+								Body:       &Body{FieldPath: nil},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	crossLinkFixture(file)
+	reg := NewRegistry()
+	reg.SetGenerateUnboundMethods(true)
+	testExtractServicesWithRegistry(t, reg, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
+}
+
+func TestExtractServicesCrossPackage(t *testing.T) {
+	srcs := []string{
+		`
+			name: "path/to/example.proto",
+			package: "example"
+			message_type <
+				name: "StringMessage"
+				field <
+					name: "string"
+					number: 1
+					label: LABEL_OPTIONAL
+					type: TYPE_STRING
+				>
+			>
+			service <
+				name: "ExampleService"
+				method <
+					name: "ToString"
+					input_type: ".another.example.BoolMessage"
+					output_type: "StringMessage"
+					options <
+						[google.api.http] <
+							post: "/v1/example/to_s"
+							body: "*"
+						>
+					>
+				>
+			>
+		`, `
+			name: "path/to/another/example.proto",
+			package: "another.example"
+			message_type <
+				name: "BoolMessage"
+				field <
+					name: "bool"
+					number: 1
+					label: LABEL_OPTIONAL
+					type: TYPE_BOOL
+				>
+			>
+		`,
+	}
+	var fds []*descriptorpb.FileDescriptorProto
+	for _, src := range srcs {
+		var fd descriptorpb.FileDescriptorProto
+		if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+			t.Fatalf("prototext.Unmarshal(%s, &fd) failed with %v; want success", src, err)
+		}
+		fds = append(fds, &fd)
+	}
+	stringMsg := &Message{
+		DescriptorProto: fds[0].MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fds[0].MessageType[0].Field[0],
+			},
+		},
+	}
+	boolMsg := &Message{
+		DescriptorProto: fds[1].MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fds[1].MessageType[0].Field[0],
+			},
+		},
+	}
+	files := []*File{
+		{
+			FileDescriptorProto: fds[0],
+			GoPkg: GoPackage{
+				Path: "path/to/example.pb",
+				Name: "example_pb",
+			},
+			Messages: []*Message{stringMsg},
+			Services: []*Service{
+				{
+					ServiceDescriptorProto: fds[0].Service[0],
+					Methods: []*Method{
+						{
+							MethodDescriptorProto: fds[0].Service[0].Method[0],
+							RequestType:           boolMsg,
+							ResponseType:          stringMsg,
+							Bindings: []*Binding{
+								{
+									PathTmpl:   compilePath(t, "/v1/example/to_s"),
+									HTTPMethod: "POST",
+									Body:       &Body{FieldPath: nil},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			FileDescriptorProto: fds[1],
+			GoPkg: GoPackage{
+				Path: "path/to/another/example.pb",
+				Name: "example_pb",
+			},
+			Messages: []*Message{boolMsg},
+		},
+	}
+
+	for _, file := range files {
+		crossLinkFixture(file)
+	}
+	testExtractServices(t, fds, "path/to/example.proto", files[0].Services)
+}
+
+func TestExtractServicesWithBodyPath(t *testing.T) {
+	src := `
+		name: "path/to/example.proto",
+		package: "example"
+		message_type <
+			name: "OuterMessage"
+			nested_type <
+				name: "StringMessage"
+				field <
+					name: "string"
+					number: 1
+					label: LABEL_OPTIONAL
+					type: TYPE_STRING
+				>
+			>
+			field <
+				name: "nested"
+				number: 1
+				label: LABEL_OPTIONAL
+				type: TYPE_MESSAGE
+				type_name: "StringMessage"
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "OuterMessage"
+				output_type: "OuterMessage"
+				options <
+					[google.api.http] <
+						post: "/v1/example/echo"
+						body: "nested"
+					>
+				>
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+	msg := &Message{
+		DescriptorProto: fd.MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fd.MessageType[0].Field[0],
+			},
+		},
+	}
+	file := &File{
+		FileDescriptorProto: &fd,
+		GoPkg: GoPackage{
+			Path: "path/to/example.pb",
+			Name: "example_pb",
+		},
+		Messages: []*Message{msg},
+		Services: []*Service{
+			{
+				ServiceDescriptorProto: fd.Service[0],
+				Methods: []*Method{
+					{
+						MethodDescriptorProto: fd.Service[0].Method[0],
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*Binding{
+							{
+								PathTmpl:   compilePath(t, "/v1/example/echo"),
+								HTTPMethod: "POST",
+								Body: &Body{
+									FieldPath: FieldPath{
+										{
+											Name:   "nested",
+											Target: msg.Fields[0],
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	crossLinkFixture(file)
+	testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
+}
+
+func TestExtractServicesWithPathParam(t *testing.T) {
+	src := `
+		name: "path/to/example.proto",
+		package: "example"
+		message_type <
+			name: "StringMessage"
+			field <
+				name: "string"
+				number: 1
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "StringMessage"
+				output_type: "StringMessage"
+				options <
+					[google.api.http] <
+						get: "/v1/example/echo/{string=*}"
+					>
+				>
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+	msg := &Message{
+		DescriptorProto: fd.MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fd.MessageType[0].Field[0],
+			},
+		},
+	}
+	file := &File{
+		FileDescriptorProto: &fd,
+		GoPkg: GoPackage{
+			Path: "path/to/example.pb",
+			Name: "example_pb",
+		},
+		Messages: []*Message{msg},
+		Services: []*Service{
+			{
+				ServiceDescriptorProto: fd.Service[0],
+				Methods: []*Method{
+					{
+						MethodDescriptorProto: fd.Service[0].Method[0],
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*Binding{
+							{
+								PathTmpl:   compilePath(t, "/v1/example/echo/{string=*}"),
+								HTTPMethod: "GET",
+								PathParams: []Parameter{
+									{
+										FieldPath: FieldPath{
+											{
+												Name:   "string",
+												Target: msg.Fields[0],
+											},
+										},
+										Target: msg.Fields[0],
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	crossLinkFixture(file)
+	testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
+}
+
+func TestExtractServicesWithAdditionalBinding(t *testing.T) {
+	src := `
+		name: "path/to/example.proto",
+		package: "example"
+		message_type <
+			name: "StringMessage"
+			field <
+				name: "string"
+				number: 1
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "StringMessage"
+				output_type: "StringMessage"
+				options <
+					[google.api.http] <
+						post: "/v1/example/echo"
+						body: "*"
+						additional_bindings <
+							get: "/v1/example/echo/{string}"
+						>
+						additional_bindings <
+							post: "/v2/example/echo"
+							body: "string"
+						>
+					>
+				>
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+	msg := &Message{
+		DescriptorProto: fd.MessageType[0],
+		Fields: []*Field{
+			{
+				FieldDescriptorProto: fd.MessageType[0].Field[0],
+			},
+		},
+	}
+	file := &File{
+		FileDescriptorProto: &fd,
+		GoPkg: GoPackage{
+			Path: "path/to/example.pb",
+			Name: "example_pb",
+		},
+		Messages: []*Message{msg},
+		Services: []*Service{
+			{
+				ServiceDescriptorProto: fd.Service[0],
+				Methods: []*Method{
+					{
+						MethodDescriptorProto: fd.Service[0].Method[0],
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*Binding{
+							{
+								Index:      0,
+								PathTmpl:   compilePath(t, "/v1/example/echo"),
+								HTTPMethod: "POST",
+								Body:       &Body{FieldPath: nil},
+							},
+							{
+								Index:      1,
+								PathTmpl:   compilePath(t, "/v1/example/echo/{string}"),
+								HTTPMethod: "GET",
+								PathParams: []Parameter{
+									{
+										FieldPath: FieldPath{
+											{
+												Name:   "string",
+												Target: msg.Fields[0],
+											},
+										},
+										Target: msg.Fields[0],
+									},
+								},
+								Body: nil,
+							},
+							{
+								Index:      2,
+								PathTmpl:   compilePath(t, "/v2/example/echo"),
+								HTTPMethod: "POST",
+								Body: &Body{
+									FieldPath: FieldPath{
+										FieldPathComponent{
+											Name:   "string",
+											Target: msg.Fields[0],
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	crossLinkFixture(file)
+	testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
+}
+
+func TestExtractServicesWithError(t *testing.T) {
+	for _, spec := range []struct {
+		target string
+		srcs   []string
+	}{
+		{
+			target: "path/to/example.proto",
+			srcs: []string{
+				// message not found
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					service <
+						name: "ExampleService"
+						method <
+							name: "Echo"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									post: "/v1/example/echo"
+									body: "*"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+		// body field path not resolved
+		{
+			target: "path/to/example.proto",
+			srcs: []string{`
+						name: "path/to/example.proto",
+						package: "example"
+						message_type <
+							name: "StringMessage"
+							field <
+								name: "string"
+								number: 1
+								label: LABEL_OPTIONAL
+								type: TYPE_STRING
+							>
+						>
+						service <
+							name: "ExampleService"
+							method <
+								name: "Echo"
+								input_type: "StringMessage"
+								output_type: "StringMessage"
+								options <
+									[google.api.http] <
+										post: "/v1/example/echo"
+										body: "bool"
+									>
+								>
+							>
+						>`,
+			},
+		},
+		// param field path not resolved
+		{
+			target: "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "StringMessage"
+						field <
+							name: "string"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_STRING
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "Echo"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									post: "/v1/example/echo/{bool=*}"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+		// non aggregate type on field path
+		{
+			target: "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "OuterMessage"
+						field <
+							name: "mid"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_STRING
+						>
+						field <
+							name: "bool"
+							number: 2
+							label: LABEL_OPTIONAL
+							type: TYPE_BOOL
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "Echo"
+							input_type: "OuterMessage"
+							output_type: "OuterMessage"
+							options <
+								[google.api.http] <
+									post: "/v1/example/echo/{mid.bool=*}"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+		// path param in client streaming
+		{
+			target: "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "StringMessage"
+						field <
+							name: "string"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_STRING
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "Echo"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									post: "/v1/example/echo/{bool=*}"
+								>
+							>
+							client_streaming: true
+						>
+					>
+				`,
+			},
+		},
+		// body for GET
+		{
+			target: "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "StringMessage"
+						field <
+							name: "string"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_STRING
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "Echo"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									get: "/v1/example/echo"
+									body: "string"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+		// body for DELETE
+		{
+			target: "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "StringMessage"
+						field <
+							name: "string"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_STRING
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "RemoveResource"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									delete: "/v1/example/resource"
+									body: "string"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+		// no pattern specified
+		{
+			target: "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					service <
+						name: "ExampleService"
+						method <
+							name: "RemoveResource"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									body: "string"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+		// unsupported path parameter type
+		{
+			target: "path/to/example.proto",
+			srcs: []string{`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "OuterMessage"
+						nested_type <
+							name: "StringMessage"
+							field <
+								name: "value"
+								number: 1
+								label: LABEL_OPTIONAL
+								type: TYPE_STRING
+							>
+						>
+						field <
+							name: "string"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_MESSAGE
+							type_name: "StringMessage"
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "Echo"
+							input_type: "OuterMessage"
+							output_type: "OuterMessage"
+							options <
+								[google.api.http] <
+									get: "/v1/example/echo/{string=*}"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+	} {
+		reg := NewRegistry()
+
+		for _, src := range spec.srcs {
+			var fd descriptorpb.FileDescriptorProto
+			if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+				t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+			}
+			reg.loadFile(spec.target, &protogen.File{
+				Proto: &fd,
+			})
+		}
+		err := reg.loadServices(reg.files[spec.target])
+		if err == nil {
+			t.Errorf("loadServices(%q) succeeded; want an error; files=%v", spec.target, spec.srcs)
+		}
+		t.Log(err)
+	}
+}
+
+func TestResolveFieldPath(t *testing.T) {
+	for _, spec := range []struct {
+		src     string
+		path    string
+		wantErr bool
+	}{
+		{
+			src: `
+				name: 'example.proto'
+				package: 'example'
+				message_type <
+					name: 'ExampleMessage'
+					field <
+						name: 'string'
+						type: TYPE_STRING
+						label: LABEL_OPTIONAL
+						number: 1
+					>
+				>
+			`,
+			path:    "string",
+			wantErr: false,
+		},
+		// no such field
+		{
+			src: `
+				name: 'example.proto'
+				package: 'example'
+				message_type <
+					name: 'ExampleMessage'
+					field <
+						name: 'string'
+						type: TYPE_STRING
+						label: LABEL_OPTIONAL
+						number: 1
+					>
+				>
+			`,
+			path:    "something_else",
+			wantErr: true,
+		},
+		// repeated field
+		{
+			src: `
+				name: 'example.proto'
+				package: 'example'
+				message_type <
+					name: 'ExampleMessage'
+					field <
+						name: 'string'
+						type: TYPE_STRING
+						label: LABEL_REPEATED
+						number: 1
+					>
+				>
+			`,
+			path:    "string",
+			wantErr: true,
+		},
+		// nested field
+		{
+			src: `
+				name: 'example.proto'
+				package: 'example'
+				message_type <
+					name: 'ExampleMessage'
+					field <
+						name: 'nested'
+						type: TYPE_MESSAGE
+						type_name: 'AnotherMessage'
+						label: LABEL_OPTIONAL
+						number: 1
+					>
+					field <
+						name: 'terminal'
+						type: TYPE_BOOL
+						label: LABEL_OPTIONAL
+						number: 2
+					>
+				>
+				message_type <
+					name: 'AnotherMessage'
+					field <
+						name: 'nested2'
+						type: TYPE_MESSAGE
+						type_name: 'ExampleMessage'
+						label: LABEL_OPTIONAL
+						number: 1
+					>
+				>
+			`,
+			path:    "nested.nested2.nested.nested2.nested.nested2.terminal",
+			wantErr: false,
+		},
+		// non aggregate field on the path
+		{
+			src: `
+				name: 'example.proto'
+				package: 'example'
+				message_type <
+					name: 'ExampleMessage'
+					field <
+						name: 'nested'
+						type: TYPE_MESSAGE
+						type_name: 'AnotherMessage'
+						label: LABEL_OPTIONAL
+						number: 1
+					>
+					field <
+						name: 'terminal'
+						type: TYPE_BOOL
+						label: LABEL_OPTIONAL
+						number: 2
+					>
+				>
+				message_type <
+					name: 'AnotherMessage'
+					field <
+						name: 'nested2'
+						type: TYPE_MESSAGE
+						type_name: 'ExampleMessage'
+						label: LABEL_OPTIONAL
+						number: 1
+					>
+				>
+			`,
+			path:    "nested.terminal.nested2",
+			wantErr: true,
+		},
+		// repeated field
+		{
+			src: `
+				name: 'example.proto'
+				package: 'example'
+				message_type <
+					name: 'ExampleMessage'
+					field <
+						name: 'nested'
+						type: TYPE_MESSAGE
+						type_name: 'AnotherMessage'
+						label: LABEL_OPTIONAL
+						number: 1
+					>
+					field <
+						name: 'terminal'
+						type: TYPE_BOOL
+						label: LABEL_OPTIONAL
+						number: 2
+					>
+				>
+				message_type <
+					name: 'AnotherMessage'
+					field <
+						name: 'nested2'
+						type: TYPE_MESSAGE
+						type_name: 'ExampleMessage'
+						label: LABEL_REPEATED
+						number: 1
+					>
+				>
+			`,
+			path:    "nested.nested2.terminal",
+			wantErr: true,
+		},
+	} {
+		var file descriptorpb.FileDescriptorProto
+		if err := prototext.Unmarshal([]byte(spec.src), &file); err != nil {
+			t.Fatalf("proto.Unmarshal(%s) failed with %v; want success", spec.src, err)
+		}
+		reg := NewRegistry()
+		reg.loadFile(file.GetName(), &protogen.File{
+			Proto: &file,
+		})
+		f, err := reg.LookupFile(file.GetName())
+		if err != nil {
+			t.Fatalf("reg.LookupFile(%q) failed with %v; want success; on file=%s", file.GetName(), err, spec.src)
+		}
+		_, err = reg.resolveFieldPath(f.Messages[0], spec.path, false)
+		if got, want := err != nil, spec.wantErr; got != want {
+			if want {
+				t.Errorf("reg.resolveFiledPath(%q, %q) succeeded; want an error", f.Messages[0].GetName(), spec.path)
+				continue
+			}
+			t.Errorf("reg.resolveFiledPath(%q, %q) failed with %v; want success", f.Messages[0].GetName(), spec.path, err)
+		}
+	}
+}
+
+func TestExtractServicesWithDeleteBody(t *testing.T) {
+	for _, spec := range []struct {
+		allowDeleteBody bool
+		expectErr       bool
+		target          string
+		srcs            []string
+	}{
+		// body for DELETE, but registry configured to allow it
+		{
+			allowDeleteBody: true,
+			expectErr:       false,
+			target:          "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "StringMessage"
+						field <
+							name: "string"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_STRING
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "RemoveResource"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									delete: "/v1/example/resource"
+									body: "string"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+		// body for DELETE, registry configured not to allow it
+		{
+			allowDeleteBody: false,
+			expectErr:       true,
+			target:          "path/to/example.proto",
+			srcs: []string{
+				`
+					name: "path/to/example.proto",
+					package: "example"
+					message_type <
+						name: "StringMessage"
+						field <
+							name: "string"
+							number: 1
+							label: LABEL_OPTIONAL
+							type: TYPE_STRING
+						>
+					>
+					service <
+						name: "ExampleService"
+						method <
+							name: "RemoveResource"
+							input_type: "StringMessage"
+							output_type: "StringMessage"
+							options <
+								[google.api.http] <
+									delete: "/v1/example/resource"
+									body: "string"
+								>
+							>
+						>
+					>
+				`,
+			},
+		},
+	} {
+		reg := NewRegistry()
+		reg.SetAllowDeleteBody(spec.allowDeleteBody)
+
+		for _, src := range spec.srcs {
+			var fd descriptorpb.FileDescriptorProto
+			if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+				t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+			}
+			reg.loadFile(fd.GetName(), &protogen.File{
+				Proto: &fd,
+			})
+		}
+		err := reg.loadServices(reg.files[spec.target])
+		if spec.expectErr && err == nil {
+			t.Errorf("loadServices(%q) succeeded; want an error; allowDeleteBody=%v, files=%v", spec.target, spec.allowDeleteBody, spec.srcs)
+		}
+		if !spec.expectErr && err != nil {
+			t.Errorf("loadServices(%q) failed; do not want an error; allowDeleteBody=%v, files=%v", spec.target, spec.allowDeleteBody, spec.srcs)
+		}
+		t.Log(err)
+	}
+}
+
+func TestCauseErrorWithPathParam(t *testing.T) {
+	src := `
+		name: "path/to/example.proto",
+		package: "example"
+		message_type <
+			name: "TypeMessage"
+			field <
+					name: "message"
+					type: TYPE_MESSAGE
+					type_name: 'ExampleMessage'
+					number: 1,
+					label: LABEL_OPTIONAL
+				>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "TypeMessage"
+				output_type: "TypeMessage"
+				options <
+					[google.api.http] <
+						get: "/v1/example/echo/{message=*}"
+					>
+				>
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+	target := "path/to/example.proto"
+	reg := NewRegistry()
+	input := []*descriptorpb.FileDescriptorProto{&fd}
+	reg.loadFile(fd.GetName(), &protogen.File{
+		Proto: &fd,
+	})
+	// switch this field to see the error
+	wantErr := true
+	err := reg.loadServices(reg.files[target])
+	if got, want := err != nil, wantErr; got != want {
+		if want {
+			t.Errorf("loadServices(%q, %q) succeeded; want an error", target, input)
+		}
+		t.Errorf("loadServices(%q, %q) failed with %v; want success", target, input, err)
+	}
+}
+
+func TestOptionalProto3URLPathMappingError(t *testing.T) {
+	src := `
+		name: "path/to/example.proto"
+		package: "example"
+		message_type <
+			name: "StringMessage"
+			field <
+				name: "field1"
+				number: 1
+				type: TYPE_STRING
+				proto3_optional: true
+			>
+		>
+		service <
+			name: "ExampleService"
+			method <
+				name: "Echo"
+				input_type: "StringMessage"
+				output_type: "StringMessage"
+				options <
+					[google.api.http] <
+						get: "/v1/example/echo/{field1=*}"
+					>
+				>
+			>
+		>
+	`
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+	target := "path/to/example.proto"
+	reg := NewRegistry()
+	input := []*descriptorpb.FileDescriptorProto{&fd}
+	reg.loadFile(fd.GetName(), &protogen.File{
+		Proto: &fd,
+	})
+	wantErrMsg := "field not allowed in field path: field1 in field1"
+	err := reg.loadServices(reg.files[target])
+	if err != nil {
+		if !strings.Contains(err.Error(), wantErrMsg) {
+			t.Errorf("loadServices(%q, %q) failed with %v; want %s", target, input, err, wantErrMsg)
+		}
+	} else {
+		t.Errorf("loadServices(%q, %q) expcted an error %s, got nil", target, input, wantErrMsg)
+	}
+}

+ 538 - 0
protoc-gen-openapiv2/internal/descriptor/types.go

@@ -0,0 +1,538 @@
+package descriptor
+
+import (
+	"fmt"
+	"strings"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/casing"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/httprule"
+	"google.golang.org/protobuf/types/descriptorpb"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+// IsWellKnownType returns true if the provided fully qualified type name is considered 'well-known'.
+func IsWellKnownType(typeName string) bool {
+	_, ok := wellKnownTypeConv[typeName]
+	return ok
+}
+
+// GoPackage represents a golang package.
+type GoPackage struct {
+	// Path is the package path to the package.
+	Path string
+	// Name is the package name of the package
+	Name string
+	// Alias is an alias of the package unique within the current invocation of gRPC-Gateway generator.
+	Alias string
+}
+
+// Standard returns whether the import is a golang standard package.
+func (p GoPackage) Standard() bool {
+	return !strings.Contains(p.Path, ".")
+}
+
+// String returns a string representation of this package in the form of import line in golang.
+func (p GoPackage) String() string {
+	if p.Alias == "" {
+		return fmt.Sprintf("%q", p.Path)
+	}
+	return fmt.Sprintf("%s %q", p.Alias, p.Path)
+}
+
+// ResponseFile wraps pluginpb.CodeGeneratorResponse_File.
+type ResponseFile struct {
+	*pluginpb.CodeGeneratorResponse_File
+	// GoPkg is the Go package of the generated file.
+	GoPkg GoPackage
+}
+
+// File wraps descriptorpb.FileDescriptorProto for richer features.
+type File struct {
+	*descriptorpb.FileDescriptorProto
+	// GoPkg is the go package of the go file generated from this file.
+	GoPkg GoPackage
+	// GeneratedFilenamePrefix is used to construct filenames for generated
+	// files associated with this source file.
+	//
+	// For example, the source file "dir/foo.proto" might have a filename prefix
+	// of "dir/foo". Appending ".pb.go" produces an output file of "dir/foo.pb.go".
+	GeneratedFilenamePrefix string
+	// Messages is the list of messages defined in this file.
+	Messages []*Message
+	// Enums is the list of enums defined in this file.
+	Enums []*Enum
+	// Services is the list of services defined in this file.
+	Services []*Service
+}
+
+// Pkg returns package name or alias if it's present
+func (f *File) Pkg() string {
+	pkg := f.GoPkg.Name
+	if alias := f.GoPkg.Alias; alias != "" {
+		pkg = alias
+	}
+	return pkg
+}
+
+// proto2 determines if the syntax of the file is proto2.
+func (f *File) proto2() bool {
+	return f.Syntax == nil || f.GetSyntax() == "proto2"
+}
+
+// Message describes a protocol buffer message types.
+type Message struct {
+	*descriptorpb.DescriptorProto
+	// File is the file where the message is defined.
+	File *File
+	// Outers is a list of outer messages if this message is a nested type.
+	Outers []string
+	// Fields is a list of message fields.
+	Fields []*Field
+	// Index is proto path index of this message in File.
+	Index int
+	// ForcePrefixedName when set to true, prefixes a type with a package prefix.
+	ForcePrefixedName bool
+}
+
+// FQMN returns a fully qualified message name of this message.
+func (m *Message) FQMN() string {
+	components := []string{""}
+	if m.File.Package != nil {
+		components = append(components, m.File.GetPackage())
+	}
+	components = append(components, m.Outers...)
+	components = append(components, m.GetName())
+	return strings.Join(components, ".")
+}
+
+// GoType returns a go type name for the message type.
+// It prefixes the type name with the package alias if
+// its belonging package is not "currentPackage".
+func (m *Message) GoType(currentPackage string) string {
+	var components []string
+	components = append(components, m.Outers...)
+	components = append(components, m.GetName())
+
+	name := strings.Join(components, "_")
+	if !m.ForcePrefixedName && m.File.GoPkg.Path == currentPackage {
+		return name
+	}
+	return fmt.Sprintf("%s.%s", m.File.Pkg(), name)
+}
+
+// Enum describes a protocol buffer enum types.
+type Enum struct {
+	*descriptorpb.EnumDescriptorProto
+	// File is the file where the enum is defined
+	File *File
+	// Outers is a list of outer messages if this enum is a nested type.
+	Outers []string
+	// Index is a enum index value.
+	Index int
+	// ForcePrefixedName when set to true, prefixes a type with a package prefix.
+	ForcePrefixedName bool
+}
+
+// FQEN returns a fully qualified enum name of this enum.
+func (e *Enum) FQEN() string {
+	components := []string{""}
+	if e.File.Package != nil {
+		components = append(components, e.File.GetPackage())
+	}
+	components = append(components, e.Outers...)
+	components = append(components, e.GetName())
+	return strings.Join(components, ".")
+}
+
+// GoType returns a go type name for the enum type.
+// It prefixes the type name with the package alias if
+// its belonging package is not "currentPackage".
+func (e *Enum) GoType(currentPackage string) string {
+	var components []string
+	components = append(components, e.Outers...)
+	components = append(components, e.GetName())
+
+	name := strings.Join(components, "_")
+	if !e.ForcePrefixedName && e.File.GoPkg.Path == currentPackage {
+		return name
+	}
+	return fmt.Sprintf("%s.%s", e.File.Pkg(), name)
+}
+
+// Service wraps descriptorpb.ServiceDescriptorProto for richer features.
+type Service struct {
+	*descriptorpb.ServiceDescriptorProto
+	// File is the file where this service is defined.
+	File *File
+	// Methods is the list of methods defined in this service.
+	Methods []*Method
+	// ForcePrefixedName when set to true, prefixes a type with a package prefix.
+	ForcePrefixedName bool
+}
+
+// FQSN returns the fully qualified service name of this service.
+func (s *Service) FQSN() string {
+	components := []string{""}
+	if s.File.Package != nil {
+		components = append(components, s.File.GetPackage())
+	}
+	components = append(components, s.GetName())
+	return strings.Join(components, ".")
+}
+
+// InstanceName returns object name of the service with package prefix if needed
+func (s *Service) InstanceName() string {
+	if !s.ForcePrefixedName {
+		return s.GetName()
+	}
+	return fmt.Sprintf("%s.%s", s.File.Pkg(), s.GetName())
+}
+
+// ClientConstructorName returns name of the Client constructor with package prefix if needed
+func (s *Service) ClientConstructorName() string {
+	constructor := "New" + s.GetName() + "Client"
+	if !s.ForcePrefixedName {
+		return constructor
+	}
+	return fmt.Sprintf("%s.%s", s.File.Pkg(), constructor)
+}
+
+// Method wraps descriptorpb.MethodDescriptorProto for richer features.
+type Method struct {
+	*descriptorpb.MethodDescriptorProto
+	// Service is the service which this method belongs to.
+	Service *Service
+	// RequestType is the message type of requests to this method.
+	RequestType *Message
+	// ResponseType is the message type of responses from this method.
+	ResponseType *Message
+	Bindings     []*Binding
+}
+
+// FQMN returns a fully qualified rpc method name of this method.
+func (m *Method) FQMN() string {
+	var components []string
+	components = append(components, m.Service.FQSN())
+	components = append(components, m.GetName())
+	return strings.Join(components, ".")
+}
+
+// Binding describes how an HTTP endpoint is bound to a gRPC method.
+type Binding struct {
+	// Method is the method which the endpoint is bound to.
+	Method *Method
+	// Index is a zero-origin index of the binding in the target method
+	Index int
+	// PathTmpl is path template where this method is mapped to.
+	PathTmpl httprule.Template
+	// HTTPMethod is the HTTP method which this method is mapped to.
+	HTTPMethod string
+	// PathParams is the list of parameters provided in HTTP request paths.
+	PathParams []Parameter
+	// Body describes parameters provided in HTTP request body.
+	Body *Body
+	// ResponseBody describes field in response struct to marshal in HTTP response body.
+	ResponseBody *Body
+}
+
+// ExplicitParams returns a list of explicitly bound parameters of "b",
+// i.e. a union of field path for body and field paths for path parameters.
+func (b *Binding) ExplicitParams() []string {
+	var result []string
+	if b.Body != nil {
+		result = append(result, b.Body.FieldPath.String())
+	}
+	for _, p := range b.PathParams {
+		result = append(result, p.FieldPath.String())
+	}
+	return result
+}
+
+// Field wraps descriptorpb.FieldDescriptorProto for richer features.
+type Field struct {
+	*descriptorpb.FieldDescriptorProto
+	// Message is the message type which this field belongs to.
+	Message *Message
+	// FieldMessage is the message type of the field.
+	FieldMessage *Message
+	// ForcePrefixedName when set to true, prefixes a type with a package prefix.
+	ForcePrefixedName bool
+}
+
+// FQFN returns a fully qualified field name of this field.
+func (f *Field) FQFN() string {
+	return strings.Join([]string{f.Message.FQMN(), f.GetName()}, ".")
+}
+
+// Parameter is a parameter provided in http requests
+type Parameter struct {
+	// FieldPath is a path to a proto field which this parameter is mapped to.
+	FieldPath
+	// Target is the proto field which this parameter is mapped to.
+	Target *Field
+	// Method is the method which this parameter is used for.
+	Method *Method
+}
+
+// ConvertFuncExpr returns a go expression of a converter function.
+// The converter function converts a string into a value for the parameter.
+func (p Parameter) ConvertFuncExpr() (string, error) {
+	tbl := proto3ConvertFuncs
+	if !p.IsProto2() && p.IsRepeated() {
+		tbl = proto3RepeatedConvertFuncs
+	} else if !p.IsProto2() && p.IsOptionalProto3() {
+		tbl = proto3OptionalConvertFuncs
+	} else if p.IsProto2() && !p.IsRepeated() {
+		tbl = proto2ConvertFuncs
+	} else if p.IsProto2() && p.IsRepeated() {
+		tbl = proto2RepeatedConvertFuncs
+	}
+	typ := p.Target.GetType()
+	conv, ok := tbl[typ]
+	if !ok {
+		conv, ok = wellKnownTypeConv[p.Target.GetTypeName()]
+	}
+	if !ok {
+		return "", fmt.Errorf("unsupported field type %s of parameter %s in %s.%s", typ, p.FieldPath, p.Method.Service.GetName(), p.Method.GetName())
+	}
+	return conv, nil
+}
+
+// IsEnum returns true if the field is an enum type, otherwise false is returned.
+func (p Parameter) IsEnum() bool {
+	return p.Target.GetType() == descriptorpb.FieldDescriptorProto_TYPE_ENUM
+}
+
+// IsRepeated returns true if the field is repeated, otherwise false is returned.
+func (p Parameter) IsRepeated() bool {
+	return p.Target.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED
+}
+
+// IsProto2 returns true if the field is proto2, otherwise false is returned.
+func (p Parameter) IsProto2() bool {
+	return p.Target.Message.File.proto2()
+}
+
+// Body describes a http (request|response) body to be sent to the (method|client).
+// This is used in body and response_body options in google.api.HttpRule
+type Body struct {
+	// FieldPath is a path to a proto field which the (request|response) body is mapped to.
+	// The (request|response) body is mapped to the (request|response) type itself if FieldPath is empty.
+	FieldPath FieldPath
+}
+
+// AssignableExpr returns an assignable expression in Go to be used to initialize method request object.
+// It starts with "msgExpr", which is the go expression of the method request object.
+func (b Body) AssignableExpr(msgExpr string) string {
+	return b.FieldPath.AssignableExpr(msgExpr)
+}
+
+// FieldPath is a path to a field from a request message.
+type FieldPath []FieldPathComponent
+
+// String returns a string representation of the field path.
+func (p FieldPath) String() string {
+	var components []string
+	for _, c := range p {
+		components = append(components, c.Name)
+	}
+	return strings.Join(components, ".")
+}
+
+// IsNestedProto3 indicates whether the FieldPath is a nested Proto3 path.
+func (p FieldPath) IsNestedProto3() bool {
+	if len(p) > 1 && !p[0].Target.Message.File.proto2() {
+		return true
+	}
+	return false
+}
+
+// IsOptionalProto3 indicates whether the FieldPath is a proto3 optional field.
+func (p FieldPath) IsOptionalProto3() bool {
+	if len(p) == 0 {
+		return false
+	}
+	return p[0].Target.GetProto3Optional()
+}
+
+// AssignableExpr is an assignable expression in Go to be used to assign a value to the target field.
+// It starts with "msgExpr", which is the go expression of the method request object.
+func (p FieldPath) AssignableExpr(msgExpr string) string {
+	l := len(p)
+	if l == 0 {
+		return msgExpr
+	}
+
+	var preparations []string
+	components := msgExpr
+	for i, c := range p {
+		// We need to check if the target is not proto3_optional first.
+		// Under the hood, proto3_optional uses oneof to signal to old proto3 clients
+		// that presence is tracked for this field. This oneof is known as a "synthetic" oneof.
+		if !c.Target.GetProto3Optional() && c.Target.OneofIndex != nil {
+			index := c.Target.OneofIndex
+			msg := c.Target.Message
+			oneOfName := casing.Camel(msg.GetOneofDecl()[*index].GetName())
+			oneofFieldName := msg.GetName() + "_" + c.AssignableExpr()
+
+			if c.Target.ForcePrefixedName {
+				oneofFieldName = msg.File.Pkg() + "." + oneofFieldName
+			}
+
+			components = components + "." + oneOfName
+			s := `if %s == nil {
+				%s =&%s{}
+			} else if _, ok := %s.(*%s); !ok {
+				return nil, metadata, status.Errorf(codes.InvalidArgument, "expect type: *%s, but: %%t\n",%s)
+			}`
+
+			preparations = append(preparations, fmt.Sprintf(s, components, components, oneofFieldName, components, oneofFieldName, oneofFieldName, components))
+			components = components + ".(*" + oneofFieldName + ")"
+		}
+
+		if i == l-1 {
+			components = components + "." + c.AssignableExpr()
+			continue
+		}
+		components = components + "." + c.ValueExpr()
+	}
+
+	preparations = append(preparations, components)
+	return strings.Join(preparations, "\n")
+}
+
+// FieldPathComponent is a path component in FieldPath
+type FieldPathComponent struct {
+	// Name is a name of the proto field which this component corresponds to.
+	// TODO(yugui) is this necessary?
+	Name string
+	// Target is the proto field which this component corresponds to.
+	Target *Field
+}
+
+// AssignableExpr returns an assignable expression in go for this field.
+func (c FieldPathComponent) AssignableExpr() string {
+	return casing.Camel(c.Name)
+}
+
+// ValueExpr returns an expression in go for this field.
+func (c FieldPathComponent) ValueExpr() string {
+	if c.Target.Message.File.proto2() {
+		return fmt.Sprintf("Get%s()", casing.Camel(c.Name))
+	}
+	return casing.Camel(c.Name)
+}
+
+var (
+	proto3ConvertFuncs = map[descriptorpb.FieldDescriptorProto_Type]string{
+		descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:  "runtime.Float64",
+		descriptorpb.FieldDescriptorProto_TYPE_FLOAT:   "runtime.Float32",
+		descriptorpb.FieldDescriptorProto_TYPE_INT64:   "runtime.Int64",
+		descriptorpb.FieldDescriptorProto_TYPE_UINT64:  "runtime.Uint64",
+		descriptorpb.FieldDescriptorProto_TYPE_INT32:   "runtime.Int32",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED64: "runtime.Uint64",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED32: "runtime.Uint32",
+		descriptorpb.FieldDescriptorProto_TYPE_BOOL:    "runtime.Bool",
+		descriptorpb.FieldDescriptorProto_TYPE_STRING:  "runtime.String",
+		// FieldDescriptorProto_TYPE_GROUP
+		// FieldDescriptorProto_TYPE_MESSAGE
+		descriptorpb.FieldDescriptorProto_TYPE_BYTES:    "runtime.Bytes",
+		descriptorpb.FieldDescriptorProto_TYPE_UINT32:   "runtime.Uint32",
+		descriptorpb.FieldDescriptorProto_TYPE_ENUM:     "runtime.Enum",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: "runtime.Int32",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: "runtime.Int64",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT32:   "runtime.Int32",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT64:   "runtime.Int64",
+	}
+
+	proto3OptionalConvertFuncs = func() map[descriptorpb.FieldDescriptorProto_Type]string {
+		result := make(map[descriptorpb.FieldDescriptorProto_Type]string)
+		for typ, converter := range proto3ConvertFuncs {
+			// TODO: this will use convert functions from proto2.
+			//       The converters returning pointers should be moved
+			//       to a more generic file.
+			result[typ] = converter + "P"
+		}
+		return result
+	}()
+
+	// TODO: replace it with a IIFE
+	proto3RepeatedConvertFuncs = map[descriptorpb.FieldDescriptorProto_Type]string{
+		descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:  "runtime.Float64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_FLOAT:   "runtime.Float32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_INT64:   "runtime.Int64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_UINT64:  "runtime.Uint64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_INT32:   "runtime.Int32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED64: "runtime.Uint64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED32: "runtime.Uint32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_BOOL:    "runtime.BoolSlice",
+		descriptorpb.FieldDescriptorProto_TYPE_STRING:  "runtime.StringSlice",
+		// FieldDescriptorProto_TYPE_GROUP
+		// FieldDescriptorProto_TYPE_MESSAGE
+		descriptorpb.FieldDescriptorProto_TYPE_BYTES:    "runtime.BytesSlice",
+		descriptorpb.FieldDescriptorProto_TYPE_UINT32:   "runtime.Uint32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_ENUM:     "runtime.EnumSlice",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: "runtime.Int32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: "runtime.Int64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT32:   "runtime.Int32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT64:   "runtime.Int64Slice",
+	}
+
+	proto2ConvertFuncs = map[descriptorpb.FieldDescriptorProto_Type]string{
+		descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:  "runtime.Float64P",
+		descriptorpb.FieldDescriptorProto_TYPE_FLOAT:   "runtime.Float32P",
+		descriptorpb.FieldDescriptorProto_TYPE_INT64:   "runtime.Int64P",
+		descriptorpb.FieldDescriptorProto_TYPE_UINT64:  "runtime.Uint64P",
+		descriptorpb.FieldDescriptorProto_TYPE_INT32:   "runtime.Int32P",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED64: "runtime.Uint64P",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED32: "runtime.Uint32P",
+		descriptorpb.FieldDescriptorProto_TYPE_BOOL:    "runtime.BoolP",
+		descriptorpb.FieldDescriptorProto_TYPE_STRING:  "runtime.StringP",
+		// FieldDescriptorProto_TYPE_GROUP
+		// FieldDescriptorProto_TYPE_MESSAGE
+		// FieldDescriptorProto_TYPE_BYTES
+		// TODO(yugui) Handle bytes
+		descriptorpb.FieldDescriptorProto_TYPE_UINT32:   "runtime.Uint32P",
+		descriptorpb.FieldDescriptorProto_TYPE_ENUM:     "runtime.EnumP",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: "runtime.Int32P",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: "runtime.Int64P",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT32:   "runtime.Int32P",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT64:   "runtime.Int64P",
+	}
+
+	proto2RepeatedConvertFuncs = map[descriptorpb.FieldDescriptorProto_Type]string{
+		descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:  "runtime.Float64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_FLOAT:   "runtime.Float32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_INT64:   "runtime.Int64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_UINT64:  "runtime.Uint64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_INT32:   "runtime.Int32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED64: "runtime.Uint64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_FIXED32: "runtime.Uint32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_BOOL:    "runtime.BoolSlice",
+		descriptorpb.FieldDescriptorProto_TYPE_STRING:  "runtime.StringSlice",
+		// FieldDescriptorProto_TYPE_GROUP
+		// FieldDescriptorProto_TYPE_MESSAGE
+		// FieldDescriptorProto_TYPE_BYTES
+		// TODO(maros7) Handle bytes
+		descriptorpb.FieldDescriptorProto_TYPE_UINT32:   "runtime.Uint32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_ENUM:     "runtime.EnumSlice",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: "runtime.Int32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: "runtime.Int64Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT32:   "runtime.Int32Slice",
+		descriptorpb.FieldDescriptorProto_TYPE_SINT64:   "runtime.Int64Slice",
+	}
+
+	wellKnownTypeConv = map[string]string{
+		".google.protobuf.Timestamp":   "runtime.Timestamp",
+		".google.protobuf.Duration":    "runtime.Duration",
+		".google.protobuf.StringValue": "runtime.StringValue",
+		".google.protobuf.FloatValue":  "runtime.FloatValue",
+		".google.protobuf.DoubleValue": "runtime.DoubleValue",
+		".google.protobuf.BoolValue":   "runtime.BoolValue",
+		".google.protobuf.BytesValue":  "runtime.BytesValue",
+		".google.protobuf.Int32Value":  "runtime.Int32Value",
+		".google.protobuf.UInt32Value": "runtime.UInt32Value",
+		".google.protobuf.Int64Value":  "runtime.Int64Value",
+		".google.protobuf.UInt64Value": "runtime.UInt64Value",
+	}
+)

+ 269 - 0
protoc-gen-openapiv2/internal/descriptor/types_test.go

@@ -0,0 +1,269 @@
+package descriptor
+
+import (
+	"testing"
+
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+func TestGoPackageStandard(t *testing.T) {
+	for _, spec := range []struct {
+		pkg  GoPackage
+		want bool
+	}{
+		{
+			pkg:  GoPackage{Path: "fmt", Name: "fmt"},
+			want: true,
+		},
+		{
+			pkg:  GoPackage{Path: "encoding/json", Name: "json"},
+			want: true,
+		},
+		{
+			pkg:  GoPackage{Path: "google.golang.org/protobuf/encoding/protojson", Name: "jsonpb"},
+			want: false,
+		},
+		{
+			pkg:  GoPackage{Path: "golang.org/x/net/context", Name: "context"},
+			want: false,
+		},
+		{
+			pkg:  GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway", Name: "main"},
+			want: false,
+		},
+		{
+			pkg:  GoPackage{Path: "github.com/google/googleapis/google/api/http.pb", Name: "http_pb", Alias: "htpb"},
+			want: false,
+		},
+	} {
+		if got, want := spec.pkg.Standard(), spec.want; got != want {
+			t.Errorf("%#v.Standard() = %v; want %v", spec.pkg, got, want)
+		}
+	}
+}
+
+func TestGoPackageString(t *testing.T) {
+	for _, spec := range []struct {
+		pkg  GoPackage
+		want string
+	}{
+		{
+			pkg:  GoPackage{Path: "fmt", Name: "fmt"},
+			want: `"fmt"`,
+		},
+		{
+			pkg:  GoPackage{Path: "encoding/json", Name: "json"},
+			want: `"encoding/json"`,
+		},
+		{
+			pkg:  GoPackage{Path: "google.golang.org/protobuf/encoding/protojson", Name: "jsonpb"},
+			want: `"google.golang.org/protobuf/encoding/protojson"`,
+		},
+		{
+			pkg:  GoPackage{Path: "golang.org/x/net/context", Name: "context"},
+			want: `"golang.org/x/net/context"`,
+		},
+		{
+			pkg:  GoPackage{Path: "github.com/grpc-ecosystem/grpc-gateway", Name: "main"},
+			want: `"github.com/grpc-ecosystem/grpc-gateway"`,
+		},
+		{
+			pkg:  GoPackage{Path: "github.com/google/googleapis/google/api/http.pb", Name: "http_pb", Alias: "htpb"},
+			want: `htpb "github.com/google/googleapis/google/api/http.pb"`,
+		},
+	} {
+		if got, want := spec.pkg.String(), spec.want; got != want {
+			t.Errorf("%#v.String() = %q; want %q", spec.pkg, got, want)
+		}
+	}
+}
+
+func TestFieldPath(t *testing.T) {
+	var fds []*descriptorpb.FileDescriptorProto
+	for _, src := range []string{
+		`
+		name: 'example.proto'
+		package: 'example'
+		message_type <
+			name: 'Nest'
+			field <
+				name: 'nest2_field'
+				label: LABEL_OPTIONAL
+				type: TYPE_MESSAGE
+				type_name: 'Nest2'
+				number: 1
+			>
+			field <
+				name: 'terminal_field'
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+				number: 2
+			>
+		>
+		syntax: "proto3"
+		`, `
+		name: 'another.proto'
+		package: 'example'
+		message_type <
+			name: 'Nest2'
+			field <
+				name: 'nest_field'
+				label: LABEL_OPTIONAL
+				type: TYPE_MESSAGE
+				type_name: 'Nest'
+				number: 1
+			>
+			field <
+				name: 'terminal_field'
+				label: LABEL_OPTIONAL
+				type: TYPE_STRING
+				number: 2
+			>
+		>
+		syntax: "proto2"
+		`,
+	} {
+		var fd descriptorpb.FileDescriptorProto
+		if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+			t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+		}
+		fds = append(fds, &fd)
+	}
+	nest1 := &Message{
+		DescriptorProto: fds[0].MessageType[0],
+		Fields: []*Field{
+			{FieldDescriptorProto: fds[0].MessageType[0].Field[0]},
+			{FieldDescriptorProto: fds[0].MessageType[0].Field[1]},
+		},
+	}
+	nest2 := &Message{
+		DescriptorProto: fds[1].MessageType[0],
+		Fields: []*Field{
+			{FieldDescriptorProto: fds[1].MessageType[0].Field[0]},
+			{FieldDescriptorProto: fds[1].MessageType[0].Field[1]},
+		},
+	}
+	file1 := &File{
+		FileDescriptorProto: fds[0],
+		GoPkg:               GoPackage{Path: "example", Name: "example"},
+		Messages:            []*Message{nest1},
+	}
+	file2 := &File{
+		FileDescriptorProto: fds[1],
+		GoPkg:               GoPackage{Path: "example", Name: "example"},
+		Messages:            []*Message{nest2},
+	}
+	crossLinkFixture(file1)
+	crossLinkFixture(file2)
+
+	c1 := FieldPathComponent{
+		Name:   "nest_field",
+		Target: nest2.Fields[0],
+	}
+	if got, want := c1.ValueExpr(), "GetNestField()"; got != want {
+		t.Errorf("c1.ValueExpr() = %q; want %q", got, want)
+	}
+	if got, want := c1.AssignableExpr(), "NestField"; got != want {
+		t.Errorf("c1.AssignableExpr() = %q; want %q", got, want)
+	}
+
+	c2 := FieldPathComponent{
+		Name:   "nest2_field",
+		Target: nest1.Fields[0],
+	}
+	if got, want := c2.ValueExpr(), "Nest2Field"; got != want {
+		t.Errorf("c2.ValueExpr() = %q; want %q", got, want)
+	}
+	if got, want := c2.ValueExpr(), "Nest2Field"; got != want {
+		t.Errorf("c2.ValueExpr() = %q; want %q", got, want)
+	}
+
+	fp := FieldPath{
+		c1, c2, c1, FieldPathComponent{
+			Name:   "terminal_field",
+			Target: nest1.Fields[1],
+		},
+	}
+	if got, want := fp.AssignableExpr("resp"), "resp.GetNestField().Nest2Field.GetNestField().TerminalField"; got != want {
+		t.Errorf("fp.AssignableExpr(%q) = %q; want %q", "resp", got, want)
+	}
+
+	fp2 := FieldPath{
+		c2, c1, c2, FieldPathComponent{
+			Name:   "terminal_field",
+			Target: nest2.Fields[1],
+		},
+	}
+	if got, want := fp2.AssignableExpr("resp"), "resp.Nest2Field.GetNestField().Nest2Field.TerminalField"; got != want {
+		t.Errorf("fp2.AssignableExpr(%q) = %q; want %q", "resp", got, want)
+	}
+
+	var fpEmpty FieldPath
+	if got, want := fpEmpty.AssignableExpr("resp"), "resp"; got != want {
+		t.Errorf("fpEmpty.AssignableExpr(%q) = %q; want %q", "resp", got, want)
+	}
+}
+
+func TestGoType(t *testing.T) {
+	src := `
+		name: 'example.proto'
+		package: 'example'
+		message_type <
+			name: 'Message'
+			field <
+				name: 'field'
+				type: TYPE_STRING
+				number: 1
+			>
+		>,
+		enum_type <
+			name: 'EnumName'
+		>,
+	`
+
+	var fd descriptorpb.FileDescriptorProto
+	if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
+		t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
+	}
+
+	msg := &Message{
+		DescriptorProto: fd.MessageType[0],
+		Fields: []*Field{
+			{FieldDescriptorProto: fd.MessageType[0].Field[0]},
+		},
+	}
+	enum := &Enum{
+		EnumDescriptorProto: fd.EnumType[0],
+	}
+	file := &File{
+		FileDescriptorProto: &fd,
+		GoPkg:               GoPackage{Path: "example", Name: "example"},
+		Messages:            []*Message{msg},
+		Enums:               []*Enum{enum},
+	}
+	crossLinkFixture(file)
+
+	if got, want := msg.GoType("example"), "Message"; got != want {
+		t.Errorf("msg.GoType() = %q; want %q", got, want)
+	}
+	if got, want := msg.GoType("extPackage"), "example.Message"; got != want {
+		t.Errorf("msg.GoType() = %q; want %q", got, want)
+	}
+	msg.ForcePrefixedName = true
+	if got, want := msg.GoType("example"), "example.Message"; got != want {
+		t.Errorf("msg.GoType() = %q; want %q", got, want)
+	}
+
+	if got, want := enum.GoType("example"), "EnumName"; got != want {
+		t.Errorf("enum.GoType() = %q; want %q", got, want)
+	}
+	if got, want := enum.GoType("extPackage"), "example.EnumName"; got != want {
+		t.Errorf("enum.GoType() = %q; want %q", got, want)
+	}
+	enum.ForcePrefixedName = true
+	if got, want := enum.GoType("example"), "example.EnumName"; got != want {
+		t.Errorf("enum.GoType() = %q; want %q", got, want)
+	}
+
+}

+ 12 - 0
protoc-gen-openapiv2/internal/generator/generator.go

@@ -0,0 +1,12 @@
+// Package generator provides an abstract interface to code generators.
+package generator
+
+import (
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor"
+)
+
+// Generator is an abstraction of code generators.
+type Generator interface {
+	// Generate generates output files from input .proto files.
+	Generate(targets []*descriptor.File) ([]*descriptor.ResponseFile, error)
+}

+ 41 - 0
protoc-gen-openapiv2/internal/genopenapi/cycle_test.go

@@ -0,0 +1,41 @@
+package genopenapi
+
+import (
+	"testing"
+)
+
+func TestCycle(t *testing.T) {
+	for _, tt := range []struct {
+		max     int
+		attempt int
+		e       bool
+	}{
+		{
+			max:     3,
+			attempt: 3,
+			e:       true,
+		},
+		{
+			max:     5,
+			attempt: 6,
+		},
+		{
+			max:     1000,
+			attempt: 1001,
+		},
+	} {
+
+		c := newCycleChecker(tt.max)
+		var final bool
+		for i := 0; i < tt.attempt; i++ {
+			final = c.Check("a")
+			if !final {
+				break
+			}
+		}
+
+		if final != tt.e {
+			t.Errorf("got: %t wanted: %t", final, tt.e)
+		}
+	}
+}

+ 2 - 0
protoc-gen-openapiv2/internal/genopenapi/doc.go

@@ -0,0 +1,2 @@
+// Package genopenapi provides a code generator for OpenAPI v2.
+package genopenapi

+ 247 - 0
protoc-gen-openapiv2/internal/genopenapi/generator.go

@@ -0,0 +1,247 @@
+package genopenapi
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"path/filepath"
+	"reflect"
+	"strings"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor"
+	gen "git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/generator"
+	"github.com/golang/glog"
+	anypb "github.com/golang/protobuf/ptypes/any"
+	openapi_options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
+	statuspb "google.golang.org/genproto/googleapis/rpc/status"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/descriptorpb"
+	"google.golang.org/protobuf/types/pluginpb"
+
+	//nolint:staticcheck // Known issue, will be replaced when possible
+	legacydescriptor "github.com/golang/protobuf/descriptor"
+)
+
+var (
+	errNoTargetService = errors.New("no target service defined in the file")
+)
+
+type generator struct {
+	reg *descriptor.Registry
+}
+
+type wrapper struct {
+	fileName string
+	swagger  *openapiSwaggerObject
+}
+
+type GeneratorOptions struct {
+	Registry       *descriptor.Registry
+	RecursiveDepth int
+}
+
+// New returns a new generator which generates grpc gateway files.
+func New(reg *descriptor.Registry) gen.Generator {
+	return &generator{reg: reg}
+}
+
+// Merge a lot of OpenAPI file (wrapper) to single one OpenAPI file
+func mergeTargetFile(targets []*wrapper, mergeFileName string) *wrapper {
+	var mergedTarget *wrapper
+	for _, f := range targets {
+		if mergedTarget == nil {
+			mergedTarget = &wrapper{
+				fileName: mergeFileName,
+				swagger:  f.swagger,
+			}
+		} else {
+			for k, v := range f.swagger.Definitions {
+				mergedTarget.swagger.Definitions[k] = v
+			}
+			for k, v := range f.swagger.Paths {
+				mergedTarget.swagger.Paths[k] = v
+			}
+			for k, v := range f.swagger.SecurityDefinitions {
+				mergedTarget.swagger.SecurityDefinitions[k] = v
+			}
+			mergedTarget.swagger.Security = append(mergedTarget.swagger.Security, f.swagger.Security...)
+		}
+	}
+	return mergedTarget
+}
+
+// Q: What's up with the alias types here?
+// A: We don't want to completely override how these structs are marshaled into
+//
+//	JSON, we only want to add fields (see below, extensionMarshalJSON).
+//	An infinite recursion would happen if we'd call json.Marshal on the struct
+//	that has swaggerObject as an embedded field. To avoid that, we'll create
+//	type aliases, and those don't have the custom MarshalJSON methods defined
+//	on them. See http://choly.ca/post/go-json-marshalling/ (or, if it ever
+//	goes away, use
+//	https://web.archive.org/web/20190806073003/http://choly.ca/post/go-json-marshalling/.
+func (so openapiSwaggerObject) MarshalJSON() ([]byte, error) {
+	type alias openapiSwaggerObject
+	return extensionMarshalJSON(alias(so), so.extensions)
+}
+
+func (so openapiInfoObject) MarshalJSON() ([]byte, error) {
+	type alias openapiInfoObject
+	return extensionMarshalJSON(alias(so), so.extensions)
+}
+
+func (so openapiSecuritySchemeObject) MarshalJSON() ([]byte, error) {
+	type alias openapiSecuritySchemeObject
+	return extensionMarshalJSON(alias(so), so.extensions)
+}
+
+func (so openapiOperationObject) MarshalJSON() ([]byte, error) {
+	type alias openapiOperationObject
+	return extensionMarshalJSON(alias(so), so.extensions)
+}
+
+func (so openapiResponseObject) MarshalJSON() ([]byte, error) {
+	type alias openapiResponseObject
+	return extensionMarshalJSON(alias(so), so.extensions)
+}
+
+func extensionMarshalJSON(so interface{}, extensions []extension) ([]byte, error) {
+	// To append arbitrary keys to the struct we'll render into json,
+	// we're creating another struct that embeds the original one, and
+	// its extra fields:
+	//
+	// The struct will look like
+	// struct {
+	//   *openapiCore
+	//   XGrpcGatewayFoo json.RawMessage `json:"x-grpc-gateway-foo"`
+	//   XGrpcGatewayBar json.RawMessage `json:"x-grpc-gateway-bar"`
+	// }
+	// and thus render into what we want -- the JSON of openapiCore with the
+	// extensions appended.
+	fields := []reflect.StructField{
+		{ // embedded
+			Name:      "Embedded",
+			Type:      reflect.TypeOf(so),
+			Anonymous: true,
+		},
+	}
+	for _, ext := range extensions {
+		fields = append(fields, reflect.StructField{
+			Name: fieldName(ext.key),
+			Type: reflect.TypeOf(ext.value),
+			Tag:  reflect.StructTag(fmt.Sprintf("json:\"%s\"", ext.key)),
+		})
+	}
+
+	t := reflect.StructOf(fields)
+	s := reflect.New(t).Elem()
+	s.Field(0).Set(reflect.ValueOf(so))
+	for _, ext := range extensions {
+		s.FieldByName(fieldName(ext.key)).Set(reflect.ValueOf(ext.value))
+	}
+	return json.Marshal(s.Interface())
+}
+
+// encodeOpenAPI converts OpenAPI file obj to pluginpb.CodeGeneratorResponse_File
+func encodeOpenAPI(file *wrapper) (*descriptor.ResponseFile, error) {
+	var formatted bytes.Buffer
+	enc := json.NewEncoder(&formatted)
+	enc.SetIndent("", "  ")
+	if err := enc.Encode(*file.swagger); err != nil {
+		return nil, err
+	}
+	name := file.fileName
+	ext := filepath.Ext(name)
+	base := strings.TrimSuffix(name, ext)
+	output := fmt.Sprintf("%s.swagger.json", base)
+	return &descriptor.ResponseFile{
+		CodeGeneratorResponse_File: &pluginpb.CodeGeneratorResponse_File{
+			Name:    proto.String(output),
+			Content: proto.String(formatted.String()),
+		},
+	}, nil
+}
+
+func (g *generator) Generate(targets []*descriptor.File) ([]*descriptor.ResponseFile, error) {
+	var files []*descriptor.ResponseFile
+	if g.reg.IsAllowMerge() {
+		var mergedTarget *descriptor.File
+		// try to find proto leader
+		for _, f := range targets {
+			if proto.HasExtension(f.Options, openapi_options.E_Openapiv2Swagger) {
+				mergedTarget = f
+				break
+			}
+		}
+		// merge protos to leader
+		for _, f := range targets {
+			if mergedTarget == nil {
+				mergedTarget = f
+			} else if mergedTarget != f {
+				mergedTarget.Enums = append(mergedTarget.Enums, f.Enums...)
+				mergedTarget.Messages = append(mergedTarget.Messages, f.Messages...)
+				mergedTarget.Services = append(mergedTarget.Services, f.Services...)
+			}
+		}
+
+		targets = nil
+		targets = append(targets, mergedTarget)
+	}
+
+	var openapis []*wrapper
+	for _, file := range targets {
+		glog.V(1).Infof("Processing %s", file.GetName())
+		swagger, err := applyTemplate(param{File: file, reg: g.reg})
+		if err == errNoTargetService {
+			glog.V(1).Infof("%s: %v", file.GetName(), err)
+			continue
+		}
+		if err != nil {
+			return nil, err
+		}
+		openapis = append(openapis, &wrapper{
+			fileName: file.GetName(),
+			swagger:  swagger,
+		})
+	}
+
+	if g.reg.IsAllowMerge() {
+		targetOpenAPI := mergeTargetFile(openapis, g.reg.GetMergeFileName())
+		f, err := encodeOpenAPI(targetOpenAPI)
+		if err != nil {
+			return nil, fmt.Errorf("failed to encode OpenAPI for %s: %s", g.reg.GetMergeFileName(), err)
+		}
+		files = append(files, f)
+		glog.V(1).Infof("New OpenAPI file will emit")
+	} else {
+		for _, file := range openapis {
+			f, err := encodeOpenAPI(file)
+			if err != nil {
+				return nil, fmt.Errorf("failed to encode OpenAPI for %s: %s", file.fileName, err)
+			}
+			files = append(files, f)
+			glog.V(1).Infof("New OpenAPI file will emit")
+		}
+	}
+	return files, nil
+}
+
+// AddErrorDefs Adds google.rpc.Status and google.protobuf.Any
+// to registry (used for error-related API responses)
+func AddErrorDefs(reg *descriptor.Registry) error {
+	// load internal protos
+	any, _ := legacydescriptor.MessageDescriptorProto(&anypb.Any{})
+	any.SourceCodeInfo = new(descriptorpb.SourceCodeInfo)
+	status, _ := legacydescriptor.MessageDescriptorProto(&statuspb.Status{})
+	status.SourceCodeInfo = new(descriptorpb.SourceCodeInfo)
+	// TODO(johanbrandhorst): Use new conversion later when possible
+	// any := protodesc.ToFileDescriptorProto((&anypb.Any{}).ProtoReflect().Descriptor().ParentFile())
+	// status := protodesc.ToFileDescriptorProto((&statuspb.Status{}).ProtoReflect().Descriptor().ParentFile())
+	return reg.Load(&pluginpb.CodeGeneratorRequest{
+		ProtoFile: []*descriptorpb.FileDescriptorProto{
+			any,
+			status,
+		},
+	})
+}

+ 10 - 0
protoc-gen-openapiv2/internal/genopenapi/helpers.go

@@ -0,0 +1,10 @@
+//go:build go1.12
+// +build go1.12
+
+package genopenapi
+
+import "strings"
+
+func fieldName(k string) string {
+	return strings.ReplaceAll(strings.Title(k), "-", "_")
+}

+ 10 - 0
protoc-gen-openapiv2/internal/genopenapi/helpers_go111_old.go

@@ -0,0 +1,10 @@
+//go:build !go1.12
+// +build !go1.12
+
+package genopenapi
+
+import "strings"
+
+func fieldName(k string) string {
+	return strings.Replace(strings.Title(k), "-", "_", -1)
+}

+ 2454 - 0
protoc-gen-openapiv2/internal/genopenapi/template.go

@@ -0,0 +1,2454 @@
+package genopenapi
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"math"
+	"net/textproto"
+	"reflect"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"text/template"
+	"time"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/casing"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor"
+	"github.com/golang/glog"
+	openapi_options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
+	"google.golang.org/genproto/googleapis/api/annotations"
+	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/descriptorpb"
+	"google.golang.org/protobuf/types/known/structpb"
+)
+
+// wktSchemas are the schemas of well-known-types.
+// The schemas must match with the behavior of the JSON unmarshaler in
+// https://github.com/protocolbuffers/protobuf-go/blob/v1.25.0/encoding/protojson/well_known_types.go
+var wktSchemas = map[string]schemaCore{
+	".google.protobuf.FieldMask": {
+		Type: "string",
+	},
+	".google.protobuf.Timestamp": {
+		Type:   "string",
+		Format: "date-time",
+	},
+	".google.protobuf.Duration": {
+		Type: "integer",
+	},
+	".google.protobuf.StringValue": {
+		Type: "string",
+	},
+	".google.protobuf.BytesValue": {
+		Type:   "string",
+		Format: "byte",
+	},
+	".google.protobuf.Int32Value": {
+		Type:   "integer",
+		Format: "int32",
+	},
+	".google.protobuf.UInt32Value": {
+		Type:   "integer",
+		Format: "int64",
+	},
+	".google.protobuf.Int64Value": {
+		Type:   "integer",
+		Format: "int64",
+	},
+	".google.protobuf.UInt64Value": {
+		Type:   "integer",
+		Format: "uint64",
+	},
+	".google.protobuf.FloatValue": {
+		Type:   "number",
+		Format: "float",
+	},
+	".google.protobuf.DoubleValue": {
+		Type:   "number",
+		Format: "double",
+	},
+	".google.protobuf.BoolValue": {
+		Type: "boolean",
+	},
+	".google.protobuf.Empty": {},
+	".google.protobuf.Struct": {
+		Type: "object",
+	},
+	".google.protobuf.Value": {
+		Type: "object",
+	},
+	".google.protobuf.ListValue": {
+		Type: "array",
+		Items: (*openapiItemsObject)(&schemaCore{
+			Type: "object",
+		}),
+	},
+	".google.protobuf.NullValue": {
+		Type: "string",
+	},
+}
+
+func listEnumNames(enum *descriptor.Enum) (names []string) {
+	for _, value := range enum.GetValue() {
+		names = append(names, value.GetName())
+	}
+	return names
+}
+
+func listEnumNumbers(enum *descriptor.Enum) (numbers []string) {
+	for _, value := range enum.GetValue() {
+		numbers = append(numbers, strconv.Itoa(int(value.GetNumber())))
+	}
+	return
+}
+
+func getEnumDefault(enum *descriptor.Enum) string {
+	for _, value := range enum.GetValue() {
+		if value.GetNumber() == 0 {
+			return value.GetName()
+		}
+	}
+	return ""
+}
+
+// messageToQueryParameters converts a message to a list of OpenAPI query parameters.
+func messageToQueryParameters(message *descriptor.Message, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body) (params []openapiParameterObject, err error) {
+	for _, field := range message.Fields {
+		p, err := queryParams(message, field, "", reg, pathParams, body, reg.GetRecursiveDepth())
+		if err != nil {
+			return nil, err
+		}
+		params = append(params, p...)
+	}
+	return params, nil
+}
+
+// queryParams converts a field to a list of OpenAPI query parameters recursively through the use of nestedQueryParams.
+func queryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, recursiveCount int) (params []openapiParameterObject, err error) {
+	return nestedQueryParams(message, field, prefix, reg, pathParams, body, newCycleChecker(recursiveCount))
+}
+
+type cycleChecker struct {
+	m     map[string]int
+	count int
+}
+
+func newCycleChecker(recursive int) *cycleChecker {
+	return &cycleChecker{
+		m:     make(map[string]int),
+		count: recursive,
+	}
+}
+
+// Check returns whether name is still within recursion
+// toleration
+func (c *cycleChecker) Check(name string) bool {
+	count, ok := c.m[name]
+	count = count + 1
+	isCycle := count > c.count
+
+	if isCycle {
+		return false
+	}
+
+	// provision map entry if not available
+	if !ok {
+		c.m[name] = 1
+		return true
+	}
+
+	c.m[name] = count
+
+	return true
+}
+
+func (c *cycleChecker) Branch() *cycleChecker {
+	copy := &cycleChecker{
+		count: c.count,
+		m:     map[string]int{},
+	}
+
+	for k, v := range c.m {
+		copy.m[k] = v
+	}
+
+	return copy
+}
+
+// nestedQueryParams converts a field to a list of OpenAPI query parameters recursively.
+// This function is a helper function for queryParams, that keeps track of cyclical message references
+//
+//	through the use of
+//	    touched map[string]int
+//
+// If a cycle is discovered, an error is returned, as cyclical data structures are dangerous
+//
+//	in query parameters.
+func nestedQueryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, cycle *cycleChecker) (params []openapiParameterObject, err error) {
+	// make sure the parameter is not already listed as a path parameter
+	for _, pathParam := range pathParams {
+		if pathParam.Target == field {
+			return nil, nil
+		}
+	}
+	// make sure the parameter is not already listed as a body parameter
+	if body != nil {
+		if body.FieldPath == nil {
+			return nil, nil
+		}
+		for _, fieldPath := range body.FieldPath {
+			if fieldPath.Target == field {
+				return nil, nil
+			}
+		}
+	}
+	schema := schemaOfField(field, reg, nil)
+	fieldType := field.GetTypeName()
+	if message.File != nil {
+		comments := fieldProtoComments(reg, message, field)
+		if err := updateOpenAPIDataFromComments(reg, &schema, message, comments, false); err != nil {
+			return nil, err
+		}
+	}
+
+	isEnum := field.GetType() == descriptorpb.FieldDescriptorProto_TYPE_ENUM
+	items := schema.Items
+	if schema.Type != "" || isEnum {
+		if schema.Type == "object" {
+			return nil, nil // TODO: currently, mapping object in query parameter is not supported
+		}
+		if items != nil && (items.Type == "" || items.Type == "object") && !isEnum {
+			return nil, nil // TODO: currently, mapping object in query parameter is not supported
+		}
+		desc := schema.Description
+		if schema.Title != "" { // merge title because title of parameter object will be ignored
+			desc = strings.TrimSpace(schema.Title + ". " + schema.Description)
+		}
+
+		// verify if the field is required
+		required := false
+		for _, fieldName := range schema.Required {
+			if fieldName == field.GetName() {
+				required = true
+				break
+			}
+		}
+
+		param := openapiParameterObject{
+			Description: desc,
+			In:          "query",
+			Default:     schema.Default,
+			Type:        schema.Type,
+			Items:       schema.Items,
+			Format:      schema.Format,
+			Required:    required,
+		}
+		if param.Type == "array" {
+			param.CollectionFormat = "multi"
+		}
+
+		param.Name = prefix + reg.FieldName(field)
+
+		if isEnum {
+			enum, err := reg.LookupEnum("", fieldType)
+			if err != nil {
+				return nil, fmt.Errorf("unknown enum type %s", fieldType)
+			}
+			if items != nil { // array
+				param.Items = &openapiItemsObject{
+					Type: "string",
+					Enum: listEnumNames(enum),
+				}
+				if reg.GetEnumsAsInts() {
+					param.Items.Type = "integer"
+					param.Items.Enum = listEnumNumbers(enum)
+				}
+			} else {
+				param.Type = "string"
+				param.Enum = listEnumNames(enum)
+				param.Default = getEnumDefault(enum)
+				if reg.GetEnumsAsInts() {
+					param.Type = "integer"
+					param.Enum = listEnumNumbers(enum)
+					param.Default = "0"
+				}
+			}
+			valueComments := enumValueProtoComments(reg, enum)
+			if valueComments != "" {
+				param.Description = strings.TrimLeft(param.Description+"\n\n "+valueComments, "\n")
+			}
+		}
+		return []openapiParameterObject{param}, nil
+	}
+
+	// nested type, recurse
+	msg, err := reg.LookupMsg("", fieldType)
+	if err != nil {
+		return nil, fmt.Errorf("unknown message type %s", fieldType)
+	}
+
+	// Check for cyclical message reference:
+	isOK := cycle.Check(*msg.Name)
+	if !isOK {
+		return nil, fmt.Errorf("exceeded recursive count (%d) for query parameter %q", cycle.count, fieldType)
+	}
+
+	// Construct a new map with the message name so a cycle further down the recursive path can be detected.
+	// Do not keep anything in the original touched reference and do not pass that reference along.  This will
+	// prevent clobbering adjacent records while recursing.
+	touchedOut := cycle.Branch()
+
+	for _, nestedField := range msg.Fields {
+		fieldName := reg.FieldName(field)
+		p, err := nestedQueryParams(msg, nestedField, prefix+fieldName+".", reg, pathParams, body, touchedOut)
+		if err != nil {
+			return nil, err
+		}
+		params = append(params, p...)
+	}
+	return params, nil
+}
+
+// findServicesMessagesAndEnumerations discovers all messages and enums defined in the RPC methods of the service.
+func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descriptor.Registry, m messageMap, ms messageMap, e enumMap, refs refMap) {
+	for _, svc := range s {
+		for _, meth := range svc.Methods {
+			// Request may be fully included in query
+			{
+				swgReqName, ok := fullyQualifiedNameToOpenAPIName(meth.RequestType.FQMN(), reg)
+				if !ok {
+					glog.Errorf("couldn't resolve OpenAPI name for FQMN '%v'", meth.RequestType.FQMN())
+					continue
+				}
+				if _, ok := refs[fmt.Sprintf("#/definitions/%s", swgReqName)]; ok {
+					if !skipRenderingRef(meth.RequestType.FQMN()) {
+						m[swgReqName] = meth.RequestType
+					}
+				}
+			}
+
+			swgRspName, ok := fullyQualifiedNameToOpenAPIName(meth.ResponseType.FQMN(), reg)
+			if !ok && !skipRenderingRef(meth.ResponseType.FQMN()) {
+				glog.Errorf("couldn't resolve OpenAPI name for FQMN '%v'", meth.ResponseType.FQMN())
+				continue
+			}
+
+			findNestedMessagesAndEnumerations(meth.RequestType, reg, m, e)
+
+			if !skipRenderingRef(meth.ResponseType.FQMN()) {
+				m[swgRspName] = meth.ResponseType
+			}
+			findNestedMessagesAndEnumerations(meth.ResponseType, reg, m, e)
+		}
+	}
+}
+
+// findNestedMessagesAndEnumerations those can be generated by the services.
+func findNestedMessagesAndEnumerations(message *descriptor.Message, reg *descriptor.Registry, m messageMap, e enumMap) {
+	// Iterate over all the fields that
+	for _, t := range message.Fields {
+		fieldType := t.GetTypeName()
+		// If the type is an empty string then it is a proto primitive
+		if fieldType != "" {
+			if _, ok := m[fieldType]; !ok {
+				msg, err := reg.LookupMsg("", fieldType)
+				if err != nil {
+					enum, err := reg.LookupEnum("", fieldType)
+					if err != nil {
+						panic(err)
+					}
+					e[fieldType] = enum
+					continue
+				}
+				m[fieldType] = msg
+				findNestedMessagesAndEnumerations(msg, reg, m, e)
+			}
+		}
+	}
+}
+
+func skipRenderingRef(refName string) bool {
+	_, ok := wktSchemas[refName]
+	return ok
+}
+
+func renderMessageAsDefinition(msg *descriptor.Message, reg *descriptor.Registry, customRefs refMap, excludeFields []*descriptor.Field) openapiSchemaObject {
+	schema := openapiSchemaObject{
+		schemaCore: schemaCore{
+			Type: "object",
+		},
+	}
+	msgComments := protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index))
+	if err := updateOpenAPIDataFromComments(reg, &schema, msg, msgComments, false); err != nil {
+		panic(err)
+	}
+	opts, err := getMessageOpenAPIOption(reg, msg)
+	if err != nil {
+		panic(err)
+	}
+	if opts != nil {
+		protoSchema := openapiSchemaFromProtoSchema(opts, reg, customRefs, msg)
+
+		// Warning: Make sure not to overwrite any fields already set on the schema type.
+		schema.ExternalDocs = protoSchema.ExternalDocs
+		schema.ReadOnly = protoSchema.ReadOnly
+		schema.MultipleOf = protoSchema.MultipleOf
+		schema.Maximum = protoSchema.Maximum
+		schema.ExclusiveMaximum = protoSchema.ExclusiveMaximum
+		schema.Minimum = protoSchema.Minimum
+		schema.ExclusiveMinimum = protoSchema.ExclusiveMinimum
+		schema.MaxLength = protoSchema.MaxLength
+		schema.MinLength = protoSchema.MinLength
+		schema.Pattern = protoSchema.Pattern
+		schema.Default = protoSchema.Default
+		schema.MaxItems = protoSchema.MaxItems
+		schema.MinItems = protoSchema.MinItems
+		schema.UniqueItems = protoSchema.UniqueItems
+		schema.MaxProperties = protoSchema.MaxProperties
+		schema.MinProperties = protoSchema.MinProperties
+		schema.Required = protoSchema.Required
+		schema.XNullable = protoSchema.XNullable
+		if protoSchema.schemaCore.Type != "" || protoSchema.schemaCore.Ref != "" {
+			schema.schemaCore = protoSchema.schemaCore
+		}
+		if protoSchema.Title != "" {
+			schema.Title = protoSchema.Title
+		}
+		if protoSchema.Description != "" {
+			schema.Description = protoSchema.Description
+		}
+		if protoSchema.Example != nil {
+			schema.Example = protoSchema.Example
+		}
+	}
+
+	schema.Required = filterOutExcludedFields(schema.Required, excludeFields, reg)
+
+	for _, f := range msg.Fields {
+		if shouldExcludeField(reg.FieldName(f), excludeFields, reg) {
+			continue
+		}
+		fieldValue := schemaOfField(f, reg, customRefs)
+		comments := fieldProtoComments(reg, msg, f)
+		if err := updateOpenAPIDataFromComments(reg, &fieldValue, f, comments, false); err != nil {
+			panic(err)
+		}
+
+		if requiredIdx := find(schema.Required, *f.Name); requiredIdx != -1 && reg.GetUseJSONNamesForFields() {
+			schema.Required[requiredIdx] = f.GetJsonName()
+		}
+
+		if fieldValue.Required != nil {
+			for _, req := range fieldValue.Required {
+				if reg.GetUseJSONNamesForFields() {
+					schema.Required = append(schema.Required, f.GetJsonName())
+				} else {
+					schema.Required = append(schema.Required, req)
+				}
+			}
+		}
+
+		kv := keyVal{Value: fieldValue}
+		kv.Key = reg.FieldName(f)
+		if schema.Properties == nil {
+			schema.Properties = &openapiSchemaObjectProperties{}
+		}
+		*schema.Properties = append(*schema.Properties, kv)
+	}
+	return schema
+}
+
+func renderMessagesAsDefinition(messages messageMap, d openapiDefinitionsObject, reg *descriptor.Registry, customRefs refMap, excludeFields []*descriptor.Field) {
+	for name, msg := range messages {
+		swgName, ok := fullyQualifiedNameToOpenAPIName(msg.FQMN(), reg)
+		if !ok {
+			panic(fmt.Sprintf("can't resolve OpenAPI name from '%v'", msg.FQMN()))
+		}
+		if skipRenderingRef(name) {
+			continue
+		}
+
+		if opt := msg.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
+			continue
+		}
+		d[swgName] = renderMessageAsDefinition(msg, reg, customRefs, excludeFields)
+	}
+}
+
+func shouldExcludeField(name string, excluded []*descriptor.Field, reg *descriptor.Registry) bool {
+	for _, f := range excluded {
+		if name == reg.FieldName(f) {
+			return true
+		}
+	}
+	return false
+}
+func filterOutExcludedFields(fields []string, excluded []*descriptor.Field, reg *descriptor.Registry) []string {
+	var filtered []string
+	for _, f := range fields {
+		if !shouldExcludeField(f, excluded, reg) {
+			filtered = append(filtered, f)
+		}
+	}
+	return filtered
+}
+
+// schemaOfField returns a OpenAPI Schema Object for a protobuf field.
+func schemaOfField(f *descriptor.Field, reg *descriptor.Registry, refs refMap) openapiSchemaObject {
+	const (
+		singular = 0
+		array    = 1
+		object   = 2
+	)
+	var (
+		core      schemaCore
+		aggregate int
+	)
+
+	fd := f.FieldDescriptorProto
+	if m, err := reg.LookupMsg("", f.GetTypeName()); err == nil {
+		if opt := m.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
+			fd = m.GetField()[1]
+			aggregate = object
+		}
+	}
+	if fd.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
+		aggregate = array
+	}
+
+	var props *openapiSchemaObjectProperties
+
+	switch ft := fd.GetType(); ft {
+	case descriptorpb.FieldDescriptorProto_TYPE_ENUM, descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
+		if wktSchema, ok := wktSchemas[fd.GetTypeName()]; ok {
+			core = wktSchema
+
+			if fd.GetTypeName() == ".google.protobuf.Empty" {
+				props = &openapiSchemaObjectProperties{}
+			}
+		} else {
+			swgRef, ok := fullyQualifiedNameToOpenAPIName(fd.GetTypeName(), reg)
+			if !ok {
+				panic(fmt.Sprintf("can't resolve OpenAPI ref from typename '%v'", fd.GetTypeName()))
+			}
+			core = schemaCore{
+				Ref: "#/definitions/" + swgRef,
+			}
+			if refs != nil {
+				refs[fd.GetTypeName()] = struct{}{}
+			}
+		}
+	default:
+		ftype, format, ok := primitiveSchema(ft)
+		if ok {
+			core = schemaCore{Type: ftype, Format: format}
+		} else {
+			core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
+		}
+	}
+
+	ret := openapiSchemaObject{}
+
+	switch aggregate {
+	case array:
+		ret = openapiSchemaObject{
+			schemaCore: schemaCore{
+				Type:  "array",
+				Items: (*openapiItemsObject)(&core),
+			},
+		}
+	case object:
+		ret = openapiSchemaObject{
+			schemaCore: schemaCore{
+				Type: "object",
+			},
+			AdditionalProperties: &openapiSchemaObject{Properties: props, schemaCore: core},
+		}
+	default:
+		ret = openapiSchemaObject{
+			schemaCore: core,
+			Properties: props,
+		}
+	}
+
+	if j, err := getFieldOpenAPIOption(reg, f); err == nil {
+		updateswaggerObjectFromJSONSchema(&ret, j, reg, f)
+	}
+
+	if j, err := getFieldBehaviorOption(reg, f); err == nil {
+		updateSwaggerObjectFromFieldBehavior(&ret, j, f)
+	}
+
+	if reg.GetProto3OptionalNullable() && f.GetProto3Optional() {
+		ret.XNullable = true
+	}
+
+	return ret
+}
+
+// primitiveSchema returns a pair of "Type" and "Format" in JSON Schema for
+// the given primitive field type.
+// The last return parameter is true iff the field type is actually primitive.
+func primitiveSchema(t descriptorpb.FieldDescriptorProto_Type) (ftype, format string, ok bool) {
+	switch t {
+	case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:
+		return "number", "double", true
+	case descriptorpb.FieldDescriptorProto_TYPE_FLOAT:
+		return "number", "float", true
+	case descriptorpb.FieldDescriptorProto_TYPE_INT64:
+		return "integer", "int64", true
+	case descriptorpb.FieldDescriptorProto_TYPE_UINT64:
+		// 64bit integer types are marshaled as string in the default JSONPb marshaler.
+		// TODO(yugui) Add an option to declare 64bit integers as int64.
+		//
+		// NOTE: uint64 is not a predefined format of integer type in OpenAPI spec.
+		// So we cannot expect that uint64 is commonly supported by OpenAPI processor.
+		return "integer", "uint64", true
+	case descriptorpb.FieldDescriptorProto_TYPE_INT32:
+		return "integer", "int32", true
+	case descriptorpb.FieldDescriptorProto_TYPE_FIXED64:
+		// Ditto.
+		return "integer", "uint64", true
+	case descriptorpb.FieldDescriptorProto_TYPE_FIXED32:
+		// Ditto.
+		return "integer", "int64", true
+	case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
+		// NOTE: in OpenAPI specification, format should be empty on boolean type
+		return "boolean", "", true
+	case descriptorpb.FieldDescriptorProto_TYPE_STRING:
+		// NOTE: in OpenAPI specification, format should be empty on string type
+		return "string", "", true
+	case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
+		return "string", "byte", true
+	case descriptorpb.FieldDescriptorProto_TYPE_UINT32:
+		// Ditto.
+		return "integer", "int64", true
+	case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32:
+		return "integer", "int32", true
+	case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64:
+		return "integer", "int64", true
+	case descriptorpb.FieldDescriptorProto_TYPE_SINT32:
+		return "integer", "int32", true
+	case descriptorpb.FieldDescriptorProto_TYPE_SINT64:
+		return "integer", "int64", true
+	default:
+		return "", "", false
+	}
+}
+
+// renderEnumerationsAsDefinition inserts enums into the definitions object.
+func renderEnumerationsAsDefinition(enums enumMap, d openapiDefinitionsObject, reg *descriptor.Registry) {
+	for _, enum := range enums {
+		swgName, ok := fullyQualifiedNameToOpenAPIName(enum.FQEN(), reg)
+		if !ok {
+			panic(fmt.Sprintf("can't resolve OpenAPI name from FQEN '%v'", enum.FQEN()))
+		}
+		enumComments := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index))
+
+		// it may be necessary to sort the result of the GetValue function.
+		enumNames := listEnumNames(enum)
+		defaultValue := getEnumDefault(enum)
+		valueComments := enumValueProtoComments(reg, enum)
+		if valueComments != "" {
+			enumComments = strings.TrimLeft(enumComments+"\n\n "+valueComments, "\n")
+		}
+		enumSchemaObject := openapiSchemaObject{
+			schemaCore: schemaCore{
+				Type:    "string",
+				Enum:    enumNames,
+				Default: defaultValue,
+			},
+		}
+		if reg.GetEnumsAsInts() {
+			enumSchemaObject.Type = "integer"
+			enumSchemaObject.Format = "int32"
+			enumSchemaObject.Default = "0"
+			enumSchemaObject.Enum = listEnumNumbers(enum)
+		}
+		if err := updateOpenAPIDataFromComments(reg, &enumSchemaObject, enum, enumComments, false); err != nil {
+			panic(err)
+		}
+
+		d[swgName] = enumSchemaObject
+	}
+}
+
+// Take in a FQMN or FQEN and return a OpenAPI safe version of the FQMN and
+// a boolean indicating if FQMN was properly resolved.
+func fullyQualifiedNameToOpenAPIName(fqn string, reg *descriptor.Registry) (string, bool) {
+	registriesSeenMutex.Lock()
+	defer registriesSeenMutex.Unlock()
+	if mapping, present := registriesSeen[reg]; present {
+		ret, ok := mapping[fqn]
+		return ret, ok
+	}
+	mapping := resolveFullyQualifiedNameToOpenAPINames(append(reg.GetAllFQMNs(), reg.GetAllFQENs()...), reg.GetUseFQNForOpenAPIName())
+	registriesSeen[reg] = mapping
+	ret, ok := mapping[fqn]
+	return ret, ok
+}
+
+// Lookup message type by location.name and return a openapiv2-safe version
+// of its FQMN.
+func lookupMsgAndOpenAPIName(location, name string, reg *descriptor.Registry) (*descriptor.Message, string, error) {
+	msg, err := reg.LookupMsg(location, name)
+	if err != nil {
+		return nil, "", err
+	}
+	swgName, ok := fullyQualifiedNameToOpenAPIName(msg.FQMN(), reg)
+	if !ok {
+		return nil, "", fmt.Errorf("can't map OpenAPI name from FQMN '%v'", msg.FQMN())
+	}
+	return msg, swgName, nil
+}
+
+// registriesSeen is used to memoise calls to resolveFullyQualifiedNameToOpenAPINames so
+// we don't repeat it unnecessarily, since it can take some time.
+var registriesSeen = map[*descriptor.Registry]map[string]string{}
+var registriesSeenMutex sync.Mutex
+
+// Take the names of every proto and "uniq-ify" them. The idea is to produce a
+// set of names that meet a couple of conditions. They must be stable, they
+// must be unique, and they must be shorter than the FQN.
+//
+// This likely could be made better. This will always generate the same names
+// but may not always produce optimal names. This is a reasonably close
+// approximation of what they should look like in most cases.
+func resolveFullyQualifiedNameToOpenAPINames(messages []string, useFQNForOpenAPIName bool) map[string]string {
+	packagesByDepth := make(map[int][][]string)
+	uniqueNames := make(map[string]string)
+
+	hierarchy := func(pkg string) []string {
+		return strings.Split(pkg, ".")
+	}
+
+	for _, p := range messages {
+		h := hierarchy(p)
+		for depth := range h {
+			if _, ok := packagesByDepth[depth]; !ok {
+				packagesByDepth[depth] = make([][]string, 0)
+			}
+			packagesByDepth[depth] = append(packagesByDepth[depth], h[len(h)-depth:])
+		}
+	}
+
+	count := func(list [][]string, item []string) int {
+		i := 0
+		for _, element := range list {
+			if reflect.DeepEqual(element, item) {
+				i++
+			}
+		}
+		return i
+	}
+
+	for _, p := range messages {
+		if useFQNForOpenAPIName {
+			// strip leading dot from proto fqn
+			uniqueNames[p] = p[1:]
+		} else {
+			h := hierarchy(p)
+			for depth := 0; depth < len(h); depth++ {
+				if count(packagesByDepth[depth], h[len(h)-depth:]) == 1 {
+					uniqueNames[p] = strings.Join(h[len(h)-depth-1:], "")
+					break
+				}
+				if depth == len(h)-1 {
+					uniqueNames[p] = strings.Join(h, "")
+				}
+			}
+		}
+	}
+	return uniqueNames
+}
+
+var canRegexp = regexp.MustCompile("{([a-zA-Z][a-zA-Z0-9_.]*).*}")
+
+// OpenAPI expects paths of the form /path/{string_value} but gRPC-Gateway paths are expected to be of the form /path/{string_value=strprefix/*}. This should reformat it correctly.
+func templateToOpenAPIPath(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message) string {
+	// It seems like the right thing to do here is to just use
+	// strings.Split(path, "/") but that breaks badly when you hit a url like
+	// /{my_field=prefix/*}/ and end up with 2 sections representing my_field.
+	// Instead do the right thing and write a small pushdown (counter) automata
+	// for it.
+	var parts []string
+	depth := 0
+	buffer := ""
+	jsonBuffer := ""
+	for _, char := range path {
+		switch char {
+		case '{':
+			// Push on the stack
+			depth++
+			buffer += string(char)
+			jsonBuffer = ""
+			jsonBuffer += string(char)
+		case '}':
+			if depth == 0 {
+				panic("Encountered } without matching { before it.")
+			}
+			// Pop from the stack
+			depth--
+			buffer += string(char)
+			if reg.GetUseJSONNamesForFields() &&
+				len(jsonBuffer) > 1 {
+				jsonSnakeCaseName := string(jsonBuffer[1:])
+				jsonCamelCaseName := string(lowerCamelCase(jsonSnakeCaseName, fields, msgs))
+				prev := string(buffer[:len(buffer)-len(jsonSnakeCaseName)-2])
+				buffer = strings.Join([]string{prev, "{", jsonCamelCaseName, "}"}, "")
+				jsonBuffer = ""
+			}
+		case '/':
+			if depth == 0 {
+				parts = append(parts, buffer)
+				buffer = ""
+				// Since the stack was empty when we hit the '/' we are done with this
+				// section.
+				continue
+			}
+			buffer += string(char)
+			jsonBuffer += string(char)
+		default:
+			buffer += string(char)
+			jsonBuffer += string(char)
+		}
+	}
+
+	// Now append the last element to parts
+	parts = append(parts, buffer)
+
+	// Parts is now an array of segments of the path. Interestingly, since the
+	// syntax for this subsection CAN be handled by a regexp since it has no
+	// memory.
+	for index, part := range parts {
+		// If part is a resource name such as "parent", "name", "user.name", the format info must be retained.
+		prefix := canRegexp.ReplaceAllString(part, "$1")
+		if isResourceName(prefix) {
+			continue
+		}
+		parts[index] = canRegexp.ReplaceAllString(part, "{$1}")
+	}
+
+	return strings.Join(parts, "/")
+}
+
+func isResourceName(prefix string) bool {
+	words := strings.Split(prefix, ".")
+	l := len(words)
+	field := words[l-1]
+	words = strings.Split(field, ":")
+	field = words[0]
+	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
+	var lastFile *descriptor.File = nil
+	for svcIdx, svc := range services {
+		if svc.File != lastFile {
+			lastFile = svc.File
+			svcBaseIdx = svcIdx
+		}
+		for methIdx, meth := range svc.Methods {
+			for bIdx, b := range meth.Bindings {
+				// Iterate over all the OpenAPI parameters
+				parameters := openapiParametersObject{}
+				for _, parameter := range b.PathParams {
+
+					var paramType, paramFormat, desc, collectionFormat, defaultValue string
+					var enumNames []string
+					var items *openapiItemsObject
+					var minItems *int
+					switch pt := parameter.Target.GetType(); pt {
+					case descriptorpb.FieldDescriptorProto_TYPE_GROUP, descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
+						if descriptor.IsWellKnownType(parameter.Target.GetTypeName()) {
+							if parameter.IsRepeated() {
+								return fmt.Errorf("only primitive and enum types are allowed in repeated path parameters")
+							}
+							schema := schemaOfField(parameter.Target, reg, customRefs)
+							paramType = schema.Type
+							paramFormat = schema.Format
+							desc = schema.Description
+							defaultValue = schema.Default
+						} else {
+							return fmt.Errorf("only primitive and well-known types are allowed in path parameters")
+						}
+					case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
+						enum, err := reg.LookupEnum("", parameter.Target.GetTypeName())
+						if err != nil {
+							return err
+						}
+						paramType = "string"
+						paramFormat = ""
+						enumNames = listEnumNames(enum)
+						if reg.GetEnumsAsInts() {
+							paramType = "integer"
+							paramFormat = ""
+							enumNames = listEnumNumbers(enum)
+						}
+						schema := schemaOfField(parameter.Target, reg, customRefs)
+						desc = schema.Description
+						defaultValue = schema.Default
+					default:
+						var ok bool
+						paramType, paramFormat, ok = primitiveSchema(pt)
+						if !ok {
+							return fmt.Errorf("unknown field type %v", pt)
+						}
+
+						schema := schemaOfField(parameter.Target, reg, customRefs)
+						desc = schema.Description
+						defaultValue = schema.Default
+					}
+
+					if parameter.IsRepeated() {
+						core := schemaCore{Type: paramType, Format: paramFormat}
+						if parameter.IsEnum() {
+							var s []string
+							core.Enum = enumNames
+							enumNames = s
+						}
+						items = (*openapiItemsObject)(&core)
+						paramType = "array"
+						paramFormat = ""
+						collectionFormat = reg.GetRepeatedPathParamSeparatorName()
+						minItems = new(int)
+						*minItems = 1
+					}
+
+					if desc == "" {
+						desc = fieldProtoComments(reg, parameter.Target.Message, parameter.Target)
+					}
+					parameterString := parameter.String()
+					if reg.GetUseJSONNamesForFields() {
+						parameterString = lowerCamelCase(parameterString, meth.RequestType.Fields, msgs)
+					}
+					parameters = append(parameters, openapiParameterObject{
+						Name:        parameterString,
+						Description: desc,
+						In:          "path",
+						Required:    true,
+						Default:     defaultValue,
+						// Parameters in gRPC-Gateway can only be strings?
+						Type:             paramType,
+						Format:           paramFormat,
+						Enum:             enumNames,
+						Items:            items,
+						CollectionFormat: collectionFormat,
+						MinItems:         minItems,
+					})
+				}
+				// Now check if there is a body parameter
+				if b.Body != nil {
+					var schema openapiSchemaObject
+					desc := ""
+
+					if len(b.Body.FieldPath) == 0 {
+						schema = openapiSchemaObject{
+							schemaCore: schemaCore{},
+						}
+
+						wknSchemaCore, isWkn := wktSchemas[meth.RequestType.FQMN()]
+						if !isWkn {
+							var bodyExcludedFields []*descriptor.Field
+							if len(b.PathParams) != 0 {
+								for _, p := range b.PathParams {
+									// We only support excluding top-level fields captured by path parameters.
+									if len(p.FieldPath) == 1 {
+										bodyExcludedFields = append(bodyExcludedFields, p.FieldPath[0].Target)
+									}
+								}
+							}
+							if len(bodyExcludedFields) != 0 {
+								schema = renderMessageAsDefinition(meth.RequestType, reg, customRefs, bodyExcludedFields)
+								if schema.Properties == nil || len(*schema.Properties) == 0 {
+									glog.Errorf("created a body with 0 properties in the message, this might be unintended: %s", *meth.RequestType)
+								}
+							} else {
+								err := schema.setRefFromFQN(meth.RequestType.FQMN(), reg)
+								if err != nil {
+									return err
+								}
+							}
+						} else {
+							schema.schemaCore = wknSchemaCore
+
+							// Special workaround for Empty: it's well-known type but wknSchemas only returns schema.schemaCore; but we need to set schema.Properties which is a level higher.
+							if meth.RequestType.FQMN() == ".google.protobuf.Empty" {
+								schema.Properties = &openapiSchemaObjectProperties{}
+							}
+						}
+					} else {
+						lastField := b.Body.FieldPath[len(b.Body.FieldPath)-1]
+						schema = schemaOfField(lastField.Target, reg, customRefs)
+						if schema.Description != "" {
+							desc = schema.Description
+						} else {
+							desc = fieldProtoComments(reg, lastField.Target.Message, lastField.Target)
+						}
+					}
+
+					if meth.GetClientStreaming() {
+						desc += " (streaming inputs)"
+					}
+					parameters = append(parameters, openapiParameterObject{
+						Name:        "body",
+						Description: desc,
+						In:          "body",
+						Required:    true,
+						Schema:      &schema,
+					})
+					// add the parameters to the query string
+					queryParams, err := messageToQueryParameters(meth.RequestType, reg, b.PathParams, b.Body)
+					if err != nil {
+						return err
+					}
+					parameters = append(parameters, queryParams...)
+				} else if b.HTTPMethod == "GET" || b.HTTPMethod == "DELETE" {
+					// add the parameters to the query string
+					queryParams, err := messageToQueryParameters(meth.RequestType, reg, b.PathParams, b.Body)
+					if err != nil {
+						return err
+					}
+					parameters = append(parameters, queryParams...)
+				}
+
+				pathItemObject, ok := paths[templateToOpenAPIPath(b.PathTmpl.Template, reg, meth.RequestType.Fields, msgs)]
+				if !ok {
+					pathItemObject = openapiPathItemObject{}
+				}
+
+				methProtoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.ServiceDescriptorProto)(nil)), "Method")
+				desc := "A successful response."
+				var responseSchema openapiSchemaObject
+
+				if b.ResponseBody == nil || len(b.ResponseBody.FieldPath) == 0 {
+					responseSchema = openapiSchemaObject{
+						schemaCore: schemaCore{},
+					}
+
+					// Don't link to a full definition for
+					// empty; it's overly verbose.
+					// schema.Properties{} renders it as
+					// well, without a definition
+					wknSchemaCore, isWkn := wktSchemas[meth.ResponseType.FQMN()]
+					if !isWkn {
+						err := responseSchema.setRefFromFQN(meth.ResponseType.FQMN(), reg)
+						if err != nil {
+							return err
+						}
+					} else {
+						responseSchema.schemaCore = wknSchemaCore
+
+						// Special workaround for Empty: it's well-known type but wknSchemas only returns schema.schemaCore; but we need to set schema.Properties which is a level higher.
+						if meth.ResponseType.FQMN() == ".google.protobuf.Empty" {
+							responseSchema.Properties = &openapiSchemaObjectProperties{}
+						}
+					}
+				} else {
+					// This is resolving the value of response_body in the google.api.HttpRule
+					lastField := b.ResponseBody.FieldPath[len(b.ResponseBody.FieldPath)-1]
+					responseSchema = schemaOfField(lastField.Target, reg, customRefs)
+					if responseSchema.Description != "" {
+						desc = responseSchema.Description
+					} else {
+						desc = fieldProtoComments(reg, lastField.Target.Message, lastField.Target)
+					}
+				}
+				if meth.GetServerStreaming() {
+					desc += "(streaming responses)"
+					responseSchema.Type = "object"
+					swgRef, _ := fullyQualifiedNameToOpenAPIName(meth.ResponseType.FQMN(), reg)
+					responseSchema.Title = "Stream result of " + swgRef
+
+					props := openapiSchemaObjectProperties{
+						keyVal{
+							Key: "result",
+							Value: openapiSchemaObject{
+								schemaCore: schemaCore{
+									Ref: responseSchema.Ref,
+								},
+							},
+						},
+					}
+					statusDef, hasStatus := fullyQualifiedNameToOpenAPIName(".google.rpc.Status", reg)
+					if hasStatus {
+						props = append(props, keyVal{
+							Key: "error",
+							Value: openapiSchemaObject{
+								schemaCore: schemaCore{
+									Ref: fmt.Sprintf("#/definitions/%s", statusDef)},
+							},
+						})
+					}
+					responseSchema.Properties = &props
+					responseSchema.Ref = ""
+				}
+
+				tag := svc.GetName()
+				if pkg := svc.File.GetPackage(); pkg != "" && reg.IsIncludePackageInTags() {
+					tag = pkg + "." + tag
+				}
+
+				operationObject := &openapiOperationObject{
+					Tags:       []string{tag},
+					Parameters: parameters,
+					Responses: openapiResponsesObject{
+						"200": openapiResponseObject{
+							Description: desc,
+							Schema:      responseSchema,
+							Headers:     openapiHeadersObject{},
+						},
+					},
+				}
+				if !reg.GetDisableDefaultErrors() {
+					errDef, hasErrDef := fullyQualifiedNameToOpenAPIName(".google.rpc.Status", reg)
+					if hasErrDef {
+						// https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#responses-object
+						operationObject.Responses["default"] = openapiResponseObject{
+							Description: "An unexpected error response.",
+							Schema: openapiSchemaObject{
+								schemaCore: schemaCore{
+									Ref: fmt.Sprintf("#/definitions/%s", errDef),
+								},
+							},
+						}
+					}
+				}
+				operationObject.OperationID = fmt.Sprintf("%s_%s", svc.GetName(), meth.GetName())
+				if reg.GetSimpleOperationIDs() {
+					operationObject.OperationID = meth.GetName()
+				}
+				if bIdx != 0 {
+					// OperationID must be unique in an OpenAPI v2 definition.
+					operationObject.OperationID += strconv.Itoa(bIdx + 1)
+				}
+
+				// Fill reference map with referenced request messages
+				for _, param := range operationObject.Parameters {
+					if param.Schema != nil && param.Schema.Ref != "" {
+						requestResponseRefs[param.Schema.Ref] = struct{}{}
+					}
+				}
+
+				methComments := protoComments(reg, svc.File, nil, "Service", int32(svcIdx-svcBaseIdx), methProtoPath, int32(methIdx))
+				if err := updateOpenAPIDataFromComments(reg, operationObject, meth, methComments, false); err != nil {
+					panic(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.Summary != "" {
+						operationObject.Summary = opts.Summary
+					}
+					if opts.Description != "" {
+						operationObject.Description = opts.Description
+					}
+					if len(opts.Tags) > 0 {
+						operationObject.Tags = make([]string, len(opts.Tags))
+						copy(operationObject.Tags, opts.Tags)
+					}
+					if opts.OperationId != "" {
+						operationObject.OperationID = opts.OperationId
+					}
+					if opts.Security != nil {
+						newSecurity := []openapiSecurityRequirementObject{}
+						if operationObject.Security != nil {
+							newSecurity = *operationObject.Security
+						}
+						for _, secReq := range opts.Security {
+							newSecReq := openapiSecurityRequirementObject{}
+							for secReqKey, secReqValue := range secReq.SecurityRequirement {
+								if secReqValue == nil {
+									continue
+								}
+
+								newSecReqValue := make([]string, len(secReqValue.Scope))
+								copy(newSecReqValue, secReqValue.Scope)
+								newSecReq[secReqKey] = newSecReqValue
+							}
+
+							if len(newSecReq) > 0 {
+								newSecurity = append(newSecurity, newSecReq)
+							}
+						}
+						operationObject.Security = &newSecurity
+					}
+					if opts.Responses != nil {
+						for name, resp := range opts.Responses {
+							// Merge response data into default response if available.
+							respObj := operationObject.Responses[name]
+							if resp.Description != "" {
+								respObj.Description = resp.Description
+							}
+							if resp.Schema != nil {
+								respObj.Schema = openapiSchemaFromProtoSchema(resp.Schema, reg, customRefs, meth)
+							}
+							if resp.Examples != nil {
+								respObj.Examples = openapiExamplesFromProtoExamples(resp.Examples)
+							}
+							if resp.Headers != nil {
+								hdrs, err := processHeaders(resp.Headers)
+								if err != nil {
+									return err
+								}
+								respObj.Headers = hdrs
+							}
+							if resp.Extensions != nil {
+								exts, err := processExtensions(resp.Extensions)
+								if err != nil {
+									return err
+								}
+								respObj.extensions = exts
+							}
+							operationObject.Responses[name] = respObj
+						}
+					}
+
+					if opts.Extensions != nil {
+						exts, err := processExtensions(opts.Extensions)
+						if err != nil {
+							return err
+						}
+						operationObject.extensions = exts
+					}
+
+					if len(opts.Produces) > 0 {
+						operationObject.Produces = make([]string, len(opts.Produces))
+						copy(operationObject.Produces, opts.Produces)
+					}
+
+					// TODO(ivucica): add remaining fields of operation object
+				}
+
+				switch b.HTTPMethod {
+				case "DELETE":
+					pathItemObject.Delete = operationObject
+				case "GET":
+					pathItemObject.Get = operationObject
+				case "POST":
+					pathItemObject.Post = operationObject
+				case "PUT":
+					pathItemObject.Put = operationObject
+				case "PATCH":
+					pathItemObject.Patch = operationObject
+				}
+				paths[templateToOpenAPIPath(b.PathTmpl.Template, reg, meth.RequestType.Fields, msgs)] = pathItemObject
+			}
+		}
+	}
+
+	// Success! return nil on the error object
+	return nil
+}
+
+// This function is called with a param which contains the entire definition of a method.
+func applyTemplate(p param) (*openapiSwaggerObject, error) {
+	// Create the basic template object. This is the object that everything is
+	// defined off of.
+	s := openapiSwaggerObject{
+		// OpenAPI 2.0 is the version of this document
+		Swagger:     "2.0",
+		Consumes:    []string{"application/json"},
+		Produces:    []string{"application/json"},
+		Paths:       make(openapiPathsObject),
+		Definitions: make(openapiDefinitionsObject),
+		Info: openapiInfoObject{
+			Title:   *p.File.Name,
+			Version: "version not set",
+		},
+		Schemes: []string{"HTTP", "HTTPS", "WS", "WSS"},
+	}
+
+	// Loops through all the services and their exposed GET/POST/PUT/DELETE definitions
+	// and create entries for all of them.
+	// Also adds custom user specified references to second map.
+	requestResponseRefs, customRefs := refMap{}, refMap{}
+	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{}
+	enums := enumMap{}
+
+	if !p.reg.GetDisableDefaultErrors() {
+		// Add the error type to the message map
+		runtimeError, swgRef, err := lookupMsgAndOpenAPIName("google.rpc", "Status", p.reg)
+		if err == nil {
+			messages[swgRef] = runtimeError
+		} else {
+			// just in case there is an error looking up runtimeError
+			glog.Error(err)
+		}
+	}
+
+	// Find all the service's messages and enumerations that are defined (recursively)
+	// and write request, response and other custom (but referenced) types out as definition objects.
+	findServicesMessagesAndEnumerations(p.Services, p.reg, messages, streamingMessages, enums, requestResponseRefs)
+	renderMessagesAsDefinition(messages, s.Definitions, p.reg, customRefs, nil)
+	renderEnumerationsAsDefinition(enums, s.Definitions, p.reg)
+
+	// File itself might have some comments and metadata.
+	packageProtoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Package")
+	packageComments := protoComments(p.reg, p.File, nil, "Package", packageProtoPath)
+	if err := updateOpenAPIDataFromComments(p.reg, &s, p, packageComments, true); err != nil {
+		panic(err)
+	}
+
+	// There may be additional options in the OpenAPI option in the proto.
+	spb, err := getFileOpenAPIOption(p.reg, p.File)
+	if err != nil {
+		panic(err)
+	}
+	if spb != nil {
+		if spb.Swagger != "" {
+			s.Swagger = spb.Swagger
+		}
+		if spb.Info != nil {
+			if spb.Info.Title != "" {
+				s.Info.Title = spb.Info.Title
+			}
+			if spb.Info.Description != "" {
+				s.Info.Description = spb.Info.Description
+			}
+			if spb.Info.TermsOfService != "" {
+				s.Info.TermsOfService = spb.Info.TermsOfService
+			}
+			if spb.Info.Version != "" {
+				s.Info.Version = spb.Info.Version
+			}
+			if spb.Info.Contact != nil {
+				if s.Info.Contact == nil {
+					s.Info.Contact = &openapiContactObject{}
+				}
+				if spb.Info.Contact.Name != "" {
+					s.Info.Contact.Name = spb.Info.Contact.Name
+				}
+				if spb.Info.Contact.Url != "" {
+					s.Info.Contact.URL = spb.Info.Contact.Url
+				}
+				if spb.Info.Contact.Email != "" {
+					s.Info.Contact.Email = spb.Info.Contact.Email
+				}
+			}
+			if spb.Info.License != nil {
+				if s.Info.License == nil {
+					s.Info.License = &openapiLicenseObject{}
+				}
+				if spb.Info.License.Name != "" {
+					s.Info.License.Name = spb.Info.License.Name
+				}
+				if spb.Info.License.Url != "" {
+					s.Info.License.URL = spb.Info.License.Url
+				}
+			}
+			if spb.Info.Extensions != nil {
+				exts, err := processExtensions(spb.Info.Extensions)
+				if err != nil {
+					return nil, err
+				}
+				s.Info.extensions = exts
+			}
+		}
+		if spb.Host != "" {
+			s.Host = spb.Host
+		}
+		if spb.BasePath != "" {
+			s.BasePath = spb.BasePath
+		}
+		if len(spb.Schemes) > 0 {
+			s.Schemes = make([]string, len(spb.Schemes))
+			for i, scheme := range spb.Schemes {
+				s.Schemes[i] = strings.ToLower(scheme.String())
+			}
+		}
+		if len(spb.Consumes) > 0 {
+			s.Consumes = make([]string, len(spb.Consumes))
+			copy(s.Consumes, spb.Consumes)
+		}
+		if len(spb.Produces) > 0 {
+			s.Produces = make([]string, len(spb.Produces))
+			copy(s.Produces, spb.Produces)
+		}
+		if spb.SecurityDefinitions != nil && spb.SecurityDefinitions.Security != nil {
+			if s.SecurityDefinitions == nil {
+				s.SecurityDefinitions = openapiSecurityDefinitionsObject{}
+			}
+			for secDefKey, secDefValue := range spb.SecurityDefinitions.Security {
+				var newSecDefValue openapiSecuritySchemeObject
+				if oldSecDefValue, ok := s.SecurityDefinitions[secDefKey]; !ok {
+					newSecDefValue = openapiSecuritySchemeObject{}
+				} else {
+					newSecDefValue = oldSecDefValue
+				}
+				if secDefValue.Type != openapi_options.SecurityScheme_TYPE_INVALID {
+					switch secDefValue.Type {
+					case openapi_options.SecurityScheme_TYPE_BASIC:
+						newSecDefValue.Type = "basic"
+					case openapi_options.SecurityScheme_TYPE_API_KEY:
+						newSecDefValue.Type = "apiKey"
+					case openapi_options.SecurityScheme_TYPE_OAUTH2:
+						newSecDefValue.Type = "oauth2"
+					}
+				}
+				if secDefValue.Description != "" {
+					newSecDefValue.Description = secDefValue.Description
+				}
+				if secDefValue.Name != "" {
+					newSecDefValue.Name = secDefValue.Name
+				}
+				if secDefValue.In != openapi_options.SecurityScheme_IN_INVALID {
+					switch secDefValue.In {
+					case openapi_options.SecurityScheme_IN_QUERY:
+						newSecDefValue.In = "query"
+					case openapi_options.SecurityScheme_IN_HEADER:
+						newSecDefValue.In = "header"
+					}
+				}
+				if secDefValue.Flow != openapi_options.SecurityScheme_FLOW_INVALID {
+					switch secDefValue.Flow {
+					case openapi_options.SecurityScheme_FLOW_IMPLICIT:
+						newSecDefValue.Flow = "implicit"
+					case openapi_options.SecurityScheme_FLOW_PASSWORD:
+						newSecDefValue.Flow = "password"
+					case openapi_options.SecurityScheme_FLOW_APPLICATION:
+						newSecDefValue.Flow = "application"
+					case openapi_options.SecurityScheme_FLOW_ACCESS_CODE:
+						newSecDefValue.Flow = "accessCode"
+					}
+				}
+				if secDefValue.AuthorizationUrl != "" {
+					newSecDefValue.AuthorizationURL = secDefValue.AuthorizationUrl
+				}
+				if secDefValue.TokenUrl != "" {
+					newSecDefValue.TokenURL = secDefValue.TokenUrl
+				}
+				if secDefValue.Scopes != nil {
+					if newSecDefValue.Scopes == nil {
+						newSecDefValue.Scopes = openapiScopesObject{}
+					}
+					for scopeKey, scopeDesc := range secDefValue.Scopes.Scope {
+						newSecDefValue.Scopes[scopeKey] = scopeDesc
+					}
+				}
+				if secDefValue.Extensions != nil {
+					exts, err := processExtensions(secDefValue.Extensions)
+					if err != nil {
+						return nil, err
+					}
+					newSecDefValue.extensions = exts
+				}
+				s.SecurityDefinitions[secDefKey] = newSecDefValue
+			}
+		}
+		if spb.Security != nil {
+			var newSecurity []openapiSecurityRequirementObject
+			if s.Security != nil {
+				newSecurity = s.Security
+			}
+			for _, secReq := range spb.Security {
+				newSecReq := openapiSecurityRequirementObject{}
+				for secReqKey, secReqValue := range secReq.SecurityRequirement {
+					if secReqValue == nil {
+						return nil, fmt.Errorf("malformed security requirement spec for key %q; value is required", secReqKey)
+					}
+					newSecReqValue := make([]string, len(secReqValue.Scope))
+					copy(newSecReqValue, secReqValue.Scope)
+					newSecReq[secReqKey] = newSecReqValue
+				}
+				newSecurity = append(newSecurity, newSecReq)
+			}
+			s.Security = newSecurity
+		}
+		s.ExternalDocs = protoExternalDocumentationToOpenAPIExternalDocumentation(spb.ExternalDocs, p.reg, spb)
+		// Populate all Paths with Responses set at top level,
+		// preferring Responses already set over those at the top level.
+		if spb.Responses != nil {
+			for _, verbs := range s.Paths {
+				var maps []openapiResponsesObject
+				if verbs.Delete != nil {
+					maps = append(maps, verbs.Delete.Responses)
+				}
+				if verbs.Get != nil {
+					maps = append(maps, verbs.Get.Responses)
+				}
+				if verbs.Post != nil {
+					maps = append(maps, verbs.Post.Responses)
+				}
+				if verbs.Put != nil {
+					maps = append(maps, verbs.Put.Responses)
+				}
+				if verbs.Patch != nil {
+					maps = append(maps, verbs.Patch.Responses)
+				}
+
+				for k, v := range spb.Responses {
+					for _, respMap := range maps {
+						if _, ok := respMap[k]; ok {
+							// Don't overwrite already existing Responses
+							continue
+						}
+						respMap[k] = openapiResponseObject{
+							Description: v.Description,
+							Schema:      openapiSchemaFromProtoSchema(v.Schema, p.reg, customRefs, nil),
+							Examples:    openapiExamplesFromProtoExamples(v.Examples),
+						}
+					}
+				}
+			}
+		}
+
+		if spb.Extensions != nil {
+			exts, err := processExtensions(spb.Extensions)
+			if err != nil {
+				return nil, err
+			}
+			s.extensions = exts
+		}
+
+		// Additional fields on the OpenAPI v2 spec's "OpenAPI" object
+		// should be added here, once supported in the proto.
+	}
+
+	// Finally add any references added by users that aren't
+	// otherwise rendered.
+	addCustomRefs(s.Definitions, p.reg, customRefs)
+
+	return &s, nil
+}
+
+func processExtensions(inputExts map[string]*structpb.Value) ([]extension, error) {
+	exts := []extension{}
+	for k, v := range inputExts {
+		if !strings.HasPrefix(k, "x-") {
+			return nil, fmt.Errorf("extension keys need to start with \"x-\": %q", k)
+		}
+		ext, err := (&protojson.MarshalOptions{Indent: "  "}).Marshal(v)
+		if err != nil {
+			return nil, err
+		}
+		exts = append(exts, extension{key: k, value: json.RawMessage(ext)})
+	}
+	sort.Slice(exts, func(i, j int) bool { return exts[i].key < exts[j].key })
+	return exts, nil
+}
+
+func validateHeaderTypeAndFormat(headerType string, format string) error {
+	// The type of the object. The value MUST be one of "string", "number", "integer", "boolean", or "array"
+	// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#headerObject
+	// Note: currently not implementing array as we are only implementing this in the operation response context
+	switch headerType {
+	// the format property is an open string-valued property, and can have any value to support documentation needs
+	// primary check for format is to ensure that the number/integer formats are extensions of the specified type
+	// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#dataTypeFormat
+	case "string":
+		return nil
+	case "number":
+		switch format {
+		case "uint",
+			"uint8",
+			"uint16",
+			"uint32",
+			"uint64",
+			"int",
+			"int8",
+			"int16",
+			"int32",
+			"int64",
+			"float",
+			"float32",
+			"float64",
+			"complex64",
+			"complex128",
+			"double",
+			"byte",
+			"rune",
+			"uintptr",
+			"":
+			return nil
+		default:
+			return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
+		}
+	case "integer":
+		switch format {
+		case "uint",
+			"uint8",
+			"uint16",
+			"uint32",
+			"uint64",
+			"int",
+			"int8",
+			"int16",
+			"int32",
+			"int64",
+			"":
+			return nil
+		default:
+			return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
+		}
+	case "boolean":
+		return nil
+	}
+	return fmt.Errorf("the provided header type %q is not supported", headerType)
+}
+
+func validateDefaultValueTypeAndFormat(headerType string, defaultValue string, format string) error {
+	switch headerType {
+	case "string":
+		if !isQuotedString(defaultValue) {
+			return fmt.Errorf("the provided default value %q does not match provider type %q, or is not properly quoted with escaped quotations", defaultValue, headerType)
+		}
+		switch format {
+		case "date-time":
+			unquoteTime := strings.Trim(defaultValue, `"`)
+			_, err := time.Parse(time.RFC3339, unquoteTime)
+			if err != nil {
+				return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
+			}
+		case "date":
+			const (
+				layoutRFC3339Date = "2006-01-02"
+			)
+			unquoteDate := strings.Trim(defaultValue, `"`)
+			_, err := time.Parse(layoutRFC3339Date, unquoteDate)
+			if err != nil {
+				return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
+			}
+		}
+	case "number":
+		err := isJSONNumber(defaultValue, headerType)
+		if err != nil {
+			return err
+		}
+	case "integer":
+		switch format {
+		case "int32":
+			_, err := strconv.ParseInt(defaultValue, 0, 32)
+			if err != nil {
+				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
+			}
+		case "uint32":
+			_, err := strconv.ParseUint(defaultValue, 0, 32)
+			if err != nil {
+				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
+			}
+		case "int64":
+			_, err := strconv.ParseInt(defaultValue, 0, 64)
+			if err != nil {
+				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
+			}
+		case "uint64":
+			_, err := strconv.ParseUint(defaultValue, 0, 64)
+			if err != nil {
+				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
+			}
+		default:
+			_, err := strconv.ParseInt(defaultValue, 0, 64)
+			if err != nil {
+				return fmt.Errorf("the provided default value %q does not match provided type %q", defaultValue, headerType)
+			}
+		}
+	case "boolean":
+		if !isBool(defaultValue) {
+			return fmt.Errorf("the provided default value %q does not match provider type %q", defaultValue, headerType)
+		}
+	}
+	return nil
+}
+
+func isQuotedString(s string) bool {
+	return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
+}
+
+func isJSONNumber(s string, t string) error {
+	val, err := strconv.ParseFloat(s, 64)
+	if err != nil {
+		return fmt.Errorf("the provided default value %q does not match provider type %q", s, t)
+	}
+	// Floating point values that cannot be represented as sequences of digits (such as Infinity and NaN) are not permitted.
+	// See: https://tools.ietf.org/html/rfc4627#section-2.4
+	if math.IsInf(val, 0) || math.IsNaN(val) {
+		return fmt.Errorf("the provided number %q is not a valid JSON number", s)
+	}
+
+	return nil
+}
+
+func isBool(s string) bool {
+	// Unable to use strconv.ParseBool because it returns truthy values https://golang.org/pkg/strconv/#example_ParseBool
+	// per https://swagger.io/specification/v2/#data-types
+	// type: boolean represents two values: true and false. Note that truthy and falsy values such as "true", "", 0 or null are not considered boolean values.
+	return s == "true" || s == "false"
+}
+
+func processHeaders(inputHdrs map[string]*openapi_options.Header) (openapiHeadersObject, error) {
+	hdrs := map[string]openapiHeaderObject{}
+	for k, v := range inputHdrs {
+		header := textproto.CanonicalMIMEHeaderKey(k)
+		ret := openapiHeaderObject{
+			Description: v.Description,
+			Format:      v.Format,
+			Pattern:     v.Pattern,
+		}
+		err := validateHeaderTypeAndFormat(v.Type, v.Format)
+		if err != nil {
+			return nil, err
+		}
+		ret.Type = v.Type
+		if v.Default != "" {
+			err := validateDefaultValueTypeAndFormat(v.Type, v.Default, v.Format)
+			if err != nil {
+				return nil, err
+			}
+			ret.Default = json.RawMessage(v.Default)
+		}
+		hdrs[header] = ret
+	}
+	return hdrs, nil
+}
+
+// updateOpenAPIDataFromComments updates a OpenAPI object based on a comment
+// from the proto file.
+//
+// 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.
+//
+// If there is no 'Summary', the same behavior will be attempted on 'Title',
+// but only if the last character is not a period.
+func updateOpenAPIDataFromComments(reg *descriptor.Registry, swaggerObject interface{}, data interface{}, comment string, isPackageObject bool) error {
+	if len(comment) == 0 {
+		return nil
+	}
+
+	// Checks whether the "use_go_templates" flag is set to true
+	if reg.GetUseGoTemplate() {
+		comment = goTemplateComments(comment, data, reg)
+	}
+
+	// Figure out what to apply changes to.
+	swaggerObjectValue := reflect.ValueOf(swaggerObject)
+	infoObjectValue := swaggerObjectValue.Elem().FieldByName("Info")
+	if !infoObjectValue.CanSet() {
+		// No such field? Apply summary and description directly to
+		// passed object.
+		infoObjectValue = swaggerObjectValue.Elem()
+	}
+
+	// Figure out which properties to update.
+	summaryValue := infoObjectValue.FieldByName("Summary")
+	descriptionValue := infoObjectValue.FieldByName("Description")
+	readOnlyValue := infoObjectValue.FieldByName("ReadOnly")
+
+	if readOnlyValue.Kind() == reflect.Bool && readOnlyValue.CanSet() && strings.Contains(comment, "Output only.") {
+		readOnlyValue.Set(reflect.ValueOf(true))
+	}
+
+	usingTitle := false
+	if !summaryValue.CanSet() {
+		summaryValue = infoObjectValue.FieldByName("Title")
+		usingTitle = true
+	}
+
+	paragraphs := strings.Split(comment, "\n\n")
+
+	// If there is a summary (or summary-equivalent) and it's empty, use the first
+	// paragraph as summary, and the rest as description.
+	if summaryValue.CanSet() {
+		summary := strings.TrimSpace(paragraphs[0])
+		description := strings.TrimSpace(strings.Join(paragraphs[1:], "\n\n"))
+		if !usingTitle || (len(summary) > 0 && summary[len(summary)-1] != '.') {
+			// overrides the schema value only if it's empty
+			// keep the comment precedence when updating the package definition
+			if summaryValue.Len() == 0 || isPackageObject {
+				summaryValue.Set(reflect.ValueOf(summary))
+			}
+			if len(description) > 0 {
+				if !descriptionValue.CanSet() {
+					return fmt.Errorf("encountered object type with a summary, but no description")
+				}
+				// overrides the schema value only if it's empty
+				// keep the comment precedence when updating the package definition
+				if descriptionValue.Len() == 0 || isPackageObject {
+					descriptionValue.Set(reflect.ValueOf(description))
+				}
+			}
+			return nil
+		}
+	}
+
+	// There was no summary field on the swaggerObject. Try to apply the
+	// whole comment into description if the OpenAPI object description is empty.
+	if descriptionValue.CanSet() {
+		if descriptionValue.Len() == 0 || isPackageObject {
+			descriptionValue.Set(reflect.ValueOf(strings.Join(paragraphs, "\n\n")))
+		}
+		return nil
+	}
+
+	return fmt.Errorf("no description nor summary property")
+}
+
+func fieldProtoComments(reg *descriptor.Registry, msg *descriptor.Message, field *descriptor.Field) string {
+	protoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.DescriptorProto)(nil)), "Field")
+	for i, f := range msg.Fields {
+		if f == field {
+			return protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index), protoPath, int32(i))
+		}
+	}
+	return ""
+}
+
+func enumValueProtoComments(reg *descriptor.Registry, enum *descriptor.Enum) string {
+	protoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.EnumDescriptorProto)(nil)), "Value")
+	var comments []string
+	for idx, value := range enum.GetValue() {
+		name := value.GetName()
+		if reg.GetEnumsAsInts() {
+			name = strconv.Itoa(int(value.GetNumber()))
+		}
+		str := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index), protoPath, int32(idx))
+		if str != "" {
+			comments = append(comments, name+": "+str)
+		}
+	}
+	if len(comments) > 0 {
+		return "- " + strings.Join(comments, "\n - ")
+	}
+	return ""
+}
+
+func protoComments(reg *descriptor.Registry, file *descriptor.File, outers []string, typeName string, typeIndex int32, fieldPaths ...int32) string {
+	if file.SourceCodeInfo == nil {
+		//fmt.Fprintln(os.Stderr, "descriptor.File should not contain nil SourceCodeInfo")
+		return ""
+	}
+
+	outerPaths := make([]int32, len(outers))
+	for i := range outers {
+		location := ""
+		if file.Package != nil {
+			location = file.GetPackage()
+		}
+
+		msg, err := reg.LookupMsg(location, strings.Join(outers[:i+1], "."))
+		if err != nil {
+			panic(err)
+		}
+		outerPaths[i] = int32(msg.Index)
+	}
+
+	for _, loc := range file.SourceCodeInfo.Location {
+		if !isProtoPathMatches(loc.Path, outerPaths, typeName, typeIndex, fieldPaths) {
+			continue
+		}
+		comments := ""
+		if loc.LeadingComments != nil {
+			comments = strings.TrimRight(*loc.LeadingComments, "\n")
+			comments = strings.TrimSpace(comments)
+			// TODO(ivucica): this is a hack to fix "// " being interpreted as "//".
+			// perhaps we should:
+			// - split by \n
+			// - determine if every (but first and last) line begins with " "
+			// - trim every line only if that is the case
+			// - join by \n
+			comments = strings.Replace(comments, "\n ", "\n", -1)
+		}
+		return comments
+	}
+	return ""
+}
+
+func goTemplateComments(comment string, data interface{}, reg *descriptor.Registry) string {
+	var temp bytes.Buffer
+	tpl, err := template.New("").Funcs(template.FuncMap{
+		// Allows importing documentation from a file
+		"import": func(name string) string {
+			file, err := ioutil.ReadFile(name)
+			if err != nil {
+				return err.Error()
+			}
+			// Runs template over imported file
+			return goTemplateComments(string(file), data, reg)
+		},
+		// Grabs title and description from a field
+		"fieldcomments": func(msg *descriptor.Message, field *descriptor.Field) string {
+			return strings.Replace(fieldProtoComments(reg, msg, field), "\n", "<br>", -1)
+		},
+	}).Parse(comment)
+	if err != nil {
+		// If there is an error parsing the templating insert the error as string in the comment
+		// to make it easier to debug the template error
+		return err.Error()
+	}
+	err = tpl.Execute(&temp, data)
+	if err != nil {
+		// If there is an error executing the templating insert the error as string in the comment
+		// to make it easier to debug the error
+		return err.Error()
+	}
+	return temp.String()
+}
+
+var messageProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "MessageType")
+var nestedProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.DescriptorProto)(nil)), "NestedType")
+var packageProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Package")
+var serviceProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Service")
+var methodProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.ServiceDescriptorProto)(nil)), "Method")
+
+func isProtoPathMatches(paths []int32, outerPaths []int32, typeName string, typeIndex int32, fieldPaths []int32) bool {
+	if typeName == "Package" && typeIndex == packageProtoPath {
+		// path for package comments is just [2], and all the other processing
+		// is too complex for it.
+		if len(paths) == 0 || typeIndex != paths[0] {
+			return false
+		}
+		return true
+	}
+
+	if len(paths) != len(outerPaths)*2+2+len(fieldPaths) {
+		return false
+	}
+
+	if typeName == "Method" {
+		if paths[0] != serviceProtoPath || paths[2] != methodProtoPath {
+			return false
+		}
+		paths = paths[2:]
+	} else {
+		typeNameDescriptor := reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil))
+
+		if len(outerPaths) > 0 {
+			if paths[0] != messageProtoPath || paths[1] != outerPaths[0] {
+				return false
+			}
+			paths = paths[2:]
+			outerPaths = outerPaths[1:]
+
+			for i, v := range outerPaths {
+				if paths[i*2] != nestedProtoPath || paths[i*2+1] != v {
+					return false
+				}
+			}
+			paths = paths[len(outerPaths)*2:]
+
+			if typeName == "MessageType" {
+				typeName = "NestedType"
+			}
+			typeNameDescriptor = reflect.TypeOf((*descriptorpb.DescriptorProto)(nil))
+		}
+
+		if paths[0] != protoPathIndex(typeNameDescriptor, typeName) || paths[1] != typeIndex {
+			return false
+		}
+		paths = paths[2:]
+	}
+
+	for i, v := range fieldPaths {
+		if paths[i] != v {
+			return false
+		}
+	}
+	return true
+}
+
+// protoPathIndex returns a path component for google.protobuf.descriptor.SourceCode_Location.
+//
+// Specifically, it returns an id as generated from descriptor proto which
+// can be used to determine what type the id following it in the path is.
+// For example, if we are trying to locate comments related to a field named
+// `Address` in a message named `Person`, the path will be:
+//
+//	[4, a, 2, b]
+//
+// While `a` gets determined by the order in which the messages appear in
+// the proto file, and `b` is the field index specified in the proto
+// file itself, the path actually needs to specify that `a` refers to a
+// message and not, say, a service; and  that `b` refers to a field and not
+// an option.
+//
+// protoPathIndex figures out the values 4 and 2 in the above example. Because
+// messages are top level objects, the value of 4 comes from field id for
+// `MessageType` inside `google.protobuf.descriptor.FileDescriptor` message.
+// This field has a message type `google.protobuf.descriptor.DescriptorProto`.
+// And inside message `DescriptorProto`, there is a field named `Field` with id
+// 2.
+//
+// Some code generators seem to be hardcoding these values; this method instead
+// interprets them from `descriptor.proto`-derived Go source as necessary.
+func protoPathIndex(descriptorType reflect.Type, what string) int32 {
+	field, ok := descriptorType.Elem().FieldByName(what)
+	if !ok {
+		panic(fmt.Errorf("could not find protobuf descriptor type id for %s", what))
+	}
+	pbtag := field.Tag.Get("protobuf")
+	if pbtag == "" {
+		panic(fmt.Errorf("no Go tag 'protobuf' on protobuf descriptor for %s", what))
+	}
+	path, err := strconv.Atoi(strings.Split(pbtag, ",")[1])
+	if err != nil {
+		panic(fmt.Errorf("protobuf descriptor id for %s cannot be converted to a number: %s", what, err.Error()))
+	}
+
+	return int32(path)
+}
+
+// extractOperationOptionFromMethodDescriptor extracts the message of type
+// openapi_options.Operation from a given proto method's descriptor.
+func extractOperationOptionFromMethodDescriptor(meth *descriptorpb.MethodDescriptorProto) (*openapi_options.Operation, error) {
+	if meth.Options == nil {
+		return nil, nil
+	}
+	if !proto.HasExtension(meth.Options, openapi_options.E_Openapiv2Operation) {
+		return nil, nil
+	}
+	ext := proto.GetExtension(meth.Options, openapi_options.E_Openapiv2Operation)
+	opts, ok := ext.(*openapi_options.Operation)
+	if !ok {
+		return nil, fmt.Errorf("extension is %T; want an Operation", ext)
+	}
+	return opts, nil
+}
+
+// extractSchemaOptionFromMessageDescriptor extracts the message of type
+// openapi_options.Schema from a given proto message's descriptor.
+func extractSchemaOptionFromMessageDescriptor(msg *descriptorpb.DescriptorProto) (*openapi_options.Schema, error) {
+	if msg.Options == nil {
+		return nil, nil
+	}
+	if !proto.HasExtension(msg.Options, openapi_options.E_Openapiv2Schema) {
+		return nil, nil
+	}
+	ext := proto.GetExtension(msg.Options, openapi_options.E_Openapiv2Schema)
+	opts, ok := ext.(*openapi_options.Schema)
+	if !ok {
+		return nil, fmt.Errorf("extension is %T; want a Schema", ext)
+	}
+	return opts, nil
+}
+
+// extractOpenAPIOptionFromFileDescriptor extracts the message of type
+// openapi_options.OpenAPI from a given proto method's descriptor.
+func extractOpenAPIOptionFromFileDescriptor(file *descriptorpb.FileDescriptorProto) (*openapi_options.Swagger, error) {
+	if file.Options == nil {
+		return nil, nil
+	}
+	if !proto.HasExtension(file.Options, openapi_options.E_Openapiv2Swagger) {
+		return nil, nil
+	}
+	ext := proto.GetExtension(file.Options, openapi_options.E_Openapiv2Swagger)
+	opts, ok := ext.(*openapi_options.Swagger)
+	if !ok {
+		return nil, fmt.Errorf("extension is %T; want a OpenAPI object", ext)
+	}
+	return opts, nil
+}
+
+func extractJSONSchemaFromFieldDescriptor(fd *descriptorpb.FieldDescriptorProto) (*openapi_options.JSONSchema, error) {
+	if fd.Options == nil {
+		return nil, nil
+	}
+	if !proto.HasExtension(fd.Options, openapi_options.E_Openapiv2Field) {
+		return nil, nil
+	}
+	ext := proto.GetExtension(fd.Options, openapi_options.E_Openapiv2Field)
+	opts, ok := ext.(*openapi_options.JSONSchema)
+	if !ok {
+		return nil, fmt.Errorf("extension is %T; want a JSONSchema object", ext)
+	}
+	return opts, nil
+}
+
+func extractFieldBehaviorFromFieldDescriptor(fd *descriptorpb.FieldDescriptorProto) ([]annotations.FieldBehavior, error) {
+	if fd.Options == nil {
+		return nil, nil
+	}
+	if !proto.HasExtension(fd.Options, annotations.E_FieldBehavior) {
+		return nil, nil
+	}
+	ext := proto.GetExtension(fd.Options, annotations.E_FieldBehavior)
+	opts, ok := ext.([]annotations.FieldBehavior)
+	if !ok {
+		return nil, fmt.Errorf("extension is %T; want a []FieldBehavior object", ext)
+	}
+	return opts, nil
+}
+
+func getMethodOpenAPIOption(reg *descriptor.Registry, meth *descriptor.Method) (*openapi_options.Operation, error) {
+	opts, err := extractOperationOptionFromMethodDescriptor(meth.MethodDescriptorProto)
+	if err != nil {
+		return nil, err
+	}
+	if opts != nil {
+		return opts, nil
+	}
+	opts, ok := reg.GetOpenAPIMethodOption(meth.FQMN())
+	if !ok {
+		return nil, nil
+	}
+	return opts, nil
+}
+
+func getMessageOpenAPIOption(reg *descriptor.Registry, msg *descriptor.Message) (*openapi_options.Schema, error) {
+	opts, err := extractSchemaOptionFromMessageDescriptor(msg.DescriptorProto)
+	if err != nil {
+		return nil, err
+	}
+	if opts != nil {
+		return opts, nil
+	}
+	opts, ok := reg.GetOpenAPIMessageOption(msg.FQMN())
+	if !ok {
+		return nil, nil
+	}
+	return opts, nil
+}
+
+func getFileOpenAPIOption(reg *descriptor.Registry, file *descriptor.File) (*openapi_options.Swagger, error) {
+	opts, err := extractOpenAPIOptionFromFileDescriptor(file.FileDescriptorProto)
+	if err != nil {
+		return nil, err
+	}
+	if opts != nil {
+		return opts, nil
+	}
+	opts, ok := reg.GetOpenAPIFileOption(*file.Name)
+	if !ok {
+		return nil, nil
+	}
+	return opts, nil
+}
+
+func getFieldOpenAPIOption(reg *descriptor.Registry, fd *descriptor.Field) (*openapi_options.JSONSchema, error) {
+	opts, err := extractJSONSchemaFromFieldDescriptor(fd.FieldDescriptorProto)
+	if err != nil {
+		return nil, err
+	}
+	if opts != nil {
+		return opts, nil
+	}
+	opts, ok := reg.GetOpenAPIFieldOption(fd.FQFN())
+	if !ok {
+		return nil, nil
+	}
+	return opts, nil
+}
+
+func getFieldBehaviorOption(reg *descriptor.Registry, fd *descriptor.Field) ([]annotations.FieldBehavior, error) {
+	opts, err := extractFieldBehaviorFromFieldDescriptor(fd.FieldDescriptorProto)
+	if err != nil {
+		return nil, err
+	}
+	if opts != nil {
+		return opts, nil
+	}
+	return opts, nil
+}
+
+func protoJSONSchemaToOpenAPISchemaCore(j *openapi_options.JSONSchema, reg *descriptor.Registry, refs refMap) schemaCore {
+	ret := schemaCore{}
+
+	if j.GetRef() != "" {
+		openapiName, ok := fullyQualifiedNameToOpenAPIName(j.GetRef(), reg)
+		if ok {
+			ret.Ref = "#/definitions/" + openapiName
+			if refs != nil {
+				refs[j.GetRef()] = struct{}{}
+			}
+		} else {
+			ret.Ref += j.GetRef()
+		}
+	} else {
+		f, t := protoJSONSchemaTypeToFormat(j.GetType())
+		ret.Format = f
+		ret.Type = t
+	}
+
+	return ret
+}
+
+func updateswaggerObjectFromJSONSchema(s *openapiSchemaObject, j *openapi_options.JSONSchema, reg *descriptor.Registry, data interface{}) {
+	s.Title = j.GetTitle()
+	s.Description = j.GetDescription()
+	if reg.GetUseGoTemplate() {
+		s.Title = goTemplateComments(s.Title, data, reg)
+		s.Description = goTemplateComments(s.Description, data, reg)
+	}
+
+	s.ReadOnly = j.GetReadOnly()
+	s.MultipleOf = j.GetMultipleOf()
+	s.Maximum = j.GetMaximum()
+	s.ExclusiveMaximum = j.GetExclusiveMaximum()
+	s.Minimum = j.GetMinimum()
+	s.ExclusiveMinimum = j.GetExclusiveMinimum()
+	s.MaxLength = j.GetMaxLength()
+	s.MinLength = j.GetMinLength()
+	s.Pattern = j.GetPattern()
+	s.Default = j.GetDefault()
+	s.MaxItems = j.GetMaxItems()
+	s.MinItems = j.GetMinItems()
+	s.UniqueItems = j.GetUniqueItems()
+	s.MaxProperties = j.GetMaxProperties()
+	s.MinProperties = j.GetMinProperties()
+	s.Required = j.GetRequired()
+	s.Enum = j.GetEnum()
+	if overrideType := j.GetType(); len(overrideType) > 0 {
+		s.Type = strings.ToLower(overrideType[0].String())
+	}
+	if j != nil && j.GetExample() != "" {
+		s.Example = json.RawMessage(j.GetExample())
+	}
+	if j != nil && j.GetFormat() != "" {
+		s.Format = j.GetFormat()
+	}
+}
+
+func updateSwaggerObjectFromFieldBehavior(s *openapiSchemaObject, j []annotations.FieldBehavior, field *descriptor.Field) {
+	// Per the JSON Reference syntax: Any members other than "$ref" in a JSON Reference object SHALL be ignored.
+	// https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03#section-3
+	if s.Ref != "" {
+		return
+	}
+
+	for _, fb := range j {
+		switch fb {
+		case annotations.FieldBehavior_REQUIRED:
+			s.Required = append(s.Required, *field.Name)
+		case annotations.FieldBehavior_OUTPUT_ONLY:
+			s.ReadOnly = true
+		case annotations.FieldBehavior_FIELD_BEHAVIOR_UNSPECIFIED:
+		case annotations.FieldBehavior_OPTIONAL:
+		case annotations.FieldBehavior_INPUT_ONLY:
+			// OpenAPI v3 supports a writeOnly property, but this is not supported in Open API v2
+		case annotations.FieldBehavior_IMMUTABLE:
+		}
+	}
+}
+
+func openapiSchemaFromProtoSchema(s *openapi_options.Schema, reg *descriptor.Registry, refs refMap, data interface{}) openapiSchemaObject {
+	ret := openapiSchemaObject{
+		ExternalDocs: protoExternalDocumentationToOpenAPIExternalDocumentation(s.GetExternalDocs(), reg, data),
+	}
+
+	ret.schemaCore = protoJSONSchemaToOpenAPISchemaCore(s.GetJsonSchema(), reg, refs)
+	updateswaggerObjectFromJSONSchema(&ret, s.GetJsonSchema(), reg, data)
+
+	if s != nil && s.Example != "" {
+		ret.Example = json.RawMessage(s.Example)
+	}
+
+	return ret
+}
+
+func openapiExamplesFromProtoExamples(in map[string]string) map[string]interface{} {
+	if len(in) == 0 {
+		return nil
+	}
+	out := make(map[string]interface{})
+	for mimeType, exampleStr := range in {
+		switch mimeType {
+		case "application/json":
+			// JSON example objects are rendered raw.
+			out[mimeType] = json.RawMessage(exampleStr)
+		default:
+			// All other mimetype examples are rendered as strings.
+			out[mimeType] = exampleStr
+		}
+	}
+	return out
+}
+
+func protoJSONSchemaTypeToFormat(in []openapi_options.JSONSchema_JSONSchemaSimpleTypes) (string, string) {
+	if len(in) == 0 {
+		return "", ""
+	}
+
+	// Can't support more than 1 type, just return the first element.
+	// This is due to an inconsistency in the design of the openapiv2 proto
+	// and that used in schemaCore. schemaCore uses the v3 definition of types,
+	// which only allows a single string, while the openapiv2 proto uses the OpenAPI v2
+	// definition, which defers to the JSON schema definition, which allows a string or an array.
+	// Sources:
+	// https://swagger.io/specification/#itemsObject
+	// https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.2
+	switch in[0] {
+	case openapi_options.JSONSchema_UNKNOWN, openapi_options.JSONSchema_NULL:
+		return "", ""
+	case openapi_options.JSONSchema_OBJECT:
+		return "object", ""
+	case openapi_options.JSONSchema_ARRAY:
+		return "array", ""
+	case openapi_options.JSONSchema_BOOLEAN:
+		// NOTE: in OpenAPI specification, format should be empty on boolean type
+		return "boolean", ""
+	case openapi_options.JSONSchema_INTEGER:
+		return "integer", "int32"
+	case openapi_options.JSONSchema_NUMBER:
+		return "number", "double"
+	case openapi_options.JSONSchema_STRING:
+		// NOTE: in OpenAPI specification, format should be empty on string type
+		return "string", ""
+	default:
+		// Maybe panic?
+		return "", ""
+	}
+}
+
+func protoExternalDocumentationToOpenAPIExternalDocumentation(in *openapi_options.ExternalDocumentation, reg *descriptor.Registry, data interface{}) *openapiExternalDocumentationObject {
+	if in == nil {
+		return nil
+	}
+
+	if reg.GetUseGoTemplate() {
+		in.Description = goTemplateComments(in.Description, data, reg)
+	}
+
+	return &openapiExternalDocumentationObject{
+		Description: in.Description,
+		URL:         in.Url,
+	}
+}
+
+func addCustomRefs(d openapiDefinitionsObject, reg *descriptor.Registry, refs refMap) {
+	if len(refs) == 0 {
+		return
+	}
+	msgMap := make(messageMap)
+	enumMap := make(enumMap)
+	for ref := range refs {
+		swgName, swgOk := fullyQualifiedNameToOpenAPIName(ref, reg)
+		if !swgOk {
+			glog.Errorf("can't resolve OpenAPI name from CustomRef '%v'", ref)
+			continue
+		}
+		if _, ok := d[swgName]; ok {
+			// Skip already existing definitions
+			delete(refs, ref)
+			continue
+		}
+		msg, err := reg.LookupMsg("", ref)
+		if err == nil {
+			msgMap[swgName] = msg
+			continue
+		}
+		enum, err := reg.LookupEnum("", ref)
+		if err == nil {
+			enumMap[swgName] = enum
+			continue
+		}
+
+		// ?? Should be either enum or msg
+	}
+	renderMessagesAsDefinition(msgMap, d, reg, refs, nil)
+	renderEnumerationsAsDefinition(enumMap, d, reg)
+
+	// Run again in case any new refs were added
+	addCustomRefs(d, reg, refs)
+}
+
+func lowerCamelCase(fieldName string, fields []*descriptor.Field, msgs []*descriptor.Message) string {
+	for _, oneField := range fields {
+		if oneField.GetName() == fieldName {
+			return oneField.GetJsonName()
+		}
+	}
+	messageNameToFieldsToJSONName := make(map[string]map[string]string)
+	fieldNameToType := make(map[string]string)
+	for _, msg := range msgs {
+		fieldNameToJSONName := make(map[string]string)
+		for _, oneField := range msg.GetField() {
+			fieldNameToJSONName[oneField.GetName()] = oneField.GetJsonName()
+			fieldNameToType[oneField.GetName()] = oneField.GetTypeName()
+		}
+		messageNameToFieldsToJSONName[msg.GetName()] = fieldNameToJSONName
+	}
+	if strings.Contains(fieldName, ".") {
+		fieldNames := strings.Split(fieldName, ".")
+		fieldNamesWithCamelCase := make([]string, 0)
+		for i := 0; i < len(fieldNames)-1; i++ {
+			fieldNamesWithCamelCase = append(fieldNamesWithCamelCase, doCamelCase(string(fieldNames[i])))
+		}
+		prefix := strings.Join(fieldNamesWithCamelCase, ".")
+		reservedJSONName := getReservedJSONName(fieldName, messageNameToFieldsToJSONName, fieldNameToType)
+		if reservedJSONName != "" {
+			return prefix + "." + reservedJSONName
+		}
+	}
+	return doCamelCase(fieldName)
+}
+
+func doCamelCase(input string) string {
+	parameterString := casing.Camel(input)
+	builder := &strings.Builder{}
+	builder.WriteString(strings.ToLower(string(parameterString[0])))
+	builder.WriteString(parameterString[1:])
+	return builder.String()
+}
+
+func getReservedJSONName(fieldName string, messageNameToFieldsToJSONName map[string]map[string]string, fieldNameToType map[string]string) string {
+	if len(strings.Split(fieldName, ".")) == 2 {
+		fieldNames := strings.Split(fieldName, ".")
+		firstVariable := fieldNames[0]
+		firstType := fieldNameToType[firstVariable]
+		firstTypeShortNames := strings.Split(firstType, ".")
+		firstTypeShortName := firstTypeShortNames[len(firstTypeShortNames)-1]
+		return messageNameToFieldsToJSONName[firstTypeShortName][fieldNames[1]]
+	}
+	fieldNames := strings.Split(fieldName, ".")
+	return getReservedJSONName(strings.Join(fieldNames[1:], "."), messageNameToFieldsToJSONName, fieldNameToType)
+}
+
+func find(a []string, x string) int {
+	// This is a linear search but we are dealing with a small number of fields
+	for i, n := range a {
+		if x == n {
+			return i
+		}
+	}
+	return -1
+}

+ 4635 - 0
protoc-gen-openapiv2/internal/genopenapi/template_test.go

@@ -0,0 +1,4635 @@
+package genopenapi
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math"
+	"reflect"
+	"strings"
+	"testing"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor/openapiconfig"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/httprule"
+	"github.com/google/go-cmp/cmp"
+	openapi_options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
+	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
+	"google.golang.org/genproto/googleapis/api/annotations"
+	"google.golang.org/genproto/protobuf/field_mask"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protodesc"
+	"google.golang.org/protobuf/types/descriptorpb"
+	"google.golang.org/protobuf/types/known/durationpb"
+	"google.golang.org/protobuf/types/known/structpb"
+	"google.golang.org/protobuf/types/known/timestamppb"
+	"google.golang.org/protobuf/types/known/wrapperspb"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+var marshaler = &runtime.JSONPb{}
+
+func crossLinkFixture(f *descriptor.File) *descriptor.File {
+	for _, m := range f.Messages {
+		m.File = f
+	}
+	for _, svc := range f.Services {
+		svc.File = f
+		for _, m := range svc.Methods {
+			m.Service = svc
+			for _, b := range m.Bindings {
+				b.Method = m
+				for _, param := range b.PathParams {
+					param.Method = m
+				}
+			}
+		}
+	}
+	return f
+}
+
+func reqFromFile(f *descriptor.File) *pluginpb.CodeGeneratorRequest {
+	return &pluginpb.CodeGeneratorRequest{
+		ProtoFile: []*descriptorpb.FileDescriptorProto{
+			f.FileDescriptorProto,
+		},
+		FileToGenerate: []string{f.GetName()},
+	}
+}
+
+func TestMessageToQueryParametersWithEnumAsInt(t *testing.T) {
+	type test struct {
+		MsgDescs []*descriptorpb.DescriptorProto
+		Message  string
+		Params   []openapiParameterObject
+	}
+
+	tests := []test{
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("a"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(1),
+						},
+						{
+							Name:   proto.String("b"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
+							Number: proto.Int32(2),
+						},
+						{
+							Name:   proto.String("c"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Label:  descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+							Number: proto.Int32(3),
+						},
+					},
+				},
+			},
+			Message: "ExampleMessage",
+			Params: []openapiParameterObject{
+				{
+					Name:     "a",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+				{
+					Name:     "b",
+					In:       "query",
+					Required: false,
+					Type:     "number",
+					Format:   "double",
+				},
+				{
+					Name:             "c",
+					In:               "query",
+					Required:         false,
+					Type:             "array",
+					CollectionFormat: "multi",
+				},
+			},
+		},
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("nested"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.Nested"),
+							Number:   proto.Int32(1),
+						},
+					},
+				},
+				{
+					Name: proto.String("Nested"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("a"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(1),
+						},
+						{
+							Name:     proto.String("deep"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.Nested.DeepNested"),
+							Number:   proto.Int32(2),
+						},
+					},
+					NestedType: []*descriptorpb.DescriptorProto{{
+						Name: proto.String("DeepNested"),
+						Field: []*descriptorpb.FieldDescriptorProto{
+							{
+								Name:   proto.String("b"),
+								Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+								Number: proto.Int32(1),
+							},
+							{
+								Name:     proto.String("c"),
+								Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+								TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"),
+								Number:   proto.Int32(2),
+							},
+						},
+						EnumType: []*descriptorpb.EnumDescriptorProto{
+							{
+								Name: proto.String("DeepEnum"),
+								Value: []*descriptorpb.EnumValueDescriptorProto{
+									{Name: proto.String("FALSE"), Number: proto.Int32(0)},
+									{Name: proto.String("TRUE"), Number: proto.Int32(1)},
+								},
+							},
+						},
+					}},
+				},
+			},
+			Message: "ExampleMessage",
+			Params: []openapiParameterObject{
+				{
+					Name:     "nested.a",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+				{
+					Name:     "nested.deep.b",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+				{
+					Name:     "nested.deep.c",
+					In:       "query",
+					Required: false,
+					Type:     "integer",
+					Enum:     []string{"0", "1"},
+					Default:  "0",
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		reg.SetEnumsAsInts(true)
+		msgs := []*descriptor.Message{}
+		for _, msgdesc := range test.MsgDescs {
+			msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+		}
+		file := descriptor.File{
+			FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+				SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+				Name:           proto.String("example.proto"),
+				Package:        proto.String("example"),
+				Dependency:     []string{},
+				MessageType:    test.MsgDescs,
+				Service:        []*descriptorpb.ServiceDescriptorProto{},
+				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: msgs,
+		}
+		err := reg.Load(&pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+		})
+		if err != nil {
+			t.Fatalf("failed to load code generator request: %v", err)
+		}
+
+		message, err := reg.LookupMsg("", ".example."+test.Message)
+		if err != nil {
+			t.Fatalf("failed to lookup message: %s", err)
+		}
+		params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
+		if err != nil {
+			t.Fatalf("failed to convert message to query parameters: %s", err)
+		}
+		// avoid checking Items for array types
+		for i := range params {
+			params[i].Items = nil
+		}
+		if !reflect.DeepEqual(params, test.Params) {
+			t.Errorf("expected %v, got %v", test.Params, params)
+		}
+	}
+}
+
+func TestMessageToQueryParameters(t *testing.T) {
+	type test struct {
+		MsgDescs []*descriptorpb.DescriptorProto
+		Message  string
+		Params   []openapiParameterObject
+	}
+
+	tests := []test{
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("a"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(1),
+						},
+						{
+							Name:   proto.String("b"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
+							Number: proto.Int32(2),
+						},
+						{
+							Name:   proto.String("c"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Label:  descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+							Number: proto.Int32(3),
+						},
+					},
+				},
+			},
+			Message: "ExampleMessage",
+			Params: []openapiParameterObject{
+				{
+					Name:     "a",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+				{
+					Name:     "b",
+					In:       "query",
+					Required: false,
+					Type:     "number",
+					Format:   "double",
+				},
+				{
+					Name:             "c",
+					In:               "query",
+					Required:         false,
+					Type:             "array",
+					CollectionFormat: "multi",
+				},
+			},
+		},
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("nested"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.Nested"),
+							Number:   proto.Int32(1),
+						},
+					},
+				},
+				{
+					Name: proto.String("Nested"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("a"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(1),
+						},
+						{
+							Name:     proto.String("deep"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.Nested.DeepNested"),
+							Number:   proto.Int32(2),
+						},
+					},
+					NestedType: []*descriptorpb.DescriptorProto{{
+						Name: proto.String("DeepNested"),
+						Field: []*descriptorpb.FieldDescriptorProto{
+							{
+								Name:   proto.String("b"),
+								Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+								Number: proto.Int32(1),
+							},
+							{
+								Name:     proto.String("c"),
+								Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+								TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"),
+								Number:   proto.Int32(2),
+							},
+						},
+						EnumType: []*descriptorpb.EnumDescriptorProto{
+							{
+								Name: proto.String("DeepEnum"),
+								Value: []*descriptorpb.EnumValueDescriptorProto{
+									{Name: proto.String("FALSE"), Number: proto.Int32(0)},
+									{Name: proto.String("TRUE"), Number: proto.Int32(1)},
+								},
+							},
+						},
+					}},
+				},
+			},
+			Message: "ExampleMessage",
+			Params: []openapiParameterObject{
+				{
+					Name:     "nested.a",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+				{
+					Name:     "nested.deep.b",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+				{
+					Name:     "nested.deep.c",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+					Enum:     []string{"FALSE", "TRUE"},
+					Default:  "FALSE",
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		msgs := []*descriptor.Message{}
+		for _, msgdesc := range test.MsgDescs {
+			msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+		}
+		file := descriptor.File{
+			FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+				SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+				Name:           proto.String("example.proto"),
+				Package:        proto.String("example"),
+				Dependency:     []string{},
+				MessageType:    test.MsgDescs,
+				Service:        []*descriptorpb.ServiceDescriptorProto{},
+				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: msgs,
+		}
+		err := reg.Load(&pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+		})
+		if err != nil {
+			t.Fatalf("failed to load code generator request: %v", err)
+		}
+
+		message, err := reg.LookupMsg("", ".example."+test.Message)
+		if err != nil {
+			t.Fatalf("failed to lookup message: %s", err)
+		}
+		params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
+		if err != nil {
+			t.Fatalf("failed to convert message to query parameters: %s", err)
+		}
+		// avoid checking Items for array types
+		for i := range params {
+			params[i].Items = nil
+		}
+		if !reflect.DeepEqual(params, test.Params) {
+			t.Errorf("expected %v, got %v", test.Params, params)
+		}
+	}
+}
+
+// TestMessagetoQueryParametersNoRecursive, is a check that cyclical references between messages
+//
+//	are not falsely detected given previous known edge-cases.
+func TestMessageToQueryParametersNoRecursive(t *testing.T) {
+	type test struct {
+		MsgDescs []*descriptorpb.DescriptorProto
+		Message  string
+	}
+
+	tests := []test{
+		// First test:
+		// Here is a message that has two of another message adjacent to one another in a nested message.
+		// There is no loop but this was previouly falsely flagged as a cycle.
+		// Example proto:
+		// message NonRecursiveMessage {
+		//      string field = 1;
+		// }
+		// message BaseMessage {
+		//      NonRecursiveMessage first = 1;
+		//      NonRecursiveMessage second = 2;
+		// }
+		// message QueryMessage {
+		//      BaseMessage first = 1;
+		//      string second = 2;
+		// }
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("QueryMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("first"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.BaseMessage"),
+							Number:   proto.Int32(1),
+						},
+						{
+							Name:   proto.String("second"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(2),
+						},
+					},
+				},
+				{
+					Name: proto.String("BaseMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("first"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.NonRecursiveMessage"),
+							Number:   proto.Int32(1),
+						},
+						{
+							Name:     proto.String("second"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.NonRecursiveMessage"),
+							Number:   proto.Int32(2),
+						},
+					},
+				},
+				// Note there is no recursive nature to this message
+				{
+					Name: proto.String("NonRecursiveMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name: proto.String("field"),
+							//Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(1),
+						},
+					},
+				},
+			},
+			Message: "QueryMessage",
+		},
+	}
+
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		msgs := []*descriptor.Message{}
+		for _, msgdesc := range test.MsgDescs {
+			msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+		}
+		file := descriptor.File{
+			FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+				SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+				Name:           proto.String("example.proto"),
+				Package:        proto.String("example"),
+				Dependency:     []string{},
+				MessageType:    test.MsgDescs,
+				Service:        []*descriptorpb.ServiceDescriptorProto{},
+				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: msgs,
+		}
+		err := reg.Load(&pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+		})
+		if err != nil {
+			t.Fatalf("failed to load code generator request: %v", err)
+		}
+
+		message, err := reg.LookupMsg("", ".example."+test.Message)
+		if err != nil {
+			t.Fatalf("failed to lookup message: %s", err)
+		}
+
+		_, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
+		if err != nil {
+			t.Fatalf("No recursion error should be thrown: %s", err)
+		}
+	}
+}
+
+// TestMessagetoQueryParametersRecursive, is a check that cyclical references between messages
+//
+//	are handled gracefully. The goal is to insure that attempts to add messages with cyclical
+//	references to query-parameters returns an error message.
+func TestMessageToQueryParametersRecursive(t *testing.T) {
+	type test struct {
+		MsgDescs []*descriptorpb.DescriptorProto
+		Message  string
+	}
+
+	tests := []test{
+		// First test:
+		// Here we test that a message that references it self through a field will return an error.
+		// Example proto:
+		// message DirectRecursiveMessage {
+		//      DirectRecursiveMessage nested = 1;
+		// }
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("DirectRecursiveMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("nested"),
+							Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.DirectRecursiveMessage"),
+							Number:   proto.Int32(1),
+						},
+					},
+				},
+			},
+			Message: "DirectRecursiveMessage",
+		},
+		// Second test:
+		// Here we test that a cycle through multiple messages is detected and that an error is returned.
+		// Sample:
+		// message Root { NodeMessage nested = 1; }
+		// message NodeMessage { CycleMessage nested = 1; }
+		// message CycleMessage { Root nested = 1; }
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("RootMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("nested"),
+							Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.NodeMessage"),
+							Number:   proto.Int32(1),
+						},
+					},
+				},
+				{
+					Name: proto.String("NodeMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("nested"),
+							Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.CycleMessage"),
+							Number:   proto.Int32(1),
+						},
+					},
+				},
+				{
+					Name: proto.String("CycleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("nested"),
+							Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.RootMessage"),
+							Number:   proto.Int32(1),
+						},
+					},
+				},
+			},
+			Message: "RootMessage",
+		},
+	}
+
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		msgs := []*descriptor.Message{}
+		for _, msgdesc := range test.MsgDescs {
+			msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+		}
+		file := descriptor.File{
+			FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+				SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+				Name:           proto.String("example.proto"),
+				Package:        proto.String("example"),
+				Dependency:     []string{},
+				MessageType:    test.MsgDescs,
+				Service:        []*descriptorpb.ServiceDescriptorProto{},
+				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: msgs,
+		}
+		err := reg.Load(&pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+		})
+		if err != nil {
+			t.Fatalf("failed to load code generator request: %v", err)
+		}
+
+		message, err := reg.LookupMsg("", ".example."+test.Message)
+		if err != nil {
+			t.Fatalf("failed to lookup message: %s", err)
+		}
+		_, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
+		if err == nil {
+			t.Fatalf("It should not be allowed to have recursive query parameters")
+		}
+	}
+}
+
+func TestMessageToQueryParametersWithJsonName(t *testing.T) {
+	type test struct {
+		MsgDescs []*descriptorpb.DescriptorProto
+		Message  string
+		Params   []openapiParameterObject
+	}
+
+	tests := []test{
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("test_field_a"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:   proto.Int32(1),
+							JsonName: proto.String("testFieldA"),
+						},
+					},
+				},
+			},
+			Message: "ExampleMessage",
+			Params: []openapiParameterObject{
+				{
+					Name:     "testFieldA",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+			},
+		},
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("SubMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("test_field_a"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:   proto.Int32(1),
+							JsonName: proto.String("testFieldA"),
+						},
+					},
+				},
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("sub_message"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".example.SubMessage"),
+							Number:   proto.Int32(1),
+							JsonName: proto.String("subMessage"),
+						},
+					},
+				},
+			},
+			Message: "ExampleMessage",
+			Params: []openapiParameterObject{
+				{
+					Name:     "subMessage.testFieldA",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		reg.SetUseJSONNamesForFields(true)
+		msgs := []*descriptor.Message{}
+		for _, msgdesc := range test.MsgDescs {
+			msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+		}
+		file := descriptor.File{
+			FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+				SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+				Name:           proto.String("example.proto"),
+				Package:        proto.String("example"),
+				Dependency:     []string{},
+				MessageType:    test.MsgDescs,
+				Service:        []*descriptorpb.ServiceDescriptorProto{},
+				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: msgs,
+		}
+		err := reg.Load(&pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+		})
+		if err != nil {
+			t.Fatalf("failed to load code generator request: %v", err)
+		}
+
+		message, err := reg.LookupMsg("", ".example."+test.Message)
+		if err != nil {
+			t.Fatalf("failed to lookup message: %s", err)
+		}
+		params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
+		if err != nil {
+			t.Fatalf("failed to convert message to query parameters: %s", err)
+		}
+		if !reflect.DeepEqual(params, test.Params) {
+			t.Errorf("expected %v, got %v", test.Params, params)
+		}
+	}
+}
+
+func TestMessageToQueryParametersWellKnownTypes(t *testing.T) {
+	type test struct {
+		MsgDescs          []*descriptorpb.DescriptorProto
+		WellKnownMsgDescs []*descriptorpb.DescriptorProto
+		Message           string
+		Params            []openapiParameterObject
+	}
+
+	tests := []test{
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:     proto.String("a_field_mask"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".google.protobuf.FieldMask"),
+							Number:   proto.Int32(1),
+						},
+						{
+							Name:     proto.String("a_timestamp"),
+							Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+							TypeName: proto.String(".google.protobuf.Timestamp"),
+							Number:   proto.Int32(2),
+						},
+					},
+				},
+			},
+			WellKnownMsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("FieldMask"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("paths"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Label:  descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+							Number: proto.Int32(1),
+						},
+					},
+				},
+				{
+					Name: proto.String("Timestamp"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("seconds"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
+							Number: proto.Int32(1),
+						},
+						{
+							Name:   proto.String("nanos"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+							Number: proto.Int32(2),
+						},
+					},
+				},
+			},
+			Message: "ExampleMessage",
+			Params: []openapiParameterObject{
+				{
+					Name:     "a_field_mask",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+				{
+					Name:     "a_timestamp",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+					Format:   "date-time",
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		reg.SetEnumsAsInts(true)
+		err := reg.Load(&pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{
+				{
+					SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+					Name:           proto.String("google/well_known.proto"),
+					Package:        proto.String("google.protobuf"),
+					Dependency:     []string{},
+					MessageType:    test.WellKnownMsgDescs,
+					Service:        []*descriptorpb.ServiceDescriptorProto{},
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("google/well_known"),
+					},
+				},
+				{
+					SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+					Name:           proto.String("acme/example.proto"),
+					Package:        proto.String("example"),
+					Dependency:     []string{"google/well_known.proto"},
+					MessageType:    test.MsgDescs,
+					Service:        []*descriptorpb.ServiceDescriptorProto{},
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("acme/example"),
+					},
+				},
+			},
+		})
+		if err != nil {
+			t.Fatalf("failed to load CodeGeneratorRequest: %v", err)
+		}
+
+		message, err := reg.LookupMsg("", ".example."+test.Message)
+		if err != nil {
+			t.Fatalf("failed to lookup message: %s", err)
+		}
+		params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
+		if err != nil {
+			t.Fatalf("failed to convert message to query parameters: %s", err)
+		}
+		if !reflect.DeepEqual(params, test.Params) {
+			t.Errorf("expected %v, got %v", test.Params, params)
+		}
+	}
+}
+
+func TestApplyTemplateSimple(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"),
+	}
+	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",
+								Body:       &descriptor.Body{FieldPath: nil},
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	reg := descriptor.NewRegistry()
+	if err := AddErrorDefs(reg); err != nil {
+		t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+		return
+	}
+	fileCL := crossLinkFixture(&file)
+	err := reg.Load(reqFromFile(fileCL))
+	if err != nil {
+		t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
+		return
+	}
+	result, err := applyTemplate(param{File: fileCL, reg: reg})
+	if err != nil {
+		t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+		return
+	}
+	if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
+		t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+	}
+	if want, is, name := "", result.BasePath, "BasePath"; !reflect.DeepEqual(is, want) {
+		t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+	}
+	if want, is, name := ([]string)(nil), result.Schemes, "Schemes"; !reflect.DeepEqual(is, want) {
+		t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+	}
+	if want, is, name := []string{"application/json"}, result.Consumes, "Consumes"; !reflect.DeepEqual(is, want) {
+		t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+	}
+	if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) {
+		t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+	}
+
+	// If there was a failure, print out the input and the json result for debugging.
+	if t.Failed() {
+		t.Errorf("had: %s", file)
+		t.Errorf("got: %s", fmt.Sprint(result))
+	}
+}
+
+func TestApplyTemplateMultiService(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"),
+	}
+
+	// Create two services that have the same method name. We will test that the
+	// operation IDs are different
+	svc := &descriptorpb.ServiceDescriptorProto{
+		Name:   proto.String("ExampleService"),
+		Method: []*descriptorpb.MethodDescriptorProto{meth},
+	}
+	svc2 := &descriptorpb.ServiceDescriptorProto{
+		Name:   proto.String("OtherService"),
+		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",
+								Body:       &descriptor.Body{FieldPath: nil},
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/echo",
+								},
+							},
+						},
+					},
+				},
+			},
+			{
+				ServiceDescriptorProto: svc2,
+				Methods: []*descriptor.Method{
+					{
+						MethodDescriptorProto: meth,
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*descriptor.Binding{
+							{
+								HTTPMethod: "GET",
+								Body:       &descriptor.Body{FieldPath: nil},
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/ping",
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	reg := descriptor.NewRegistry()
+	if err := AddErrorDefs(reg); err != nil {
+		t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+		return
+	}
+	fileCL := crossLinkFixture(&file)
+	err := reg.Load(reqFromFile(fileCL))
+	if err != nil {
+		t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
+		return
+	}
+	result, err := applyTemplate(param{File: fileCL, reg: reg})
+	if err != nil {
+		t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+		return
+	}
+
+	// Check that the two services have unique operation IDs even though they
+	// have the same method name.
+	if want, is := "ExampleService_Example", result.Paths["/v1/echo"].Get.OperationID; !reflect.DeepEqual(is, want) {
+		t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want)
+	}
+	if want, is := "OtherService_Example", result.Paths["/v1/ping"].Get.OperationID; !reflect.DeepEqual(is, want) {
+		t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want)
+	}
+
+	// If there was a failure, print out the input and the json result for debugging.
+	if t.Failed() {
+		t.Errorf("had: %s", file)
+		t.Errorf("got: %s", fmt.Sprint(result))
+	}
+}
+
+func TestApplyTemplateOverrideOperationID(t *testing.T) {
+	newFile := func() *descriptor.File {
+		msgdesc := &descriptorpb.DescriptorProto{
+			Name: proto.String("ExampleMessage"),
+		}
+		meth := &descriptorpb.MethodDescriptorProto{
+			Name:       proto.String("Example"),
+			InputType:  proto.String("ExampleMessage"),
+			OutputType: proto.String("ExampleMessage"),
+			Options:    &descriptorpb.MethodOptions{},
+		}
+		svc := &descriptorpb.ServiceDescriptorProto{
+			Name:   proto.String("ExampleService"),
+			Method: []*descriptorpb.MethodDescriptorProto{meth},
+		}
+		msg := &descriptor.Message{
+			DescriptorProto: msgdesc,
+		}
+		return &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",
+									Body:       &descriptor.Body{FieldPath: nil},
+									PathTmpl: httprule.Template{
+										Version:  1,
+										OpCodes:  []int{0, 0},
+										Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+	}
+
+	verifyTemplateFromReq := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File, opts *openapiconfig.OpenAPIOptions) {
+		if err := AddErrorDefs(reg); err != nil {
+			t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+			return
+		}
+		fileCL := crossLinkFixture(file)
+		err := reg.Load(reqFromFile(fileCL))
+		if err != nil {
+			t.Errorf("reg.Load(%#v) failed with %v; want success", *file, err)
+			return
+		}
+		if opts != nil {
+			if err := reg.RegisterOpenAPIOptions(opts); err != nil {
+				t.Fatalf("failed to register OpenAPI options: %s", err)
+			}
+		}
+		result, err := applyTemplate(param{File: fileCL, reg: reg})
+		if err != nil {
+			t.Errorf("applyTemplate(%#v) failed with %v; want success", *file, err)
+			return
+		}
+		if want, is := "MyExample", result.Paths["/v1/echo"].Get.OperationID; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", *file, is, want)
+		}
+
+		// If there was a failure, print out the input and the json result for debugging.
+		if t.Failed() {
+			t.Errorf("had: %s", *file)
+			t.Errorf("got: %s", fmt.Sprint(result))
+		}
+	}
+
+	openapiOperation := openapi_options.Operation{
+		OperationId: "MyExample",
+	}
+
+	t.Run("verify override via method option", func(t *testing.T) {
+		file := newFile()
+		proto.SetExtension(proto.Message(file.Services[0].Methods[0].MethodDescriptorProto.Options),
+			openapi_options.E_Openapiv2Operation, &openapiOperation)
+
+		reg := descriptor.NewRegistry()
+		verifyTemplateFromReq(t, reg, file, nil)
+	})
+
+	t.Run("verify override options annotations", func(t *testing.T) {
+		file := newFile()
+		reg := descriptor.NewRegistry()
+		opts := &openapiconfig.OpenAPIOptions{
+			Method: []*openapiconfig.OpenAPIMethodOption{
+				{
+					Method: "example.ExampleService.Example",
+					Option: &openapiOperation,
+				},
+			},
+		}
+		verifyTemplateFromReq(t, reg, file, opts)
+	})
+}
+
+func TestApplyTemplateExtensions(t *testing.T) {
+	newFile := func() *descriptor.File {
+		msgdesc := &descriptorpb.DescriptorProto{
+			Name: proto.String("ExampleMessage"),
+		}
+		meth := &descriptorpb.MethodDescriptorProto{
+			Name:       proto.String("Example"),
+			InputType:  proto.String("ExampleMessage"),
+			OutputType: proto.String("ExampleMessage"),
+			Options:    &descriptorpb.MethodOptions{},
+		}
+		svc := &descriptorpb.ServiceDescriptorProto{
+			Name:   proto.String("ExampleService"),
+			Method: []*descriptorpb.MethodDescriptorProto{meth},
+		}
+		msg := &descriptor.Message{
+			DescriptorProto: msgdesc,
+		}
+		return &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",
+									Body:       &descriptor.Body{FieldPath: nil},
+									PathTmpl: httprule.Template{
+										Version:  1,
+										OpCodes:  []int{0, 0},
+										Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+	}
+	swagger := openapi_options.Swagger{
+		Info: &openapi_options.Info{
+			Title: "test",
+			Extensions: map[string]*structpb.Value{
+				"x-info-extension": {Kind: &structpb.Value_StringValue{StringValue: "bar"}},
+			},
+		},
+		Extensions: map[string]*structpb.Value{
+			"x-foo": {Kind: &structpb.Value_StringValue{StringValue: "bar"}},
+			"x-bar": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{
+				Values: []*structpb.Value{{Kind: &structpb.Value_StringValue{StringValue: "baz"}}},
+			}}},
+		},
+		SecurityDefinitions: &openapi_options.SecurityDefinitions{
+			Security: map[string]*openapi_options.SecurityScheme{
+				"somescheme": {
+					Extensions: map[string]*structpb.Value{
+						"x-security-baz": {Kind: &structpb.Value_BoolValue{BoolValue: true}},
+					},
+				},
+			},
+		},
+	}
+	openapiOperation := openapi_options.Operation{
+		Responses: map[string]*openapi_options.Response{
+			"200": {
+				Extensions: map[string]*structpb.Value{
+					"x-resp-id": {Kind: &structpb.Value_StringValue{StringValue: "resp1000"}},
+				},
+			},
+		},
+		Extensions: map[string]*structpb.Value{
+			"x-op-foo": {Kind: &structpb.Value_StringValue{StringValue: "baz"}},
+		},
+	}
+	verifyTemplateExtensions := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File,
+		opts *openapiconfig.OpenAPIOptions) {
+		if err := AddErrorDefs(reg); err != nil {
+			t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+			return
+		}
+		fileCL := crossLinkFixture(file)
+		err := reg.Load(reqFromFile(fileCL))
+		if err != nil {
+			t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
+			return
+		}
+		if opts != nil {
+			if err := reg.RegisterOpenAPIOptions(opts); err != nil {
+				t.Fatalf("failed to register OpenAPI annotations: %s", err)
+			}
+		}
+		result, err := applyTemplate(param{File: fileCL, reg: reg})
+		if err != nil {
+			t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+			return
+		}
+		if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+		}
+		if got, want := len(result.extensions), 2; got != want {
+			t.Fatalf("len(applyTemplate(%#v).Extensions) = %d want to be %d", file, got, want)
+		}
+		if got, want := result.extensions[0].key, "x-bar"; got != want {
+			t.Errorf("applyTemplate(%#v).Extensions[0].key = %s want to be %s", file, got, want)
+		}
+		if got, want := result.extensions[1].key, "x-foo"; got != want {
+			t.Errorf("applyTemplate(%#v).Extensions[1].key = %s want to be %s", file, got, want)
+		}
+		{
+			var got []string
+			err = marshaler.Unmarshal(result.extensions[0].value, &got)
+			if err != nil {
+				t.Fatalf("marshaler.Unmarshal failed: %v", err)
+			}
+			want := []string{"baz"}
+			if diff := cmp.Diff(got, want); diff != "" {
+				t.Errorf(diff)
+			}
+		}
+		{
+			var got string
+			err = marshaler.Unmarshal(result.extensions[1].value, &got)
+			if err != nil {
+				t.Fatalf("marshaler.Unmarshal failed: %v", err)
+			}
+			want := "bar"
+			if diff := cmp.Diff(got, want); diff != "" {
+				t.Errorf(diff)
+			}
+		}
+
+		var scheme openapiSecuritySchemeObject
+		for _, v := range result.SecurityDefinitions {
+			scheme = v
+		}
+		if want, is, name := []extension{
+			{key: "x-security-baz", value: json.RawMessage("true")},
+		}, scheme.extensions, "SecurityScheme.Extensions"; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+		}
+
+		if want, is, name := []extension{
+			{key: "x-info-extension", value: json.RawMessage("\"bar\"")},
+		}, result.Info.extensions, "Info.Extensions"; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+		}
+
+		var operation *openapiOperationObject
+		var response openapiResponseObject
+		for _, v := range result.Paths {
+			operation = v.Get
+			response = v.Get.Responses["200"]
+		}
+		if want, is, name := []extension{
+			{key: "x-op-foo", value: json.RawMessage("\"baz\"")},
+		}, operation.extensions, "operation.Extensions"; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+		}
+		if want, is, name := []extension{
+			{key: "x-resp-id", value: json.RawMessage("\"resp1000\"")},
+		}, response.extensions, "response.Extensions"; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+		}
+	}
+	t.Run("verify template options set via proto options", func(t *testing.T) {
+		file := newFile()
+		proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger)
+		proto.SetExtension(proto.Message(file.Services[0].Methods[0].Options), openapi_options.E_Openapiv2Operation, &openapiOperation)
+		reg := descriptor.NewRegistry()
+		verifyTemplateExtensions(t, reg, file, nil)
+	})
+	t.Run("verify template options set via annotations", func(t *testing.T) {
+		file := newFile()
+		opts := &openapiconfig.OpenAPIOptions{
+			File: []*openapiconfig.OpenAPIFileOption{
+				{
+					File:   "example.proto",
+					Option: &swagger,
+				},
+			},
+			Method: []*openapiconfig.OpenAPIMethodOption{
+				{
+					Method: "example.ExampleService.Example",
+					Option: &openapiOperation,
+				},
+			},
+		}
+		reg := descriptor.NewRegistry()
+		verifyTemplateExtensions(t, reg, file, opts)
+	})
+}
+
+func TestApplyTemplateHeaders(t *testing.T) {
+	newFile := func() *descriptor.File {
+		msgdesc := &descriptorpb.DescriptorProto{
+			Name: proto.String("ExampleMessage"),
+		}
+		meth := &descriptorpb.MethodDescriptorProto{
+			Name:       proto.String("Example"),
+			InputType:  proto.String("ExampleMessage"),
+			OutputType: proto.String("ExampleMessage"),
+			Options:    &descriptorpb.MethodOptions{},
+		}
+		svc := &descriptorpb.ServiceDescriptorProto{
+			Name:   proto.String("ExampleService"),
+			Method: []*descriptorpb.MethodDescriptorProto{meth},
+		}
+		msg := &descriptor.Message{
+			DescriptorProto: msgdesc,
+		}
+		return &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",
+									Body:       &descriptor.Body{FieldPath: nil},
+									PathTmpl: httprule.Template{
+										Version:  1,
+										OpCodes:  []int{0, 0},
+										Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+	}
+	openapiOperation := openapi_options.Operation{
+		Responses: map[string]*openapi_options.Response{
+			"200": &openapi_options.Response{
+				Description: "Testing Headers",
+				Headers: map[string]*openapi_options.Header{
+					"string": {
+						Description: "string header description",
+						Type:        "string",
+						Format:      "uuid",
+						Pattern:     "",
+					},
+					"boolean": {
+						Description: "boolean header description",
+						Type:        "boolean",
+						Default:     "true",
+						Pattern:     "^true|false$",
+					},
+					"integer": {
+						Description: "integer header description",
+						Type:        "integer",
+						Default:     "0",
+						Pattern:     "^[0-9]$",
+					},
+					"number": {
+						Description: "number header description",
+						Type:        "number",
+						Default:     "1.2",
+						Pattern:     "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$",
+					},
+				},
+			},
+		},
+	}
+	verifyTemplateHeaders := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File,
+		opts *openapiconfig.OpenAPIOptions) {
+		if err := AddErrorDefs(reg); err != nil {
+			t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+			return
+		}
+		fileCL := crossLinkFixture(file)
+		err := reg.Load(reqFromFile(fileCL))
+		if err != nil {
+			t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
+			return
+		}
+		if opts != nil {
+			if err := reg.RegisterOpenAPIOptions(opts); err != nil {
+				t.Fatalf("failed to register OpenAPI annotations: %s", err)
+			}
+		}
+		result, err := applyTemplate(param{File: fileCL, reg: reg})
+		if err != nil {
+			t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+			return
+		}
+		if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+		}
+
+		var response openapiResponseObject
+		for _, v := range result.Paths {
+			response = v.Get.Responses["200"]
+		}
+		if want, is, name := []openapiHeadersObject{
+			{
+				"String": openapiHeaderObject{
+					Description: "string header description",
+					Type:        "string",
+					Format:      "uuid",
+					Pattern:     "",
+				},
+				"Boolean": openapiHeaderObject{
+					Description: "boolean header description",
+					Type:        "boolean",
+					Default:     json.RawMessage("true"),
+					Pattern:     "^true|false$",
+				},
+				"Integer": openapiHeaderObject{
+					Description: "integer header description",
+					Type:        "integer",
+					Default:     json.RawMessage("0"),
+					Pattern:     "^[0-9]$",
+				},
+				"Number": openapiHeaderObject{
+					Description: "number header description",
+					Type:        "number",
+					Default:     json.RawMessage("1.2"),
+					Pattern:     "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$",
+				},
+			},
+		}[0], response.Headers, "response.Headers"; !reflect.DeepEqual(is, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
+		}
+
+	}
+	t.Run("verify template options set via proto options", func(t *testing.T) {
+		file := newFile()
+		proto.SetExtension(proto.Message(file.Services[0].Methods[0].Options), openapi_options.E_Openapiv2Operation, &openapiOperation)
+		reg := descriptor.NewRegistry()
+		verifyTemplateHeaders(t, reg, file, nil)
+	})
+}
+
+func TestValidateHeaderType(t *testing.T) {
+	type test struct {
+		Type          string
+		Format        string
+		expectedError error
+	}
+	tests := []test{
+		{
+			"string",
+			"date-time",
+			nil,
+		},
+		{
+			"boolean",
+			"",
+			nil,
+		},
+		{
+			"integer",
+			"uint",
+			nil,
+		},
+		{
+			"integer",
+			"uint8",
+			nil,
+		},
+		{
+			"integer",
+			"uint16",
+			nil,
+		},
+		{
+			"integer",
+			"uint32",
+			nil,
+		},
+		{
+			"integer",
+			"uint64",
+			nil,
+		},
+		{
+			"integer",
+			"int",
+			nil,
+		},
+		{
+			"integer",
+			"int8",
+			nil,
+		},
+		{
+			"integer",
+			"int16",
+			nil,
+		},
+		{
+			"integer",
+			"int32",
+			nil,
+		},
+		{
+			"integer",
+			"int64",
+			nil,
+		},
+		{
+			"integer",
+			"float64",
+			errors.New("the provided format \"float64\" is not a valid extension of the type \"integer\""),
+		},
+		{
+			"integer",
+			"uuid",
+			errors.New("the provided format \"uuid\" is not a valid extension of the type \"integer\""),
+		},
+		{
+			"number",
+			"uint",
+			nil,
+		},
+		{
+			"number",
+			"uint8",
+			nil,
+		},
+		{
+			"number",
+			"uint16",
+			nil,
+		},
+		{
+			"number",
+			"uint32",
+			nil,
+		},
+		{
+			"number",
+			"uint64",
+			nil,
+		},
+		{
+			"number",
+			"int",
+			nil,
+		},
+		{
+			"number",
+			"int8",
+			nil,
+		},
+		{
+			"number",
+			"int16",
+			nil,
+		},
+		{
+			"number",
+			"int32",
+			nil,
+		},
+		{
+			"number",
+			"int64",
+			nil,
+		},
+		{
+			"number",
+			"float",
+			nil,
+		},
+		{
+			"number",
+			"float32",
+			nil,
+		},
+		{
+			"number",
+			"float64",
+			nil,
+		},
+		{
+			"number",
+			"complex64",
+			nil,
+		},
+		{
+			"number",
+			"complex128",
+			nil,
+		},
+		{
+			"number",
+			"double",
+			nil,
+		},
+		{
+			"number",
+			"byte",
+			nil,
+		},
+		{
+			"number",
+			"rune",
+			nil,
+		},
+		{
+			"number",
+			"uintptr",
+			nil,
+		},
+		{
+			"number",
+			"date",
+			errors.New("the provided format \"date\" is not a valid extension of the type \"number\""),
+		},
+		{
+			"array",
+			"",
+			errors.New("the provided header type \"array\" is not supported"),
+		},
+		{
+			"foo",
+			"",
+			errors.New("the provided header type \"foo\" is not supported"),
+		},
+	}
+	for _, v := range tests {
+		err := validateHeaderTypeAndFormat(v.Type, v.Format)
+
+		if v.expectedError == nil {
+			if err != nil {
+				t.Errorf("unexpected error %v", err)
+			}
+		} else {
+			if err == nil {
+				t.Fatal("expected header error not returned")
+			}
+			if err.Error() != v.expectedError.Error() {
+				t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error())
+			}
+		}
+	}
+
+}
+
+func TestValidateDefaultValueType(t *testing.T) {
+	type test struct {
+		Type          string
+		Value         string
+		Format        string
+		expectedError error
+	}
+	tests := []test{
+		{
+			"string",
+			`"string"`,
+			"",
+			nil,
+		},
+		{
+			"string",
+			"\"2012-11-01T22:08:41+00:00\"",
+			"date-time",
+			nil,
+		},
+		{
+			"string",
+			"\"2012-11-01\"",
+			"date",
+			nil,
+		},
+		{
+			"string",
+			"0",
+			"",
+			errors.New("the provided default value \"0\" does not match provider type \"string\", or is not properly quoted with escaped quotations"),
+		},
+		{
+			"string",
+			"false",
+			"",
+			errors.New("the provided default value \"false\" does not match provider type \"string\", or is not properly quoted with escaped quotations"),
+		},
+		{
+			"boolean",
+			"true",
+			"",
+			nil,
+		},
+		{
+			"boolean",
+			"0",
+			"",
+			errors.New("the provided default value \"0\" does not match provider type \"boolean\""),
+		},
+		{
+			"boolean",
+			`"string"`,
+			"",
+			errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"boolean\""),
+		},
+		{
+			"number",
+			"1.2",
+			"",
+			nil,
+		},
+		{
+			"number",
+			"123",
+			"",
+			nil,
+		},
+		{
+			"number",
+			"nan",
+			"",
+			errors.New("the provided number \"nan\" is not a valid JSON number"),
+		},
+		{
+			"number",
+			"NaN",
+			"",
+			errors.New("the provided number \"NaN\" is not a valid JSON number"),
+		},
+		{
+			"number",
+			"-459.67",
+			"",
+			nil,
+		},
+		{
+			"number",
+			"inf",
+			"",
+			errors.New("the provided number \"inf\" is not a valid JSON number"),
+		},
+		{
+			"number",
+			"infinity",
+			"",
+			errors.New("the provided number \"infinity\" is not a valid JSON number"),
+		},
+		{
+			"number",
+			"Inf",
+			"",
+			errors.New("the provided number \"Inf\" is not a valid JSON number"),
+		},
+		{
+			"number",
+			"Infinity",
+			"",
+			errors.New("the provided number \"Infinity\" is not a valid JSON number"),
+		},
+		{
+			"number",
+			"false",
+			"",
+			errors.New("the provided default value \"false\" does not match provider type \"number\""),
+		},
+		{
+			"number",
+			`"string"`,
+			"",
+			errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"number\""),
+		},
+		{
+			"integer",
+			"2",
+			"",
+			nil,
+		},
+		{
+			"integer",
+			fmt.Sprint(math.MaxInt32),
+			"int32",
+			nil,
+		},
+		{
+			"integer",
+			fmt.Sprint(math.MaxInt32 + 1),
+			"int32",
+			errors.New("the provided default value \"2147483648\" does not match provided format \"int32\""),
+		},
+		{
+			"integer",
+			fmt.Sprint(math.MaxInt64),
+			"int64",
+			nil,
+		},
+		{
+			"integer",
+			"9223372036854775808",
+			"int64",
+			errors.New("the provided default value \"9223372036854775808\" does not match provided format \"int64\""),
+		},
+		{
+			"integer",
+			"18446744073709551615",
+			"uint64",
+			nil,
+		},
+		{
+			"integer",
+			"false",
+			"",
+			errors.New("the provided default value \"false\" does not match provided type \"integer\""),
+		},
+		{
+			"integer",
+			"1.2",
+			"",
+			errors.New("the provided default value \"1.2\" does not match provided type \"integer\""),
+		},
+		{
+			"integer",
+			`"string"`,
+			"",
+			errors.New("the provided default value \"\\\"string\\\"\" does not match provided type \"integer\""),
+		},
+	}
+	for _, v := range tests {
+		err := validateDefaultValueTypeAndFormat(v.Type, v.Value, v.Format)
+
+		if v.expectedError == nil {
+			if err != nil {
+				t.Errorf("unexpected error '%v'", err)
+			}
+		} else {
+			if err == nil {
+				t.Error("expected update error not returned")
+			}
+			if err.Error() != v.expectedError.Error() {
+				t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error())
+			}
+		}
+	}
+
+}
+
+func TestApplyTemplateRequestWithoutClientStreaming(t *testing.T) {
+	msgdesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("ExampleMessage"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:     proto.String("nested"),
+				Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				TypeName: proto.String("NestedMessage"),
+				Number:   proto.Int32(1),
+			},
+		},
+	}
+	nesteddesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("NestedMessage"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:   proto.String("int32"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				Number: proto.Int32(1),
+			},
+			{
+				Name:   proto.String("bool"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(),
+				Number: proto.Int32(2),
+			},
+		},
+	}
+	meth := &descriptorpb.MethodDescriptorProto{
+		Name:            proto.String("Echo"),
+		InputType:       proto.String("ExampleMessage"),
+		OutputType:      proto.String("ExampleMessage"),
+		ClientStreaming: proto.Bool(false),
+	}
+	svc := &descriptorpb.ServiceDescriptorProto{
+		Name:   proto.String("ExampleService"),
+		Method: []*descriptorpb.MethodDescriptorProto{meth},
+	}
+
+	meth.ServerStreaming = proto.Bool(false)
+
+	msg := &descriptor.Message{
+		DescriptorProto: msgdesc,
+	}
+	nested := &descriptor.Message{
+		DescriptorProto: nesteddesc,
+	}
+
+	nestedField := &descriptor.Field{
+		Message:              msg,
+		FieldDescriptorProto: msg.GetField()[0],
+	}
+	intField := &descriptor.Field{
+		Message:              nested,
+		FieldDescriptorProto: nested.GetField()[0],
+	}
+	boolField := &descriptor.Field{
+		Message:              nested,
+		FieldDescriptorProto: nested.GetField()[1],
+	}
+	file := descriptor.File{
+		FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+			SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+			Name:           proto.String("example.proto"),
+			Package:        proto.String("example"),
+			MessageType:    []*descriptorpb.DescriptorProto{msgdesc, nesteddesc},
+			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, nested},
+		Services: []*descriptor.Service{
+			{
+				ServiceDescriptorProto: svc,
+				Methods: []*descriptor.Method{
+					{
+						MethodDescriptorProto: meth,
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*descriptor.Binding{
+							{
+								HTTPMethod: "POST",
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/echo", // TODO(achew): Figure out what this hsould really be
+								},
+								PathParams: []descriptor.Parameter{
+									{
+										FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+											{
+												Name:   "nested",
+												Target: nestedField,
+											},
+											{
+												Name:   "int32",
+												Target: intField,
+											},
+										}),
+										Target: intField,
+									},
+								},
+								Body: &descriptor.Body{
+									FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+										{
+											Name:   "nested",
+											Target: nestedField,
+										},
+										{
+											Name:   "bool",
+											Target: boolField,
+										},
+									}),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	reg := descriptor.NewRegistry()
+	if err := AddErrorDefs(reg); err != nil {
+		t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+		return
+	}
+	err := reg.Load(&pluginpb.CodeGeneratorRequest{
+		ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+	})
+	if err != nil {
+		t.Fatalf("failed to load code generator request: %v", err)
+	}
+	result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
+	if err != nil {
+		t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+		return
+	}
+	if want, got := "2.0", result.Swagger; !reflect.DeepEqual(got, want) {
+		t.Errorf("applyTemplate(%#v).Swagger = %s want to be %s", file, got, want)
+	}
+	if want, got := "", result.BasePath; !reflect.DeepEqual(got, want) {
+		t.Errorf("applyTemplate(%#v).BasePath = %s want to be %s", file, got, want)
+	}
+	if want, got := ([]string)(nil), result.Schemes; !reflect.DeepEqual(got, want) {
+		t.Errorf("applyTemplate(%#v).Schemes = %s want to be %s", file, got, want)
+	}
+	if want, got := []string{"application/json"}, result.Consumes; !reflect.DeepEqual(got, want) {
+		t.Errorf("applyTemplate(%#v).Consumes = %s want to be %s", file, got, want)
+	}
+	if want, got := []string{"application/json"}, result.Produces; !reflect.DeepEqual(got, want) {
+		t.Errorf("applyTemplate(%#v).Produces = %s want to be %s", file, got, want)
+	}
+
+	// If there was a failure, print out the input and the json result for debugging.
+	if t.Failed() {
+		t.Errorf("had: %s", file)
+		t.Errorf("got: %s", fmt.Sprint(result))
+	}
+}
+
+func TestApplyTemplateRequestWithClientStreaming(t *testing.T) {
+	msgdesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("ExampleMessage"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:     proto.String("nested"),
+				Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				TypeName: proto.String("NestedMessage"),
+				Number:   proto.Int32(1),
+			},
+		},
+	}
+	nesteddesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("NestedMessage"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:   proto.String("int32"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				Number: proto.Int32(1),
+			},
+			{
+				Name:   proto.String("bool"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(),
+				Number: proto.Int32(2),
+			},
+		},
+	}
+	meth := &descriptorpb.MethodDescriptorProto{
+		Name:            proto.String("Echo"),
+		InputType:       proto.String("ExampleMessage"),
+		OutputType:      proto.String("ExampleMessage"),
+		ClientStreaming: proto.Bool(true),
+		ServerStreaming: proto.Bool(true),
+	}
+	svc := &descriptorpb.ServiceDescriptorProto{
+		Name:   proto.String("ExampleService"),
+		Method: []*descriptorpb.MethodDescriptorProto{meth},
+	}
+
+	msg := &descriptor.Message{
+		DescriptorProto: msgdesc,
+	}
+	nested := &descriptor.Message{
+		DescriptorProto: nesteddesc,
+	}
+
+	nestedField := &descriptor.Field{
+		Message:              msg,
+		FieldDescriptorProto: msg.GetField()[0],
+	}
+	intField := &descriptor.Field{
+		Message:              nested,
+		FieldDescriptorProto: nested.GetField()[0],
+	}
+	boolField := &descriptor.Field{
+		Message:              nested,
+		FieldDescriptorProto: nested.GetField()[1],
+	}
+	file := descriptor.File{
+		FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+			SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+			Name:           proto.String("example.proto"),
+			Package:        proto.String("example"),
+			MessageType:    []*descriptorpb.DescriptorProto{msgdesc, nesteddesc},
+			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, nested},
+		Services: []*descriptor.Service{
+			{
+				ServiceDescriptorProto: svc,
+				Methods: []*descriptor.Method{
+					{
+						MethodDescriptorProto: meth,
+						RequestType:           msg,
+						ResponseType:          msg,
+						Bindings: []*descriptor.Binding{
+							{
+								HTTPMethod: "POST",
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/echo", // TODO(achew): Figure out what this hsould really be
+								},
+								PathParams: []descriptor.Parameter{
+									{
+										FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+											{
+												Name:   "nested",
+												Target: nestedField,
+											},
+											{
+												Name:   "int32",
+												Target: intField,
+											},
+										}),
+										Target: intField,
+									},
+								},
+								Body: &descriptor.Body{
+									FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+										{
+											Name:   "nested",
+											Target: nestedField,
+										},
+										{
+											Name:   "bool",
+											Target: boolField,
+										},
+									}),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	reg := descriptor.NewRegistry()
+	if err := AddErrorDefs(reg); err != nil {
+		t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+		return
+	}
+	err := reg.Load(&pluginpb.CodeGeneratorRequest{
+		ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+	})
+	if err != nil {
+		t.Fatalf("failed to load code generator request: %v", err)
+	}
+	result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
+	if err != nil {
+		t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+		return
+	}
+
+	// Only ExampleMessage must be present, not NestedMessage
+	if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
+		t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
+	}
+	if _, ok := result.Paths["/v1/echo"].Post.Responses["200"]; !ok {
+		t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.Paths["/v1/echo"].Post.Responses["200"]`)
+	} else {
+		if want, got, name := "A successful response.(streaming responses)", result.Paths["/v1/echo"].Post.Responses["200"].Description, `result.Paths["/v1/echo"].Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
+		}
+		streamExampleExampleMessage := result.Paths["/v1/echo"].Post.Responses["200"].Schema
+		if want, got, name := "object", streamExampleExampleMessage.Type, `result.Paths["/v1/echo"].Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
+		}
+		if want, got, name := "Stream result of exampleExampleMessage", streamExampleExampleMessage.Title, `result.Paths["/v1/echo"].Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
+		}
+		streamExampleExampleMessageProperties := *(streamExampleExampleMessage.Properties)
+		if want, got, name := 2, len(streamExampleExampleMessageProperties), `len(StreamDefinitions["exampleExampleMessage"].Properties)`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
+		} else {
+			resultProperty := streamExampleExampleMessageProperties[0]
+			if want, got, name := "result", resultProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) {
+				t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
+			}
+			result := resultProperty.Value.(openapiSchemaObject)
+			if want, got, name := "#/definitions/exampleExampleMessage", result.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) {
+				t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
+			}
+			errorProperty := streamExampleExampleMessageProperties[1]
+			if want, got, name := "error", errorProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) {
+				t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
+			}
+			err := errorProperty.Value.(openapiSchemaObject)
+			if want, got, name := "#/definitions/rpcStatus", err.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) {
+				t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
+			}
+		}
+	}
+
+	// If there was a failure, print out the input and the json result for debugging.
+	if t.Failed() {
+		t.Errorf("had: %s", file)
+		t.Errorf("got: %s", fmt.Sprint(result))
+	}
+}
+
+func TestApplyTemplateRequestWithUnusedReferences(t *testing.T) {
+	reqdesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("ExampleMessage"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:   proto.String("string"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Number: proto.Int32(1),
+			},
+		},
+	}
+	respdesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("EmptyMessage"),
+	}
+	meth := &descriptorpb.MethodDescriptorProto{
+		Name:            proto.String("Example"),
+		InputType:       proto.String("ExampleMessage"),
+		OutputType:      proto.String("EmptyMessage"),
+		ClientStreaming: proto.Bool(false),
+		ServerStreaming: proto.Bool(false),
+	}
+	svc := &descriptorpb.ServiceDescriptorProto{
+		Name:   proto.String("ExampleService"),
+		Method: []*descriptorpb.MethodDescriptorProto{meth},
+	}
+
+	req := &descriptor.Message{
+		DescriptorProto: reqdesc,
+	}
+	resp := &descriptor.Message{
+		DescriptorProto: respdesc,
+	}
+	stringField := &descriptor.Field{
+		Message:              req,
+		FieldDescriptorProto: req.GetField()[0],
+	}
+	file := descriptor.File{
+		FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+			SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+			Name:           proto.String("example.proto"),
+			Package:        proto.String("example"),
+			MessageType:    []*descriptorpb.DescriptorProto{reqdesc, respdesc},
+			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{req, resp},
+		Services: []*descriptor.Service{
+			{
+				ServiceDescriptorProto: svc,
+				Methods: []*descriptor.Method{
+					{
+						MethodDescriptorProto: meth,
+						RequestType:           req,
+						ResponseType:          resp,
+						Bindings: []*descriptor.Binding{
+							{
+								HTTPMethod: "GET",
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/example",
+								},
+							},
+							{
+								HTTPMethod: "POST",
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/example/{string}",
+								},
+								PathParams: []descriptor.Parameter{
+									{
+										FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+											{
+												Name:   "string",
+												Target: stringField,
+											},
+										}),
+										Target: stringField,
+									},
+								},
+								Body: &descriptor.Body{
+									FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+										{
+											Name:   "string",
+											Target: stringField,
+										},
+									}),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	reg := descriptor.NewRegistry()
+	if err := AddErrorDefs(reg); err != nil {
+		t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+		return
+	}
+	err := reg.Load(&pluginpb.CodeGeneratorRequest{
+		ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+	})
+	if err != nil {
+		t.Fatalf("failed to load code generator request: %v", err)
+	}
+	result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
+	if err != nil {
+		t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+		return
+	}
+
+	// Only EmptyMessage must be present, not ExampleMessage (plus error status)
+	if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
+		t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
+	}
+
+	// If there was a failure, print out the input and the json result for debugging.
+	if t.Failed() {
+		t.Errorf("had: %s", file)
+		t.Errorf("got: %s", fmt.Sprint(result))
+	}
+}
+
+func TestApplyTemplateRequestWithBodyQueryParameters(t *testing.T) {
+	bookDesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("Book"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:   proto.String("name"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Number: proto.Int32(1),
+			},
+			{
+				Name:   proto.String("id"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Number: proto.Int32(2),
+			},
+		},
+	}
+	createDesc := &descriptorpb.DescriptorProto{
+		Name: proto.String("CreateBookRequest"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:   proto.String("parent"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Number: proto.Int32(1),
+			},
+			{
+				Name:   proto.String("book"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Number: proto.Int32(2),
+			},
+			{
+				Name:   proto.String("book_id"),
+				Label:  descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
+				Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Number: proto.Int32(3),
+			},
+		},
+	}
+	meth := &descriptorpb.MethodDescriptorProto{
+		Name:       proto.String("CreateBook"),
+		InputType:  proto.String("CreateBookRequest"),
+		OutputType: proto.String("Book"),
+	}
+	svc := &descriptorpb.ServiceDescriptorProto{
+		Name:   proto.String("BookService"),
+		Method: []*descriptorpb.MethodDescriptorProto{meth},
+	}
+
+	bookMsg := &descriptor.Message{
+		DescriptorProto: bookDesc,
+	}
+	createMsg := &descriptor.Message{
+		DescriptorProto: createDesc,
+	}
+
+	parentField := &descriptor.Field{
+		Message:              createMsg,
+		FieldDescriptorProto: createMsg.GetField()[0],
+	}
+	bookField := &descriptor.Field{
+		Message:              createMsg,
+		FieldMessage:         bookMsg,
+		FieldDescriptorProto: createMsg.GetField()[1],
+	}
+	bookIDField := &descriptor.Field{
+		Message:              createMsg,
+		FieldDescriptorProto: createMsg.GetField()[2],
+	}
+
+	createMsg.Fields = []*descriptor.Field{parentField, bookField, bookIDField}
+
+	file := descriptor.File{
+		FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+			SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+			Name:           proto.String("book.proto"),
+			MessageType:    []*descriptorpb.DescriptorProto{bookDesc, createDesc},
+			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/book.pb",
+			Name: "book_pb",
+		},
+		Messages: []*descriptor.Message{bookMsg, createMsg},
+		Services: []*descriptor.Service{
+			{
+				ServiceDescriptorProto: svc,
+				Methods: []*descriptor.Method{
+					{
+						MethodDescriptorProto: meth,
+						RequestType:           createMsg,
+						ResponseType:          bookMsg,
+						Bindings: []*descriptor.Binding{
+							{
+								HTTPMethod: "POST",
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/{parent=publishers/*}/books",
+								},
+								PathParams: []descriptor.Parameter{
+									{
+										FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+											{
+												Name:   "parent",
+												Target: parentField,
+											},
+										}),
+										Target: parentField,
+									},
+								},
+								Body: &descriptor.Body{
+									FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
+										{
+											Name:   "book",
+											Target: bookField,
+										},
+									}),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	reg := descriptor.NewRegistry()
+	if err := AddErrorDefs(reg); err != nil {
+		t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
+		return
+	}
+	err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
+	if err != nil {
+		t.Errorf("Registry.Load() failed with %v; want success", err)
+		return
+	}
+	result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
+	if err != nil {
+		t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+		return
+	}
+
+	if _, ok := result.Paths["/v1/{parent=publishers/*}/books"].Post.Responses["200"]; !ok {
+		t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.Paths["/v1/{parent=publishers/*}/books"].Post.Responses["200"]`)
+	} else {
+		if want, got, name := 3, len(result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters), `len(result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters)`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
+		}
+
+		type param struct {
+			Name     string
+			In       string
+			Required bool
+		}
+
+		p0 := result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[0]
+		if want, got, name := (param{"parent", "path", true}), (param{p0.Name, p0.In, p0.Required}), `result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[0]`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, got, want)
+		}
+		p1 := result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[1]
+		if want, got, name := (param{"body", "body", true}), (param{p1.Name, p1.In, p1.Required}), `result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[1]`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, got, want)
+		}
+		p2 := result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[2]
+		if want, got, name := (param{"book_id", "query", false}), (param{p2.Name, p2.In, p2.Required}), `result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[1]`; !reflect.DeepEqual(got, want) {
+			t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, got, want)
+		}
+	}
+
+	// If there was a failure, print out the input and the json result for debugging.
+	if t.Failed() {
+		t.Errorf("had: %s", file)
+		t.Errorf("got: %s", fmt.Sprint(result))
+	}
+}
+
+func generateFieldsForJSONReservedName() []*descriptor.Field {
+	fields := make([]*descriptor.Field, 0)
+	fieldName := string("json_name")
+	fieldJSONName := string("jsonNAME")
+	fieldDescriptor := descriptorpb.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName}
+	field := &descriptor.Field{FieldDescriptorProto: &fieldDescriptor}
+	return append(fields, field)
+}
+
+func generateMsgsForJSONReservedName() []*descriptor.Message {
+	result := make([]*descriptor.Message, 0)
+	// The first message, its field is field_abc and its type is NewType
+	// NewType field_abc
+	fieldName := "field_abc"
+	fieldJSONName := "fieldAbc"
+	messageName1 := "message1"
+	messageType := "pkg.a.NewType"
+	pfd := descriptorpb.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName, TypeName: &messageType}
+	result = append(result,
+		&descriptor.Message{
+			DescriptorProto: &descriptorpb.DescriptorProto{
+				Name: &messageName1, Field: []*descriptorpb.FieldDescriptorProto{&pfd},
+			},
+		})
+	// The second message, its name is NewName, its type is string
+	// message NewType {
+	//    string field_newName [json_name = RESERVEDJSONNAME]
+	// }
+	messageName := "NewType"
+	field := "field_newName"
+	fieldJSONName2 := "RESERVEDJSONNAME"
+	pfd2 := descriptorpb.FieldDescriptorProto{Name: &field, JsonName: &fieldJSONName2}
+	result = append(result, &descriptor.Message{
+		DescriptorProto: &descriptorpb.DescriptorProto{
+			Name: &messageName, Field: []*descriptorpb.FieldDescriptorProto{&pfd2},
+		},
+	})
+	return result
+}
+
+func TestTemplateWithJsonCamelCase(t *testing.T) {
+	var tests = []struct {
+		input    string
+		expected string
+	}{
+		{"/test/{test_id}", "/test/{testId}"},
+		{"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1Id}/test2/{test2Id}"},
+		{"/test1/{test1_id}/{test2_id}", "/test1/{test1Id}/{test2Id}"},
+		{"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1Id}/{test2Id}"},
+		{"/test1/{test1_id1_id2}", "/test1/{test1Id1Id2}"},
+		{"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1Id1Id2}/test2/{test2Id3Id4}"},
+		{"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1Id1Id2}/{test2Id3Id4}"},
+		{"test/{a}", "test/{a}"},
+		{"test/{ab}", "test/{ab}"},
+		{"test/{a_a}", "test/{aA}"},
+		{"test/{ab_c}", "test/{abC}"},
+		{"test/{json_name}", "test/{jsonNAME}"},
+		{"test/{field_abc.field_newName}", "test/{fieldAbc.RESERVEDJSONNAME}"},
+	}
+	reg := descriptor.NewRegistry()
+	reg.SetUseJSONNamesForFields(true)
+	for _, data := range tests {
+		actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		if data.expected != actual {
+			t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
+		}
+	}
+}
+
+func TestTemplateWithoutJsonCamelCase(t *testing.T) {
+	var tests = []struct {
+		input    string
+		expected string
+	}{
+		{"/test/{test_id}", "/test/{test_id}"},
+		{"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1_id}/test2/{test2_id}"},
+		{"/test1/{test1_id}/{test2_id}", "/test1/{test1_id}/{test2_id}"},
+		{"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1_id}/{test2_id}"},
+		{"/test1/{test1_id1_id2}", "/test1/{test1_id1_id2}"},
+		{"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1_id1_id2}/test2/{test2_id3_id4}"},
+		{"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1_id1_id2}/{test2_id3_id4}"},
+		{"test/{a}", "test/{a}"},
+		{"test/{ab}", "test/{ab}"},
+		{"test/{a_a}", "test/{a_a}"},
+		{"test/{json_name}", "test/{json_name}"},
+		{"test/{field_abc.field_newName}", "test/{field_abc.field_newName}"},
+	}
+	reg := descriptor.NewRegistry()
+	reg.SetUseJSONNamesForFields(false)
+	for _, data := range tests {
+		actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		if data.expected != actual {
+			t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
+		}
+	}
+}
+
+func TestTemplateToOpenAPIPath(t *testing.T) {
+	var tests = []struct {
+		input    string
+		expected string
+	}{
+		{"/test", "/test"},
+		{"/{test}", "/{test}"},
+		{"/{test=prefix/*}", "/{test}"},
+		{"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"},
+		{"/{test1}/{test2}", "/{test1}/{test2}"},
+		{"/{test1}/{test2}/", "/{test1}/{test2}/"},
+		{"/{name=prefix/*}", "/{name=prefix/*}"},
+		{"/{name=prefix1/*/prefix2/*}", "/{name=prefix1/*/prefix2/*}"},
+		{"/{user.name=prefix/*}", "/{user.name=prefix/*}"},
+		{"/{user.name=prefix1/*/prefix2/*}", "/{user.name=prefix1/*/prefix2/*}"},
+		{"/{parent=prefix/*}/children", "/{parent=prefix/*}/children"},
+		{"/{name=prefix/*}:customMethod", "/{name=prefix/*}:customMethod"},
+		{"/{name=prefix1/*/prefix2/*}:customMethod", "/{name=prefix1/*/prefix2/*}:customMethod"},
+		{"/{user.name=prefix/*}:customMethod", "/{user.name=prefix/*}:customMethod"},
+		{"/{user.name=prefix1/*/prefix2/*}:customMethod", "/{user.name=prefix1/*/prefix2/*}:customMethod"},
+		{"/{parent=prefix/*}/children:customMethod", "/{parent=prefix/*}/children:customMethod"},
+	}
+	reg := descriptor.NewRegistry()
+	reg.SetUseJSONNamesForFields(false)
+	for _, data := range tests {
+		actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		if data.expected != actual {
+			t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
+		}
+	}
+	reg.SetUseJSONNamesForFields(true)
+	for _, data := range tests {
+		actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		if data.expected != actual {
+			t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
+		}
+	}
+}
+
+func BenchmarkTemplateToOpenAPIPath(b *testing.B) {
+	const input = "/{user.name=prefix1/*/prefix2/*}:customMethod"
+
+	b.Run("with JSON names", func(b *testing.B) {
+		reg := descriptor.NewRegistry()
+		reg.SetUseJSONNamesForFields(false)
+
+		for i := 0; i < b.N; i++ {
+			_ = templateToOpenAPIPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		}
+	})
+
+	b.Run("without JSON names", func(b *testing.B) {
+		reg := descriptor.NewRegistry()
+		reg.SetUseJSONNamesForFields(true)
+
+		for i := 0; i < b.N; i++ {
+			_ = templateToOpenAPIPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		}
+	})
+}
+
+func TestResolveFullyQualifiedNameToOpenAPIName(t *testing.T) {
+	var tests = []struct {
+		input                string
+		output               string
+		listOfFQMNs          []string
+		useFQNForOpenAPIName bool
+	}{
+		{
+			".a.b.C",
+			"C",
+			[]string{
+				".a.b.C",
+			},
+			false,
+		},
+		{
+			".a.b.C",
+			"abC",
+			[]string{
+				".a.C",
+				".a.b.C",
+			},
+			false,
+		},
+		{
+			".a.b.C",
+			"abC",
+			[]string{
+				".C",
+				".a.C",
+				".a.b.C",
+			},
+			false,
+		},
+		{
+			".a.b.C",
+			"a.b.C",
+			[]string{
+				".C",
+				".a.C",
+				".a.b.C",
+			},
+			true,
+		},
+	}
+
+	for _, data := range tests {
+		names := resolveFullyQualifiedNameToOpenAPINames(data.listOfFQMNs, data.useFQNForOpenAPIName)
+		output := names[data.input]
+		if output != data.output {
+			t.Errorf("Expected fullyQualifiedNameToOpenAPIName(%v) to be %s but got %s",
+				data.input, data.output, output)
+		}
+	}
+}
+
+func TestFQMNtoOpenAPIName(t *testing.T) {
+	var tests = []struct {
+		input    string
+		expected string
+	}{
+		{"/test", "/test"},
+		{"/{test}", "/{test}"},
+		{"/{test=prefix/*}", "/{test}"},
+		{"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"},
+		{"/{test1}/{test2}", "/{test1}/{test2}"},
+		{"/{test1}/{test2}/", "/{test1}/{test2}/"},
+	}
+	reg := descriptor.NewRegistry()
+	reg.SetUseJSONNamesForFields(false)
+	for _, data := range tests {
+		actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		if data.expected != actual {
+			t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
+		}
+	}
+	reg.SetUseJSONNamesForFields(true)
+	for _, data := range tests {
+		actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
+		if data.expected != actual {
+			t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
+		}
+	}
+}
+
+func TestSchemaOfField(t *testing.T) {
+	type test struct {
+		field          *descriptor.Field
+		refs           refMap
+		expected       openapiSchemaObject
+		openAPIOptions *openapiconfig.OpenAPIOptions
+	}
+
+	jsonSchema := &openapi_options.JSONSchema{
+		Title:       "field title",
+		Description: "field description",
+	}
+
+	var fieldOptions = new(descriptorpb.FieldOptions)
+	proto.SetExtension(fieldOptions, openapi_options.E_Openapiv2Field, jsonSchema)
+
+	var requiredField = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
+	var requiredFieldOptions = new(descriptorpb.FieldOptions)
+	proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, requiredField)
+
+	var outputOnlyField = []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY}
+	var outputOnlyOptions = new(descriptorpb.FieldOptions)
+	proto.SetExtension(outputOnlyOptions, annotations.E_FieldBehavior, outputOnlyField)
+
+	tests := []test{
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name: proto.String("primitive_field"),
+					Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "string",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:  proto.String("repeated_primitive_field"),
+					Type:  descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+					Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "array",
+					Items: &openapiItemsObject{
+						Type: "string",
+					},
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.FieldMask"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "string",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.Timestamp"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "string",
+					Format: "date-time",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.Duration"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "string",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.StringValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "string",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("repeated_wrapped_field"),
+					TypeName: proto.String(".google.protobuf.StringValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "array",
+					Items: &openapiItemsObject{
+						Type: "string",
+					},
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.BytesValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "string",
+					Format: "byte",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.Int32Value"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "integer",
+					Format: "int32",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.UInt32Value"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "integer",
+					Format: "int64",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.Int64Value"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "string",
+					Format: "int64",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.UInt64Value"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "string",
+					Format: "uint64",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.FloatValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "number",
+					Format: "float",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.DoubleValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "number",
+					Format: "double",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.BoolValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "boolean",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.Struct"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "object",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.Value"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "object",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.ListValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "array",
+					Items: (*openapiItemsObject)(&schemaCore{
+						Type: "object",
+					}),
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("wrapped_field"),
+					TypeName: proto.String(".google.protobuf.NullValue"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "string",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("message_field"),
+					TypeName: proto.String(".example.Message"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+				},
+			},
+			refs: refMap{".example.Message": struct{}{}},
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Ref: "#/definitions/exampleMessage",
+				},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("map_field"),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: proto.String(".example.Message.MapFieldEntry"),
+					Options:  fieldOptions,
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "object",
+				},
+				AdditionalProperties: &openapiSchemaObject{
+					schemaCore: schemaCore{Type: "string"},
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:    proto.String("array_field"),
+					Label:   descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+					Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+					Options: fieldOptions,
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:  "array",
+					Items: (*openapiItemsObject)(&schemaCore{Type: "string"}),
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:    proto.String("primitive_field"),
+					Label:   descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:    descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+					Options: fieldOptions,
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "integer",
+					Format: "int32",
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("message_field"),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: proto.String(".example.Empty"),
+					Options:  fieldOptions,
+				},
+			},
+			refs: refMap{".example.Empty": struct{}{}},
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Ref: "#/definitions/exampleEmpty",
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("map_field"), // should be called map_field_option but it's not valid map field name
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: proto.String(".example.Message.MapFieldEntry"),
+				},
+			},
+			openAPIOptions: &openapiconfig.OpenAPIOptions{
+				Field: []*openapiconfig.OpenAPIFieldOption{
+					{
+						Field:  "example.Message.map_field",
+						Option: jsonSchema,
+					},
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "object",
+				},
+				AdditionalProperties: &openapiSchemaObject{
+					schemaCore: schemaCore{Type: "string"},
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:  proto.String("array_field_option"),
+					Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
+					Type:  descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				},
+			},
+			openAPIOptions: &openapiconfig.OpenAPIOptions{
+				Field: []*openapiconfig.OpenAPIFieldOption{
+					{
+						Field:  "example.Message.array_field_option",
+						Option: jsonSchema,
+					},
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:  "array",
+					Items: (*openapiItemsObject)(&schemaCore{Type: "string"}),
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:  proto.String("primitive_field_option"),
+					Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:  descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+				},
+			},
+			openAPIOptions: &openapiconfig.OpenAPIOptions{
+				Field: []*openapiconfig.OpenAPIFieldOption{
+					{
+						Field:  "example.Message.primitive_field_option",
+						Option: jsonSchema,
+					},
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type:   "integer",
+					Format: "int32",
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("message_field_option"),
+					Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					TypeName: proto.String(".example.Empty"),
+				},
+			},
+			openAPIOptions: &openapiconfig.OpenAPIOptions{
+				Field: []*openapiconfig.OpenAPIFieldOption{
+					{
+						Field:  "example.Message.message_field_option",
+						Option: jsonSchema,
+					},
+				},
+			},
+			refs: refMap{".example.Empty": struct{}{}},
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Ref: "#/definitions/exampleEmpty",
+				},
+				Title:       "field title",
+				Description: "field description",
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:    proto.String("required_via_field_behavior_field"),
+					Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+					Options: requiredFieldOptions,
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "string",
+				},
+				Required: []string{"required_via_field_behavior_field"},
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:    proto.String("readonly_via_field_behavior_field"),
+					Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+					Options: outputOnlyOptions,
+				},
+			},
+			refs: make(refMap),
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Type: "string",
+				},
+				ReadOnly: true,
+			},
+		},
+		{
+			field: &descriptor.Field{
+				FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+					Name:     proto.String("message_field"),
+					TypeName: proto.String(".example.Message"),
+					Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+					Options:  requiredFieldOptions,
+				},
+			},
+			refs: refMap{".example.Message": struct{}{}},
+			expected: openapiSchemaObject{
+				schemaCore: schemaCore{
+					Ref: "#/definitions/exampleMessage",
+				},
+			},
+		},
+	}
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		req := &pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{
+				{
+					Name:    proto.String("third_party/google.proto"),
+					Package: proto.String("google.protobuf"),
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("third_party/google"),
+					},
+					MessageType: []*descriptorpb.DescriptorProto{
+						protodesc.ToDescriptorProto((&structpb.Struct{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&structpb.Value{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&structpb.ListValue{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&field_mask.FieldMask{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&timestamppb.Timestamp{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&durationpb.Duration{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.StringValue{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.BytesValue{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.Int32Value{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.UInt32Value{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.Int64Value{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.UInt64Value{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.FloatValue{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.DoubleValue{}).ProtoReflect().Descriptor()),
+						protodesc.ToDescriptorProto((&wrapperspb.BoolValue{}).ProtoReflect().Descriptor()),
+					},
+					EnumType: []*descriptorpb.EnumDescriptorProto{
+						protodesc.ToEnumDescriptorProto(structpb.NullValue(0).Descriptor()),
+					},
+				},
+				{
+					SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+					Name:           proto.String("example.proto"),
+					Package:        proto.String("example"),
+					Dependency:     []string{"third_party/google.proto"},
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
+					},
+					MessageType: []*descriptorpb.DescriptorProto{
+						{
+							Name: proto.String("Message"),
+							Field: []*descriptorpb.FieldDescriptorProto{
+								{
+									Name:   proto.String("value"),
+									Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+									Number: proto.Int32(1),
+								},
+								func() *descriptorpb.FieldDescriptorProto {
+									fd := test.field.FieldDescriptorProto
+									fd.Number = proto.Int32(2)
+									return fd
+								}(),
+							},
+							NestedType: []*descriptorpb.DescriptorProto{
+								{
+									Name:    proto.String("MapFieldEntry"),
+									Options: &descriptorpb.MessageOptions{MapEntry: proto.Bool(true)},
+									Field: []*descriptorpb.FieldDescriptorProto{
+										{
+											Name:   proto.String("key"),
+											Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+											Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+											Number: proto.Int32(1),
+										},
+										{
+											Name:   proto.String("value"),
+											Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+											Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+											Number: proto.Int32(2),
+										},
+									},
+								},
+							},
+						},
+						{
+							Name: proto.String("Empty"),
+						},
+					},
+					EnumType: []*descriptorpb.EnumDescriptorProto{
+						{
+							Name: proto.String("MessageType"),
+							Value: []*descriptorpb.EnumValueDescriptorProto{
+								{
+									Name:   proto.String("MESSAGE_TYPE_1"),
+									Number: proto.Int32(0),
+								},
+							},
+						},
+					},
+					Service: []*descriptorpb.ServiceDescriptorProto{},
+				},
+			},
+		}
+		err := reg.Load(req)
+		if err != nil {
+			t.Errorf("failed to reg.Load(req): %v", err)
+		}
+
+		// set field's parent message pointer to message so field can resolve its FQFN
+		test.field.Message = &descriptor.Message{
+			DescriptorProto: req.ProtoFile[1].MessageType[0],
+			File: &descriptor.File{
+				FileDescriptorProto: req.ProtoFile[1],
+			},
+		}
+
+		if test.openAPIOptions != nil {
+			if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil {
+				t.Fatalf("failed to register OpenAPI options: %s", err)
+			}
+		}
+
+		refs := make(refMap)
+		actual := schemaOfField(test.field, reg, refs)
+		expectedSchemaObject := test.expected
+		if e, a := expectedSchemaObject, actual; !reflect.DeepEqual(a, e) {
+			t.Errorf("Expected schemaOfField(%v) = \n%#+v, actual: \n%#+v", test.field, e, a)
+		}
+		if !reflect.DeepEqual(refs, test.refs) {
+			t.Errorf("Expected schemaOfField(%v) to add refs %v, not %v", test.field, test.refs, refs)
+		}
+	}
+}
+
+func TestRenderMessagesAsDefinition(t *testing.T) {
+	jsonSchema := &openapi_options.JSONSchema{
+		Title:       "field title",
+		Description: "field description",
+		Required:    []string{"aRequiredField"},
+	}
+
+	var requiredField = new(descriptorpb.FieldOptions)
+	proto.SetExtension(requiredField, openapi_options.E_Openapiv2Field, jsonSchema)
+
+	var fieldBehaviorRequired = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
+	var requiredFieldOptions = new(descriptorpb.FieldOptions)
+	proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, fieldBehaviorRequired)
+
+	var fieldBehaviorOutputOnlyField = []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY}
+	var fieldBehaviorOutputOnlyOptions = new(descriptorpb.FieldOptions)
+	proto.SetExtension(fieldBehaviorOutputOnlyOptions, annotations.E_FieldBehavior, fieldBehaviorOutputOnlyField)
+
+	tests := []struct {
+		descr          string
+		msgDescs       []*descriptorpb.DescriptorProto
+		schema         map[string]openapi_options.Schema // per-message schema to add
+		defs           openapiDefinitionsObject
+		openAPIOptions *openapiconfig.OpenAPIOptions
+		excludedFields []*descriptor.Field
+	}{
+		{
+			descr: "no OpenAPI options",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			schema: map[string]openapi_options.Schema{},
+			defs: map[string]openapiSchemaObject{
+				"Message": {schemaCore: schemaCore{Type: "object"}},
+			},
+		},
+		{
+			descr: "example option",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					Example: `{"foo":"bar"}`,
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {schemaCore: schemaCore{
+					Type:    "object",
+					Example: json.RawMessage(`{"foo":"bar"}`),
+				}},
+			},
+		},
+		{
+			descr: "example option with something non-json",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					Example: `XXXX anything goes XXXX`,
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {schemaCore: schemaCore{
+					Type:    "object",
+					Example: json.RawMessage(`XXXX anything goes XXXX`),
+				}},
+			},
+		},
+		{
+			descr: "external docs option",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					ExternalDocs: &openapi_options.ExternalDocumentation{
+						Description: "glorious docs",
+						Url:         "https://nada",
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					ExternalDocs: &openapiExternalDocumentationObject{
+						Description: "glorious docs",
+						URL:         "https://nada",
+					},
+				},
+			},
+		},
+		{
+			descr: "JSONSchema options",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					JsonSchema: &openapi_options.JSONSchema{
+						Title:            "title",
+						Description:      "desc",
+						MultipleOf:       100,
+						Maximum:          101,
+						ExclusiveMaximum: true,
+						Minimum:          1,
+						ExclusiveMinimum: true,
+						MaxLength:        10,
+						MinLength:        3,
+						Pattern:          "[a-z]+",
+						MaxItems:         20,
+						MinItems:         2,
+						UniqueItems:      true,
+						MaxProperties:    33,
+						MinProperties:    22,
+						Required:         []string{"req"},
+						ReadOnly:         true,
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:            "title",
+					Description:      "desc",
+					MultipleOf:       100,
+					Maximum:          101,
+					ExclusiveMaximum: true,
+					Minimum:          1,
+					ExclusiveMinimum: true,
+					MaxLength:        10,
+					MinLength:        3,
+					Pattern:          "[a-z]+",
+					MaxItems:         20,
+					MinItems:         2,
+					UniqueItems:      true,
+					MaxProperties:    33,
+					MinProperties:    22,
+					Required:         []string{"req"},
+					ReadOnly:         true,
+				},
+			},
+		},
+		{
+			descr: "JSONSchema options from registry",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			openAPIOptions: &openapiconfig.OpenAPIOptions{
+				Message: []*openapiconfig.OpenAPIMessageOption{
+					{
+						Message: "example.Message",
+						Option: &openapi_options.Schema{
+							JsonSchema: &openapi_options.JSONSchema{
+								Title:            "title",
+								Description:      "desc",
+								MultipleOf:       100,
+								Maximum:          101,
+								ExclusiveMaximum: true,
+								Minimum:          1,
+								ExclusiveMinimum: true,
+								MaxLength:        10,
+								MinLength:        3,
+								Pattern:          "[a-z]+",
+								MaxItems:         20,
+								MinItems:         2,
+								UniqueItems:      true,
+								MaxProperties:    33,
+								MinProperties:    22,
+								Required:         []string{"req"},
+								ReadOnly:         true,
+							},
+						},
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:            "title",
+					Description:      "desc",
+					MultipleOf:       100,
+					Maximum:          101,
+					ExclusiveMaximum: true,
+					Minimum:          1,
+					ExclusiveMinimum: true,
+					MaxLength:        10,
+					MinLength:        3,
+					Pattern:          "[a-z]+",
+					MaxItems:         20,
+					MinItems:         2,
+					UniqueItems:      true,
+					MaxProperties:    33,
+					MinProperties:    22,
+					Required:         []string{"req"},
+					ReadOnly:         true,
+				},
+			},
+		},
+		{
+			descr: "JSONSchema with required properties",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("Message"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:    proto.String("aRequiredField"),
+							Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:  proto.Int32(1),
+							Options: requiredField,
+						},
+					},
+				},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					JsonSchema: &openapi_options.JSONSchema{
+						Title:       "title",
+						Description: "desc",
+						Required:    []string{"req"},
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:       "title",
+					Description: "desc",
+					Required:    []string{"req", "aRequiredField"},
+					Properties: &openapiSchemaObjectProperties{
+						{
+							Key: "aRequiredField",
+							Value: openapiSchemaObject{
+								schemaCore: schemaCore{
+									Type: "string",
+								},
+								Description: "field description",
+								Title:       "field title",
+								Required:    []string{"aRequiredField"},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			descr: "JSONSchema with excluded fields",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("Message"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:    proto.String("aRequiredField"),
+							Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:  proto.Int32(1),
+							Options: requiredField,
+						},
+						{
+							Name:   proto.String("anExcludedField"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(2),
+						},
+					},
+				},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					JsonSchema: &openapi_options.JSONSchema{
+						Title:       "title",
+						Description: "desc",
+						Required:    []string{"req"},
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:       "title",
+					Description: "desc",
+					Required:    []string{"req", "aRequiredField"},
+					Properties: &openapiSchemaObjectProperties{
+						{
+							Key: "aRequiredField",
+							Value: openapiSchemaObject{
+								schemaCore: schemaCore{
+									Type: "string",
+								},
+								Description: "field description",
+								Title:       "field title",
+								Required:    []string{"aRequiredField"},
+							},
+						},
+					},
+				},
+			},
+			excludedFields: []*descriptor.Field{
+				{
+					FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
+						Name: strPtr("anExcludedField"),
+					},
+				},
+			},
+		},
+		{
+			descr: "JSONSchema with required properties via field_behavior",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("Message"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:    proto.String("aRequiredField"),
+							Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:  proto.Int32(1),
+							Options: requiredFieldOptions,
+						},
+						{
+							Name:    proto.String("aOutputOnlyField"),
+							Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:  proto.Int32(2),
+							Options: fieldBehaviorOutputOnlyOptions,
+						},
+					},
+				},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					JsonSchema: &openapi_options.JSONSchema{
+						Title:       "title",
+						Description: "desc",
+						Required:    []string{"req"},
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:       "title",
+					Description: "desc",
+					Required:    []string{"req", "aRequiredField"},
+					Properties: &openapiSchemaObjectProperties{
+						{
+							Key: "aRequiredField",
+							Value: openapiSchemaObject{
+								schemaCore: schemaCore{
+									Type: "string",
+								},
+								Required: []string{"aRequiredField"},
+							},
+						},
+						{
+							Key: "aOutputOnlyField",
+							Value: openapiSchemaObject{
+								schemaCore: schemaCore{
+									Type: "string",
+								},
+								ReadOnly: true,
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.descr, func(t *testing.T) {
+
+			msgs := []*descriptor.Message{}
+			for _, msgdesc := range test.msgDescs {
+				msgdesc.Options = &descriptorpb.MessageOptions{}
+				msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+			}
+
+			reg := descriptor.NewRegistry()
+			file := descriptor.File{
+				FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+					SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+					Name:           proto.String("example.proto"),
+					Package:        proto.String("example"),
+					Dependency:     []string{},
+					MessageType:    test.msgDescs,
+					EnumType:       []*descriptorpb.EnumDescriptorProto{},
+					Service:        []*descriptorpb.ServiceDescriptorProto{},
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
+					},
+				},
+				Messages: msgs,
+			}
+			err := reg.Load(&pluginpb.CodeGeneratorRequest{
+				ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+			})
+			if err != nil {
+				t.Fatalf("failed to load code generator request: %v", err)
+			}
+
+			msgMap := map[string]*descriptor.Message{}
+			for _, d := range test.msgDescs {
+				name := d.GetName()
+				msg, err := reg.LookupMsg("example", name)
+				if err != nil {
+					t.Fatalf("lookup message %v: %v", name, err)
+				}
+				msgMap[msg.FQMN()] = msg
+
+				if schema, ok := test.schema[name]; ok {
+					proto.SetExtension(d.Options, openapi_options.E_Openapiv2Schema, &schema)
+				}
+			}
+
+			if test.openAPIOptions != nil {
+				if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil {
+					t.Fatalf("failed to register OpenAPI options: %s", err)
+				}
+			}
+
+			refs := make(refMap)
+			actual := make(openapiDefinitionsObject)
+			renderMessagesAsDefinition(msgMap, actual, reg, refs, test.excludedFields)
+
+			if !reflect.DeepEqual(actual, test.defs) {
+				t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual)
+			}
+		})
+	}
+}
+
+func strPtr(s string) *string {
+	return &s
+}
+
+func TestUpdateOpenAPIDataFromComments(t *testing.T) {
+
+	tests := []struct {
+		descr                 string
+		openapiSwaggerObject  interface{}
+		comments              string
+		expectedError         error
+		expectedOpenAPIObject interface{}
+		useGoTemplate         bool
+	}{
+		{
+			descr:                 "empty comments",
+			openapiSwaggerObject:  nil,
+			expectedOpenAPIObject: nil,
+			comments:              "",
+			expectedError:         nil,
+		},
+		{
+			descr:                "set field to read only",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				ReadOnly:    true,
+				Description: "... Output only. ...",
+			},
+			comments:      "... Output only. ...",
+			expectedError: nil,
+		},
+		{
+			descr:                "set title",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				Title: "Comment with no trailing dot",
+			},
+			comments:      "Comment with no trailing dot",
+			expectedError: nil,
+		},
+		{
+			descr:                "set description",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				Description: "Comment with trailing dot.",
+			},
+			comments:      "Comment with trailing dot.",
+			expectedError: nil,
+		},
+		{
+			descr: "use info object",
+			openapiSwaggerObject: &openapiSwaggerObject{
+				Info: openapiInfoObject{},
+			},
+			expectedOpenAPIObject: &openapiSwaggerObject{
+				Info: openapiInfoObject{
+					Description: "Comment with trailing dot.",
+				},
+			},
+			comments:      "Comment with trailing dot.",
+			expectedError: nil,
+		},
+		{
+			descr:                "multi line comment with title",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				Title:       "First line",
+				Description: "Second line",
+			},
+			comments:      "First line\n\nSecond line",
+			expectedError: nil,
+		},
+		{
+			descr:                "multi line comment no title",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				Description: "First line.\n\nSecond line",
+			},
+			comments:      "First line.\n\nSecond line",
+			expectedError: nil,
+		},
+		{
+			descr:                "multi line comment with summary with dot",
+			openapiSwaggerObject: &openapiOperationObject{},
+			expectedOpenAPIObject: &openapiOperationObject{
+				Summary:     "First line.",
+				Description: "Second line",
+			},
+			comments:      "First line.\n\nSecond line",
+			expectedError: nil,
+		},
+		{
+			descr:                "multi line comment with summary no dot",
+			openapiSwaggerObject: &openapiOperationObject{},
+			expectedOpenAPIObject: &openapiOperationObject{
+				Summary:     "First line",
+				Description: "Second line",
+			},
+			comments:      "First line\n\nSecond line",
+			expectedError: nil,
+		},
+		{
+			descr:                 "multi line comment with summary no dot",
+			openapiSwaggerObject:  &schemaCore{},
+			expectedOpenAPIObject: &schemaCore{},
+			comments:              "Any comment",
+			expectedError:         errors.New("no description nor summary property"),
+		},
+		{
+			descr:                "without use_go_template",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				Title:       "First line",
+				Description: "{{import \"documentation.md\"}}",
+			},
+			comments:      "First line\n\n{{import \"documentation.md\"}}",
+			expectedError: nil,
+		},
+		{
+			descr:                "error with use_go_template",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				Title:       "First line",
+				Description: "open noneexistingfile.txt: no such file or directory",
+			},
+			comments:      "First line\n\n{{import \"noneexistingfile.txt\"}}",
+			expectedError: nil,
+			useGoTemplate: true,
+		},
+		{
+			descr:                "template with use_go_template",
+			openapiSwaggerObject: &openapiSchemaObject{},
+			expectedOpenAPIObject: &openapiSchemaObject{
+				Title:       "Template",
+				Description: `Description "which means nothing"`,
+			},
+			comments:      "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+			expectedError: nil,
+			useGoTemplate: true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.descr, func(t *testing.T) {
+			reg := descriptor.NewRegistry()
+			if test.useGoTemplate {
+				reg.SetUseGoTemplate(true)
+			}
+			err := updateOpenAPIDataFromComments(reg, test.openapiSwaggerObject, nil, test.comments, false)
+			if test.expectedError == nil {
+				if err != nil {
+					t.Errorf("unexpected error '%v'", err)
+				}
+				if !reflect.DeepEqual(test.openapiSwaggerObject, test.expectedOpenAPIObject) {
+					t.Errorf("openapiSwaggerObject was not updated correctly, expected '%+v', got '%+v'", test.expectedOpenAPIObject, test.openapiSwaggerObject)
+				}
+			} else {
+				if err == nil {
+					t.Error("expected update error not returned")
+				}
+				if !reflect.DeepEqual(test.openapiSwaggerObject, test.expectedOpenAPIObject) {
+					t.Errorf("openapiSwaggerObject was not updated correctly, expected '%+v', got '%+v'", test.expectedOpenAPIObject, test.openapiSwaggerObject)
+				}
+				if err.Error() != test.expectedError.Error() {
+					t.Errorf("expected error malformed, expected %q, got %q", test.expectedError.Error(), err.Error())
+				}
+			}
+		})
+	}
+}
+
+func TestMessageOptionsWithGoTemplate(t *testing.T) {
+	tests := []struct {
+		descr          string
+		msgDescs       []*descriptorpb.DescriptorProto
+		schema         map[string]openapi_options.Schema // per-message schema to add
+		defs           openapiDefinitionsObject
+		openAPIOptions *openapiconfig.OpenAPIOptions
+		useGoTemplate  bool
+	}{
+		{
+			descr: "external docs option",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					JsonSchema: &openapi_options.JSONSchema{
+						Title:       "{{.Name}}",
+						Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+					},
+					ExternalDocs: &openapi_options.ExternalDocumentation{
+						Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:       "Message",
+					Description: `Description "which means nothing"`,
+					ExternalDocs: &openapiExternalDocumentationObject{
+						Description: `Description "which means nothing"`,
+					},
+				},
+			},
+			useGoTemplate: true,
+		},
+		{
+			descr: "external docs option",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			schema: map[string]openapi_options.Schema{
+				"Message": {
+					JsonSchema: &openapi_options.JSONSchema{
+						Title:       "{{.Name}}",
+						Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+					},
+					ExternalDocs: &openapi_options.ExternalDocumentation{
+						Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:       "{{.Name}}",
+					Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+					ExternalDocs: &openapiExternalDocumentationObject{
+						Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+					},
+				},
+			},
+			useGoTemplate: false,
+		},
+		{
+			descr: "registered OpenAPIOption",
+			msgDescs: []*descriptorpb.DescriptorProto{
+				{Name: proto.String("Message")},
+			},
+			openAPIOptions: &openapiconfig.OpenAPIOptions{
+				Message: []*openapiconfig.OpenAPIMessageOption{
+					{
+						Message: "example.Message",
+						Option: &openapi_options.Schema{
+							JsonSchema: &openapi_options.JSONSchema{
+								Title:       "{{.Name}}",
+								Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+							},
+							ExternalDocs: &openapi_options.ExternalDocumentation{
+								Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
+							},
+						},
+					},
+				},
+			},
+			defs: map[string]openapiSchemaObject{
+				"Message": {
+					schemaCore: schemaCore{
+						Type: "object",
+					},
+					Title:       "Message",
+					Description: `Description "which means nothing"`,
+					ExternalDocs: &openapiExternalDocumentationObject{
+						Description: `Description "which means nothing"`,
+					},
+				},
+			},
+			useGoTemplate: true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.descr, func(t *testing.T) {
+
+			msgs := []*descriptor.Message{}
+			for _, msgdesc := range test.msgDescs {
+				msgdesc.Options = &descriptorpb.MessageOptions{}
+				msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+			}
+
+			reg := descriptor.NewRegistry()
+			reg.SetUseGoTemplate(test.useGoTemplate)
+			file := descriptor.File{
+				FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+					SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+					Name:           proto.String("example.proto"),
+					Package:        proto.String("example"),
+					Dependency:     []string{},
+					MessageType:    test.msgDescs,
+					EnumType:       []*descriptorpb.EnumDescriptorProto{},
+					Service:        []*descriptorpb.ServiceDescriptorProto{},
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
+					},
+				},
+				Messages: msgs,
+			}
+			err := reg.Load(&pluginpb.CodeGeneratorRequest{
+				ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+			})
+			if err != nil {
+				t.Fatalf("failed to load code generator request: %v", err)
+			}
+
+			msgMap := map[string]*descriptor.Message{}
+			for _, d := range test.msgDescs {
+				name := d.GetName()
+				msg, err := reg.LookupMsg("example", name)
+				if err != nil {
+					t.Fatalf("lookup message %v: %v", name, err)
+				}
+				msgMap[msg.FQMN()] = msg
+
+				if schema, ok := test.schema[name]; ok {
+					proto.SetExtension(d.Options, openapi_options.E_Openapiv2Schema, &schema)
+				}
+			}
+
+			if test.openAPIOptions != nil {
+				if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil {
+					t.Fatalf("failed to register OpenAPI options: %s", err)
+				}
+			}
+
+			refs := make(refMap)
+			actual := make(openapiDefinitionsObject)
+			renderMessagesAsDefinition(msgMap, actual, reg, refs, nil)
+
+			if !reflect.DeepEqual(actual, test.defs) {
+				t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual)
+			}
+		})
+	}
+}
+
+func TestTemplateWithoutErrorDefinition(t *testing.T) {
+	msgdesc := &descriptorpb.DescriptorProto{
+		Name:  proto.String("ExampleMessage"),
+		Field: []*descriptorpb.FieldDescriptorProto{},
+	}
+	meth := &descriptorpb.MethodDescriptorProto{
+		Name:       proto.String("Echo"),
+		InputType:  proto.String("ExampleMessage"),
+		OutputType: proto.String("ExampleMessage"),
+	}
+	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: "POST",
+								PathTmpl: httprule.Template{
+									Version:  1,
+									OpCodes:  []int{0, 0},
+									Template: "/v1/echo",
+								},
+								Body: &descriptor.Body{
+									FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	reg := descriptor.NewRegistry()
+	err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
+	if err != nil {
+		t.Errorf("failed to reg.Load(): %v", err)
+		return
+	}
+	result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
+	if err != nil {
+		t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
+		return
+	}
+
+	defRsp, ok := result.Paths["/v1/echo"].Post.Responses["default"]
+	if !ok {
+		return
+	}
+
+	ref := defRsp.Schema.schemaCore.Ref
+	refName := strings.TrimPrefix(ref, "#/definitions/")
+	if refName == "" {
+		t.Fatal("created default Error response with empty reflink")
+	}
+
+	if _, ok := result.Definitions[refName]; !ok {
+		t.Errorf("default Error response with reflink '%v', but its definition was not found", refName)
+	}
+}
+
+func Test_getReservedJsonName(t *testing.T) {
+	type args struct {
+		fieldName                     string
+		messageNameToFieldsToJSONName map[string]map[string]string
+		fieldNameToType               map[string]string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			"test case 1: single dot use case",
+			args{
+				fieldName: "abc.a_1",
+				messageNameToFieldsToJSONName: map[string]map[string]string{
+					"Msg": {
+						"a_1": "a1JSONNAME",
+						"b_1": "b1JSONNAME",
+					},
+				},
+				fieldNameToType: map[string]string{
+					"abc": "pkg1.test.Msg",
+					"bcd": "pkg1.test.Msg",
+				},
+			},
+			"a1JSONNAME",
+		},
+		{
+			"test case 2: single dot use case with no existing field",
+			args{
+				fieldName: "abc.d_1",
+				messageNameToFieldsToJSONName: map[string]map[string]string{
+					"Msg": {
+						"a_1": "a1JSONNAME",
+						"b_1": "b1JSONNAME",
+					},
+				},
+				fieldNameToType: map[string]string{
+					"abc": "pkg1.test.Msg",
+					"bcd": "pkg1.test.Msg",
+				},
+			},
+			"",
+		},
+		{
+			"test case 3: double dot use case",
+			args{
+				fieldName: "pkg.abc.a_1",
+				messageNameToFieldsToJSONName: map[string]map[string]string{
+					"Msg": {
+						"a_1": "a1JSONNAME",
+						"b_1": "b1JSONNAME",
+					},
+				},
+				fieldNameToType: map[string]string{
+					"abc": "pkg1.test.Msg",
+					"bcd": "pkg1.test.Msg",
+				},
+			},
+			"a1JSONNAME",
+		},
+		{
+			"test case 4: double dot use case with a not existed field",
+			args{
+				fieldName: "pkg.abc.c_1",
+				messageNameToFieldsToJSONName: map[string]map[string]string{
+					"Msg": {
+						"a_1": "a1JSONNAME",
+						"b_1": "b1JSONNAME",
+					},
+				},
+				fieldNameToType: map[string]string{
+					"abc": "pkg1.test.Msg",
+					"bcd": "pkg1.test.Msg",
+				},
+			},
+			"",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := getReservedJSONName(tt.args.fieldName, tt.args.messageNameToFieldsToJSONName, tt.args.fieldNameToType); got != tt.want {
+				t.Errorf("getReservedJSONName() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestParseIncompleteSecurityRequirement(t *testing.T) {
+	swagger := openapi_options.Swagger{
+		Security: []*openapi_options.SecurityRequirement{
+			{
+				SecurityRequirement: map[string]*openapi_options.SecurityRequirement_SecurityRequirementValue{
+					"key": nil,
+				},
+			},
+		},
+	}
+	file := descriptor.File{
+		FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+			SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+			Name:           proto.String("example.proto"),
+			Package:        proto.String("example"),
+			Options: &descriptorpb.FileOptions{
+				GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
+			},
+		},
+	}
+	proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger)
+	reg := descriptor.NewRegistry()
+	err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
+	if err != nil {
+		t.Errorf("failed to reg.Load(): %v", err)
+		return
+	}
+	_, err = applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
+	if err == nil {
+		t.Errorf("applyTemplate(%#v) did not error as expected", file)
+		return
+	}
+}

+ 282 - 0
protoc-gen-openapiv2/internal/genopenapi/types.go

@@ -0,0 +1,282 @@
+package genopenapi
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor"
+)
+
+type param struct {
+	*descriptor.File
+	reg *descriptor.Registry
+}
+
+// http://swagger.io/specification/#infoObject
+type openapiInfoObject struct {
+	Title          string `json:"title"`
+	Description    string `json:"description,omitempty"`
+	TermsOfService string `json:"termsOfService,omitempty"`
+	Version        string `json:"version"`
+
+	Contact *openapiContactObject `json:"contact,omitempty"`
+	License *openapiLicenseObject `json:"license,omitempty"`
+
+	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"`
+	URL   string `json:"url,omitempty"`
+	Email string `json:"email,omitempty"`
+}
+
+// http://swagger.io/specification/#licenseObject
+type openapiLicenseObject struct {
+	Name string `json:"name,omitempty"`
+	URL  string `json:"url,omitempty"`
+}
+
+// http://swagger.io/specification/#externalDocumentationObject
+type openapiExternalDocumentationObject struct {
+	Description string `json:"description,omitempty"`
+	URL         string `json:"url,omitempty"`
+}
+
+type extension struct {
+	key   string
+	value json.RawMessage
+}
+
+// http://swagger.io/specification/#swaggerObject
+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"`
+	Consumes            []string                            `json:"consumes"`
+	Produces            []string                            `json:"produces"`
+	Paths               openapiPathsObject                  `json:"paths"`
+	Definitions         openapiDefinitionsObject            `json:"definitions"`
+	SecurityDefinitions openapiSecurityDefinitionsObject    `json:"securityDefinitions,omitempty"`
+	Security            []openapiSecurityRequirementObject  `json:"security,omitempty"`
+	ExternalDocs        *openapiExternalDocumentationObject `json:"externalDocs,omitempty"`
+
+	extensions []extension
+}
+
+// http://swagger.io/specification/#securityDefinitionsObject
+type openapiSecurityDefinitionsObject map[string]openapiSecuritySchemeObject
+
+// http://swagger.io/specification/#securitySchemeObject
+type openapiSecuritySchemeObject struct {
+	Type             string              `json:"type"`
+	Description      string              `json:"description,omitempty"`
+	Name             string              `json:"name,omitempty"`
+	In               string              `json:"in,omitempty"`
+	Flow             string              `json:"flow,omitempty"`
+	AuthorizationURL string              `json:"authorizationUrl,omitempty"`
+	TokenURL         string              `json:"tokenUrl,omitempty"`
+	Scopes           openapiScopesObject `json:"scopes,omitempty"`
+
+	extensions []extension
+}
+
+// http://swagger.io/specification/#scopesObject
+type openapiScopesObject map[string]string
+
+// http://swagger.io/specification/#securityRequirementObject
+type openapiSecurityRequirementObject map[string][]string
+
+// http://swagger.io/specification/#pathsObject
+type openapiPathsObject map[string]openapiPathItemObject
+
+// http://swagger.io/specification/#pathItemObject
+type openapiPathItemObject struct {
+	Get    *openapiOperationObject `json:"get,omitempty"`
+	Delete *openapiOperationObject `json:"delete,omitempty"`
+	Post   *openapiOperationObject `json:"post,omitempty"`
+	Put    *openapiOperationObject `json:"put,omitempty"`
+	Patch  *openapiOperationObject `json:"patch,omitempty"`
+}
+
+// http://swagger.io/specification/#operationObject
+type openapiOperationObject struct {
+	Summary     string                  `json:"summary,omitempty"`
+	Description string                  `json:"description,omitempty"`
+	OperationID string                  `json:"operationId"`
+	Responses   openapiResponsesObject  `json:"responses"`
+	Parameters  openapiParametersObject `json:"parameters,omitempty"`
+	Tags        []string                `json:"tags,omitempty"`
+	Deprecated  bool                    `json:"deprecated,omitempty"`
+	Produces    []string                `json:"produces,omitempty"`
+
+	Security     *[]openapiSecurityRequirementObject `json:"security,omitempty"`
+	ExternalDocs *openapiExternalDocumentationObject `json:"externalDocs,omitempty"`
+
+	extensions []extension
+}
+
+type openapiParametersObject []openapiParameterObject
+
+// http://swagger.io/specification/#parameterObject
+type openapiParameterObject struct {
+	Name             string              `json:"name"`
+	Description      string              `json:"description,omitempty"`
+	In               string              `json:"in,omitempty"`
+	Required         bool                `json:"required"`
+	Type             string              `json:"type,omitempty"`
+	Format           string              `json:"format,omitempty"`
+	Items            *openapiItemsObject `json:"items,omitempty"`
+	Enum             []string            `json:"enum,omitempty"`
+	CollectionFormat string              `json:"collectionFormat,omitempty"`
+	Default          string              `json:"default,omitempty"`
+	MinItems         *int                `json:"minItems,omitempty"`
+
+	// Or you can explicitly refer to another type. If this is defined all
+	// other fields should be empty
+	Schema *openapiSchemaObject `json:"schema,omitempty"`
+}
+
+// core part of schema, which is common to itemsObject and schemaObject.
+// http://swagger.io/specification/v2/#itemsObject
+// The OAS3 spec (https://swagger.io/specification/#schemaObject) defines the
+// `nullable` field as part of a Schema Object. This behavior has been
+// "back-ported" to OAS2 as the Specification Extension `x-nullable`, and is
+// supported by generation tools such as swagger-codegen and go-swagger.
+// For protoc-gen-openapiv3, we'd want to add `nullable` instead.
+type schemaCore struct {
+	Type      string          `json:"type,omitempty"`
+	Format    string          `json:"format,omitempty"`
+	Ref       string          `json:"$ref,omitempty"`
+	XNullable bool            `json:"x-nullable,omitempty"`
+	Example   json.RawMessage `json:"example,omitempty"`
+
+	Items *openapiItemsObject `json:"items,omitempty"`
+
+	// If the item is an enumeration include a list of all the *NAMES* of the
+	// enum values.  I'm not sure how well this will work but assuming all enums
+	// start from 0 index it will be great. I don't think that is a good assumption.
+	Enum    []string `json:"enum,omitempty"`
+	Default string   `json:"default,omitempty"`
+}
+
+func (s *schemaCore) setRefFromFQN(ref string, reg *descriptor.Registry) error {
+	name, ok := fullyQualifiedNameToOpenAPIName(ref, reg)
+	if !ok {
+		return fmt.Errorf("setRefFromFQN: can't resolve OpenAPI name from '%v'", ref)
+	}
+	s.Ref = fmt.Sprintf("#/definitions/%s", name)
+	return nil
+}
+
+type openapiItemsObject schemaCore
+
+// http://swagger.io/specification/#responsesObject
+type openapiResponsesObject map[string]openapiResponseObject
+
+// http://swagger.io/specification/#responseObject
+type openapiResponseObject struct {
+	Description string                 `json:"description"`
+	Schema      openapiSchemaObject    `json:"schema"`
+	Examples    map[string]interface{} `json:"examples,omitempty"`
+	Headers     openapiHeadersObject   `json:"headers,omitempty"`
+
+	extensions []extension
+}
+
+type openapiHeadersObject map[string]openapiHeaderObject
+
+// http://swagger.io/specification/#headerObject
+type openapiHeaderObject struct {
+	Description string          `json:"description,omitempty"`
+	Type        string          `json:"type,omitempty"`
+	Format      string          `json:"format,omitempty"`
+	Default     json.RawMessage `json:"default,omitempty"`
+	Pattern     string          `json:"pattern,omitempty"`
+}
+
+type keyVal struct {
+	Key   string
+	Value interface{}
+}
+
+type openapiSchemaObjectProperties []keyVal
+
+func (op openapiSchemaObjectProperties) MarshalJSON() ([]byte, error) {
+	var buf bytes.Buffer
+	buf.WriteString("{")
+	for i, kv := range op {
+		if i != 0 {
+			buf.WriteString(",")
+		}
+		key, err := json.Marshal(kv.Key)
+		if err != nil {
+			return nil, err
+		}
+		buf.Write(key)
+		buf.WriteString(":")
+		val, err := json.Marshal(kv.Value)
+		if err != nil {
+			return nil, err
+		}
+		buf.Write(val)
+	}
+
+	buf.WriteString("}")
+	return buf.Bytes(), nil
+}
+
+// http://swagger.io/specification/#schemaObject
+type openapiSchemaObject struct {
+	schemaCore
+	// Properties can be recursively defined
+	Properties           *openapiSchemaObjectProperties `json:"properties,omitempty"`
+	AdditionalProperties *openapiSchemaObject           `json:"additionalProperties,omitempty"`
+
+	Description string `json:"description,omitempty"`
+	Title       string `json:"title,omitempty"`
+
+	ExternalDocs *openapiExternalDocumentationObject `json:"externalDocs,omitempty"`
+
+	ReadOnly         bool     `json:"readOnly,omitempty"`
+	MultipleOf       float64  `json:"multipleOf,omitempty"`
+	Maximum          float64  `json:"maximum,omitempty"`
+	ExclusiveMaximum bool     `json:"exclusiveMaximum,omitempty"`
+	Minimum          float64  `json:"minimum,omitempty"`
+	ExclusiveMinimum bool     `json:"exclusiveMinimum,omitempty"`
+	MaxLength        uint64   `json:"maxLength,omitempty"`
+	MinLength        uint64   `json:"minLength,omitempty"`
+	Pattern          string   `json:"pattern,omitempty"`
+	MaxItems         uint64   `json:"maxItems,omitempty"`
+	MinItems         uint64   `json:"minItems,omitempty"`
+	UniqueItems      bool     `json:"uniqueItems,omitempty"`
+	MaxProperties    uint64   `json:"maxProperties,omitempty"`
+	MinProperties    uint64   `json:"minProperties,omitempty"`
+	Required         []string `json:"required,omitempty"`
+}
+
+// http://swagger.io/specification/#definitionsObject
+type openapiDefinitionsObject map[string]openapiSchemaObject
+
+// Internal type mapping from FQMN to descriptor.Message. Used as a set by the
+// findServiceMessages function.
+type messageMap map[string]*descriptor.Message
+
+// Internal type mapping from FQEN to descriptor.Enum. Used as a set by the
+// findServiceMessages function.
+type enumMap map[string]*descriptor.Enum
+
+// Internal type to store used references.
+type refMap map[string]struct{}

+ 121 - 0
protoc-gen-openapiv2/internal/httprule/compile.go

@@ -0,0 +1,121 @@
+package httprule
+
+import (
+	"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
+)
+
+const (
+	opcodeVersion = 1
+)
+
+// Template is a compiled representation of path templates.
+type Template struct {
+	// Version is the version number of the format.
+	Version int
+	// OpCodes is a sequence of operations.
+	OpCodes []int
+	// Pool is a constant pool
+	Pool []string
+	// Verb is a VERB part in the template.
+	Verb string
+	// Fields is a list of field paths bound in this template.
+	Fields []string
+	// Original template (example: /v1/a_bit_of_everything)
+	Template string
+}
+
+// Compiler compiles utilities representation of path templates into marshallable operations.
+// They can be unmarshalled by runtime.NewPattern.
+type Compiler interface {
+	Compile() Template
+}
+
+type op struct {
+	// code is the opcode of the operation
+	code utilities.OpCode
+
+	// str is a string operand of the code.
+	// num is ignored if str is not empty.
+	str string
+
+	// num is a numeric operand of the code.
+	num int
+}
+
+func (w wildcard) compile() []op {
+	return []op{
+		{code: utilities.OpPush},
+	}
+}
+
+func (w deepWildcard) compile() []op {
+	return []op{
+		{code: utilities.OpPushM},
+	}
+}
+
+func (l literal) compile() []op {
+	return []op{
+		{
+			code: utilities.OpLitPush,
+			str:  string(l),
+		},
+	}
+}
+
+func (v variable) compile() []op {
+	var ops []op
+	for _, s := range v.segments {
+		ops = append(ops, s.compile()...)
+	}
+	ops = append(ops, op{
+		code: utilities.OpConcatN,
+		num:  len(v.segments),
+	}, op{
+		code: utilities.OpCapture,
+		str:  v.path,
+	})
+
+	return ops
+}
+
+func (t template) Compile() Template {
+	var rawOps []op
+	for _, s := range t.segments {
+		rawOps = append(rawOps, s.compile()...)
+	}
+
+	var (
+		ops    []int
+		pool   []string
+		fields []string
+	)
+	consts := make(map[string]int)
+	for _, op := range rawOps {
+		ops = append(ops, int(op.code))
+		if op.str == "" {
+			ops = append(ops, op.num)
+		} else {
+			// eof segment literal represents the "/" path pattern
+			if op.str == eof {
+				op.str = ""
+			}
+			if _, ok := consts[op.str]; !ok {
+				consts[op.str] = len(pool)
+				pool = append(pool, op.str)
+			}
+			ops = append(ops, consts[op.str])
+		}
+		if op.code == utilities.OpCapture {
+			fields = append(fields, op.str)
+		}
+	}
+	return Template{
+		Version:  opcodeVersion,
+		OpCodes:  ops,
+		Pool:     pool,
+		Verb:     t.verb,
+		Fields:   fields,
+		Template: t.template,
+	}
+}

+ 129 - 0
protoc-gen-openapiv2/internal/httprule/compile_test.go

@@ -0,0 +1,129 @@
+package httprule
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
+)
+
+const (
+	operandFiller = 0
+)
+
+func TestCompile(t *testing.T) {
+	for _, spec := range []struct {
+		segs []segment
+		verb string
+
+		ops    []int
+		pool   []string
+		fields []string
+	}{
+		{},
+		{
+			segs: []segment{
+				literal(eof),
+			},
+			ops:  []int{int(utilities.OpLitPush), 0},
+			pool: []string{""},
+		},
+		{
+			segs: []segment{
+				wildcard{},
+			},
+			ops: []int{int(utilities.OpPush), operandFiller},
+		},
+		{
+			segs: []segment{
+				deepWildcard{},
+			},
+			ops: []int{int(utilities.OpPushM), operandFiller},
+		},
+		{
+			segs: []segment{
+				literal("v1"),
+			},
+			ops:  []int{int(utilities.OpLitPush), 0},
+			pool: []string{"v1"},
+		},
+		{
+			segs: []segment{
+				literal("v1"),
+			},
+			verb: "LOCK",
+			ops:  []int{int(utilities.OpLitPush), 0},
+			pool: []string{"v1"},
+		},
+		{
+			segs: []segment{
+				variable{
+					path: "name.nested",
+					segments: []segment{
+						wildcard{},
+					},
+				},
+			},
+			ops: []int{
+				int(utilities.OpPush), operandFiller,
+				int(utilities.OpConcatN), 1,
+				int(utilities.OpCapture), 0,
+			},
+			pool:   []string{"name.nested"},
+			fields: []string{"name.nested"},
+		},
+		{
+			segs: []segment{
+				literal("obj"),
+				variable{
+					path: "name.nested",
+					segments: []segment{
+						literal("a"),
+						wildcard{},
+						literal("b"),
+					},
+				},
+				variable{
+					path: "obj",
+					segments: []segment{
+						deepWildcard{},
+					},
+				},
+			},
+			ops: []int{
+				int(utilities.OpLitPush), 0,
+				int(utilities.OpLitPush), 1,
+				int(utilities.OpPush), operandFiller,
+				int(utilities.OpLitPush), 2,
+				int(utilities.OpConcatN), 3,
+				int(utilities.OpCapture), 3,
+				int(utilities.OpPushM), operandFiller,
+				int(utilities.OpConcatN), 1,
+				int(utilities.OpCapture), 0,
+			},
+			pool:   []string{"obj", "a", "b", "name.nested"},
+			fields: []string{"name.nested", "obj"},
+		},
+	} {
+		tmpl := template{
+			segments: spec.segs,
+			verb:     spec.verb,
+		}
+		compiled := tmpl.Compile()
+		if got, want := compiled.Version, opcodeVersion; got != want {
+			t.Errorf("tmpl.Compile().Version = %d; want %d; segs=%#v, verb=%q", got, want, spec.segs, spec.verb)
+		}
+		if got, want := compiled.OpCodes, spec.ops; !reflect.DeepEqual(got, want) {
+			t.Errorf("tmpl.Compile().OpCodes = %v; want %v; segs=%#v, verb=%q", got, want, spec.segs, spec.verb)
+		}
+		if got, want := compiled.Pool, spec.pool; !reflect.DeepEqual(got, want) {
+			t.Errorf("tmpl.Compile().Pool = %q; want %q; segs=%#v, verb=%q", got, want, spec.segs, spec.verb)
+		}
+		if got, want := compiled.Verb, spec.verb; got != want {
+			t.Errorf("tmpl.Compile().Verb = %q; want %q; segs=%#v, verb=%q", got, want, spec.segs, spec.verb)
+		}
+		if got, want := compiled.Fields, spec.fields; !reflect.DeepEqual(got, want) {
+			t.Errorf("tmpl.Compile().Fields = %q; want %q; segs=%#v, verb=%q", got, want, spec.segs, spec.verb)
+		}
+	}
+}

+ 12 - 0
protoc-gen-openapiv2/internal/httprule/fuzz.go

@@ -0,0 +1,12 @@
+//go:build gofuzz
+// +build gofuzz
+
+package httprule
+
+func Fuzz(data []byte) int {
+	_, err := Parse(string(data))
+	if err != nil {
+		return 0
+	}
+	return 0
+}

+ 369 - 0
protoc-gen-openapiv2/internal/httprule/parse.go

@@ -0,0 +1,369 @@
+package httprule
+
+import (
+	"fmt"
+	"strings"
+)
+
+// InvalidTemplateError indicates that the path template is not valid.
+type InvalidTemplateError struct {
+	tmpl string
+	msg  string
+}
+
+func (e InvalidTemplateError) Error() string {
+	return fmt.Sprintf("%s: %s", e.msg, e.tmpl)
+}
+
+// Parse parses the string representation of path template
+func Parse(tmpl string) (Compiler, error) {
+	if !strings.HasPrefix(tmpl, "/") {
+		return template{}, InvalidTemplateError{tmpl: tmpl, msg: "no leading /"}
+	}
+	tokens, verb := tokenize(tmpl[1:])
+
+	p := parser{tokens: tokens}
+	segs, err := p.topLevelSegments()
+	if err != nil {
+		return template{}, InvalidTemplateError{tmpl: tmpl, msg: err.Error()}
+	}
+
+	return template{
+		segments: segs,
+		verb:     verb,
+		template: tmpl,
+	}, nil
+}
+
+func tokenize(path string) (tokens []string, verb string) {
+	if path == "" {
+		return []string{eof}, ""
+	}
+
+	const (
+		init = iota
+		field
+		nested
+	)
+	st := init
+	for path != "" {
+		var idx int
+		switch st {
+		case init:
+			idx = strings.IndexAny(path, "/{")
+		case field:
+			idx = strings.IndexAny(path, ".=}")
+		case nested:
+			idx = strings.IndexAny(path, "/}")
+		}
+		if idx < 0 {
+			tokens = append(tokens, path)
+			break
+		}
+		switch r := path[idx]; r {
+		case '/', '.':
+		case '{':
+			st = field
+		case '=':
+			st = nested
+		case '}':
+			st = init
+		}
+		if idx == 0 {
+			tokens = append(tokens, path[idx:idx+1])
+		} else {
+			tokens = append(tokens, path[:idx], path[idx:idx+1])
+		}
+		path = path[idx+1:]
+	}
+
+	l := len(tokens)
+	// See
+	// https://github.com/grpc-ecosystem/grpc-gateway/pull/1947#issuecomment-774523693 ;
+	// although normal and backwards-compat logic here is to use the last index
+	// of a colon, if the final segment is a variable followed by a colon, the
+	// part following the colon must be a verb. Hence if the previous token is
+	// an end var marker, we switch the index we're looking for to Index instead
+	// of LastIndex, so that we correctly grab the remaining part of the path as
+	// the verb.
+	var penultimateTokenIsEndVar bool
+	switch l {
+	case 0, 1:
+		// Not enough to be variable so skip this logic and don't result in an
+		// invalid index
+	default:
+		penultimateTokenIsEndVar = tokens[l-2] == "}"
+	}
+	t := tokens[l-1]
+	var idx int
+	if penultimateTokenIsEndVar {
+		idx = strings.Index(t, ":")
+	} else {
+		idx = strings.LastIndex(t, ":")
+	}
+	if idx == 0 {
+		tokens, verb = tokens[:l-1], t[1:]
+	} else if idx > 0 {
+		tokens[l-1], verb = t[:idx], t[idx+1:]
+	}
+	tokens = append(tokens, eof)
+	return tokens, verb
+}
+
+// parser is a parser of the template syntax defined in github.com/googleapis/googleapis/google/api/http.proto.
+type parser struct {
+	tokens   []string
+	accepted []string
+}
+
+// topLevelSegments is the target of this parser.
+func (p *parser) topLevelSegments() ([]segment, error) {
+	if _, err := p.accept(typeEOF); err == nil {
+		p.tokens = p.tokens[:0]
+		return []segment{literal(eof)}, nil
+	}
+	segs, err := p.segments()
+	if err != nil {
+		return nil, err
+	}
+	if _, err := p.accept(typeEOF); err != nil {
+		return nil, fmt.Errorf("unexpected token %q after segments %q", p.tokens[0], strings.Join(p.accepted, ""))
+	}
+	return segs, nil
+}
+
+func (p *parser) segments() ([]segment, error) {
+	s, err := p.segment()
+	if err != nil {
+		return nil, err
+	}
+
+	segs := []segment{s}
+	for {
+		if _, err := p.accept("/"); err != nil {
+			return segs, nil
+		}
+		s, err := p.segment()
+		if err != nil {
+			return segs, err
+		}
+		segs = append(segs, s)
+	}
+}
+
+func (p *parser) segment() (segment, error) {
+	if _, err := p.accept("*"); err == nil {
+		return wildcard{}, nil
+	}
+	if _, err := p.accept("**"); err == nil {
+		return deepWildcard{}, nil
+	}
+	if l, err := p.literal(); err == nil {
+		return l, nil
+	}
+
+	v, err := p.variable()
+	if err != nil {
+		return nil, fmt.Errorf("segment neither wildcards, literal or variable: %v", err)
+	}
+	return v, err
+}
+
+func (p *parser) literal() (segment, error) {
+	lit, err := p.accept(typeLiteral)
+	if err != nil {
+		return nil, err
+	}
+	return literal(lit), nil
+}
+
+func (p *parser) variable() (segment, error) {
+	if _, err := p.accept("{"); err != nil {
+		return nil, err
+	}
+
+	path, err := p.fieldPath()
+	if err != nil {
+		return nil, err
+	}
+
+	var segs []segment
+	if _, err := p.accept("="); err == nil {
+		segs, err = p.segments()
+		if err != nil {
+			return nil, fmt.Errorf("invalid segment in variable %q: %v", path, err)
+		}
+	} else {
+		segs = []segment{wildcard{}}
+	}
+
+	if _, err := p.accept("}"); err != nil {
+		return nil, fmt.Errorf("unterminated variable segment: %s", path)
+	}
+	return variable{
+		path:     path,
+		segments: segs,
+	}, nil
+}
+
+func (p *parser) fieldPath() (string, error) {
+	c, err := p.accept(typeIdent)
+	if err != nil {
+		return "", err
+	}
+	components := []string{c}
+	for {
+		if _, err = p.accept("."); err != nil {
+			return strings.Join(components, "."), nil
+		}
+		c, err := p.accept(typeIdent)
+		if err != nil {
+			return "", fmt.Errorf("invalid field path component: %v", err)
+		}
+		components = append(components, c)
+	}
+}
+
+// A termType is a type of terminal symbols.
+type termType string
+
+// These constants define some of valid values of termType.
+// They improve readability of parse functions.
+//
+// You can also use "/", "*", "**", "." or "=" as valid values.
+const (
+	typeIdent   = termType("ident")
+	typeLiteral = termType("literal")
+	typeEOF     = termType("$")
+)
+
+const (
+	// eof is the terminal symbol which always appears at the end of token sequence.
+	eof = "\u0000"
+)
+
+// accept tries to accept a token in "p".
+// This function consumes a token and returns it if it matches to the specified "term".
+// If it doesn't match, the function does not consume any tokens and return an error.
+func (p *parser) accept(term termType) (string, error) {
+	t := p.tokens[0]
+	switch term {
+	case "/", "*", "**", ".", "=", "{", "}":
+		if t != string(term) && t != "/" {
+			return "", fmt.Errorf("expected %q but got %q", term, t)
+		}
+	case typeEOF:
+		if t != eof {
+			return "", fmt.Errorf("expected EOF but got %q", t)
+		}
+	case typeIdent:
+		if err := expectIdent(t); err != nil {
+			return "", err
+		}
+	case typeLiteral:
+		if err := expectPChars(t); err != nil {
+			return "", err
+		}
+	default:
+		return "", fmt.Errorf("unknown termType %q", term)
+	}
+	p.tokens = p.tokens[1:]
+	p.accepted = append(p.accepted, t)
+	return t, nil
+}
+
+// expectPChars determines if "t" consists of only pchars defined in RFC3986.
+//
+// https://www.ietf.org/rfc/rfc3986.txt, P.49
+//
+//	pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+//	unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+//	sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+//	              / "*" / "+" / "," / ";" / "="
+//	pct-encoded   = "%" HEXDIG HEXDIG
+func expectPChars(t string) error {
+	const (
+		init = iota
+		pct1
+		pct2
+	)
+	st := init
+	for _, r := range t {
+		if st != init {
+			if !isHexDigit(r) {
+				return fmt.Errorf("invalid hexdigit: %c(%U)", r, r)
+			}
+			switch st {
+			case pct1:
+				st = pct2
+			case pct2:
+				st = init
+			}
+			continue
+		}
+
+		// unreserved
+		switch {
+		case 'A' <= r && r <= 'Z':
+			continue
+		case 'a' <= r && r <= 'z':
+			continue
+		case '0' <= r && r <= '9':
+			continue
+		}
+		switch r {
+		case '-', '.', '_', '~':
+			// unreserved
+		case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
+			// sub-delims
+		case ':', '@':
+			// rest of pchar
+		case '%':
+			// pct-encoded
+			st = pct1
+		default:
+			return fmt.Errorf("invalid character in path segment: %q(%U)", r, r)
+		}
+	}
+	if st != init {
+		return fmt.Errorf("invalid percent-encoding in %q", t)
+	}
+	return nil
+}
+
+// expectIdent determines if "ident" is a valid identifier in .proto schema ([[:alpha:]_][[:alphanum:]_]*).
+func expectIdent(ident string) error {
+	if ident == "" {
+		return fmt.Errorf("empty identifier")
+	}
+	for pos, r := range ident {
+		switch {
+		case '0' <= r && r <= '9':
+			if pos == 0 {
+				return fmt.Errorf("identifier starting with digit: %s", ident)
+			}
+			continue
+		case 'A' <= r && r <= 'Z':
+			continue
+		case 'a' <= r && r <= 'z':
+			continue
+		case r == '_':
+			continue
+		default:
+			return fmt.Errorf("invalid character %q(%U) in identifier: %s", r, r, ident)
+		}
+	}
+	return nil
+}
+
+func isHexDigit(r rune) bool {
+	switch {
+	case '0' <= r && r <= '9':
+		return true
+	case 'A' <= r && r <= 'F':
+		return true
+	case 'a' <= r && r <= 'f':
+		return true
+	}
+	return false
+}

+ 366 - 0
protoc-gen-openapiv2/internal/httprule/parse_test.go

@@ -0,0 +1,366 @@
+package httprule
+
+import (
+	"flag"
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/golang/glog"
+)
+
+func TestTokenize(t *testing.T) {
+	for _, spec := range []struct {
+		src    string
+		tokens []string
+		verb   string
+	}{
+		{
+			src:    "",
+			tokens: []string{eof},
+		},
+		{
+			src:    "v1",
+			tokens: []string{"v1", eof},
+		},
+		{
+			src:    "v1/b",
+			tokens: []string{"v1", "/", "b", eof},
+		},
+		{
+			src:    "v1/endpoint/*",
+			tokens: []string{"v1", "/", "endpoint", "/", "*", eof},
+		},
+		{
+			src:    "v1/endpoint/**",
+			tokens: []string{"v1", "/", "endpoint", "/", "**", eof},
+		},
+		{
+			src: "v1/b/{bucket_name=*}",
+			tokens: []string{
+				"v1", "/",
+				"b", "/",
+				"{", "bucket_name", "=", "*", "}",
+				eof,
+			},
+		},
+		{
+			src: "v1/b/{bucket_name=buckets/*}",
+			tokens: []string{
+				"v1", "/",
+				"b", "/",
+				"{", "bucket_name", "=", "buckets", "/", "*", "}",
+				eof,
+			},
+		},
+		{
+			src: "v1/b/{bucket_name=buckets/*}/o",
+			tokens: []string{
+				"v1", "/",
+				"b", "/",
+				"{", "bucket_name", "=", "buckets", "/", "*", "}", "/",
+				"o",
+				eof,
+			},
+		},
+		{
+			src: "v1/b/{bucket_name=buckets/*}/o/{name}",
+			tokens: []string{
+				"v1", "/",
+				"b", "/",
+				"{", "bucket_name", "=", "buckets", "/", "*", "}", "/",
+				"o", "/", "{", "name", "}",
+				eof,
+			},
+		},
+		{
+			src: "v1/a=b&c=d;e=f:g/endpoint.rdf",
+			tokens: []string{
+				"v1", "/",
+				"a=b&c=d;e=f:g", "/",
+				"endpoint.rdf",
+				eof,
+			},
+		},
+		{
+			src: "v1/a/{endpoint}:a",
+			tokens: []string{
+				"v1", "/",
+				"a", "/",
+				"{", "endpoint", "}",
+				eof,
+			},
+			verb: "a",
+		},
+		{
+			src: "v1/a/{endpoint}:b:c",
+			tokens: []string{
+				"v1", "/",
+				"a", "/",
+				"{", "endpoint", "}",
+				eof,
+			},
+			verb: "b:c",
+		},
+	} {
+		tokens, verb := tokenize(spec.src)
+		if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) {
+			t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want)
+		}
+
+		switch {
+		case spec.verb != "":
+			if got, want := verb, spec.verb; !reflect.DeepEqual(got, want) {
+				t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want)
+			}
+
+		default:
+			if got, want := verb, ""; got != want {
+				t.Errorf("tokenize(%q) = _, %q; want _, %q", spec.src, got, want)
+			}
+
+			src := fmt.Sprintf("%s:%s", spec.src, "LOCK")
+			tokens, verb = tokenize(src)
+			if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) {
+				t.Errorf("tokenize(%q) = %q, _; want %q, _", src, got, want)
+			}
+			if got, want := verb, "LOCK"; got != want {
+				t.Errorf("tokenize(%q) = _, %q; want _, %q", src, got, want)
+			}
+		}
+	}
+}
+
+func TestParseSegments(t *testing.T) {
+	err := flag.Set("v", "3")
+	if err != nil {
+		t.Fatalf("failed to set flag: %v", err)
+	}
+	for _, spec := range []struct {
+		tokens []string
+		want   []segment
+	}{
+		{
+			tokens: []string{eof},
+			want: []segment{
+				literal(eof),
+			},
+		},
+		{
+			// Note: this case will never arise as tokenize() will never return such a sequence of tokens
+			// and even if it does it will be treated as [eof]
+			tokens: []string{eof, "v1", eof},
+			want: []segment{
+				literal(eof),
+			},
+		},
+		{
+			tokens: []string{"v1", eof},
+			want: []segment{
+				literal("v1"),
+			},
+		},
+		{
+			tokens: []string{"/", eof},
+			want: []segment{
+				wildcard{},
+			},
+		},
+		{
+			tokens: []string{"-._~!$&'()*+,;=:@", eof},
+			want: []segment{
+				literal("-._~!$&'()*+,;=:@"),
+			},
+		},
+		{
+			tokens: []string{"%e7%ac%ac%e4%b8%80%e7%89%88", eof},
+			want: []segment{
+				literal("%e7%ac%ac%e4%b8%80%e7%89%88"),
+			},
+		},
+		{
+			tokens: []string{"v1", "/", "*", eof},
+			want: []segment{
+				literal("v1"),
+				wildcard{},
+			},
+		},
+		{
+			tokens: []string{"v1", "/", "**", eof},
+			want: []segment{
+				literal("v1"),
+				deepWildcard{},
+			},
+		},
+		{
+			tokens: []string{"{", "name", "}", eof},
+			want: []segment{
+				variable{
+					path: "name",
+					segments: []segment{
+						wildcard{},
+					},
+				},
+			},
+		},
+		{
+			tokens: []string{"{", "name", "=", "*", "}", eof},
+			want: []segment{
+				variable{
+					path: "name",
+					segments: []segment{
+						wildcard{},
+					},
+				},
+			},
+		},
+		{
+			tokens: []string{"{", "field", ".", "nested", ".", "nested2", "=", "*", "}", eof},
+			want: []segment{
+				variable{
+					path: "field.nested.nested2",
+					segments: []segment{
+						wildcard{},
+					},
+				},
+			},
+		},
+		{
+			tokens: []string{"{", "name", "=", "a", "/", "b", "/", "*", "}", eof},
+			want: []segment{
+				variable{
+					path: "name",
+					segments: []segment{
+						literal("a"),
+						literal("b"),
+						wildcard{},
+					},
+				},
+			},
+		},
+		{
+			tokens: []string{
+				"v1", "/",
+				"{",
+				"name", ".", "nested", ".", "nested2",
+				"=",
+				"a", "/", "b", "/", "*",
+				"}", "/",
+				"o", "/",
+				"{",
+				"another_name",
+				"=",
+				"a", "/", "b", "/", "*", "/", "c",
+				"}", "/",
+				"**",
+				eof,
+			},
+			want: []segment{
+				literal("v1"),
+				variable{
+					path: "name.nested.nested2",
+					segments: []segment{
+						literal("a"),
+						literal("b"),
+						wildcard{},
+					},
+				},
+				literal("o"),
+				variable{
+					path: "another_name",
+					segments: []segment{
+						literal("a"),
+						literal("b"),
+						wildcard{},
+						literal("c"),
+					},
+				},
+				deepWildcard{},
+			},
+		},
+	} {
+		p := parser{tokens: spec.tokens}
+		segs, err := p.topLevelSegments()
+		if err != nil {
+			t.Errorf("parser{%q}.segments() failed with %v; want success", spec.tokens, err)
+			continue
+		}
+		if got, want := segs, spec.want; !reflect.DeepEqual(got, want) {
+			t.Errorf("parser{%q}.segments() = %#v; want %#v", spec.tokens, got, want)
+		}
+		if got := p.tokens; len(got) > 0 {
+			t.Errorf("p.tokens = %q; want []; spec.tokens=%q", got, spec.tokens)
+		}
+	}
+}
+
+func TestParseSegmentsWithErrors(t *testing.T) {
+	err := flag.Set("v", "3")
+	if err != nil {
+		t.Fatalf("failed to set flag: %v", err)
+	}
+	for _, spec := range []struct {
+		tokens []string
+	}{
+		{
+			// double slash
+			tokens: []string{"//", eof},
+		},
+		{
+			// invalid literal
+			tokens: []string{"a?b", eof},
+		},
+		{
+			// invalid percent-encoding
+			tokens: []string{"%", eof},
+		},
+		{
+			// invalid percent-encoding
+			tokens: []string{"%2", eof},
+		},
+		{
+			// invalid percent-encoding
+			tokens: []string{"a%2z", eof},
+		},
+		{
+			// unterminated variable
+			tokens: []string{"{", "name", eof},
+		},
+		{
+			// unterminated variable
+			tokens: []string{"{", "name", "=", eof},
+		},
+		{
+			// unterminated variable
+			tokens: []string{"{", "name", "=", "*", eof},
+		},
+		{
+			// empty component in field path
+			tokens: []string{"{", "name", ".", "}", eof},
+		},
+		{
+			// empty component in field path
+			tokens: []string{"{", "name", ".", ".", "nested", "}", eof},
+		},
+		{
+			// invalid character in identifier
+			tokens: []string{"{", "field-name", "}", eof},
+		},
+		{
+			// no slash between segments
+			tokens: []string{"v1", "endpoint", eof},
+		},
+		{
+			// no slash between segments
+			tokens: []string{"v1", "{", "name", "}", eof},
+		},
+	} {
+		p := parser{tokens: spec.tokens}
+		segs, err := p.topLevelSegments()
+		if err == nil {
+			t.Errorf("parser{%q}.segments() succeeded; want InvalidTemplateError; accepted %#v", spec.tokens, segs)
+			continue
+		}
+		glog.V(1).Info(err)
+	}
+}

+ 60 - 0
protoc-gen-openapiv2/internal/httprule/types.go

@@ -0,0 +1,60 @@
+package httprule
+
+import (
+	"fmt"
+	"strings"
+)
+
+type template struct {
+	segments []segment
+	verb     string
+	template string
+}
+
+type segment interface {
+	fmt.Stringer
+	compile() (ops []op)
+}
+
+type wildcard struct{}
+
+type deepWildcard struct{}
+
+type literal string
+
+type variable struct {
+	path     string
+	segments []segment
+}
+
+func (wildcard) String() string {
+	return "*"
+}
+
+func (deepWildcard) String() string {
+	return "**"
+}
+
+func (l literal) String() string {
+	return string(l)
+}
+
+func (v variable) String() string {
+	var segs []string
+	for _, s := range v.segments {
+		segs = append(segs, s.String())
+	}
+	return fmt.Sprintf("{%s=%s}", v.path, strings.Join(segs, "/"))
+}
+
+func (t template) String() string {
+	var segs []string
+	for _, s := range t.segments {
+		segs = append(segs, s.String())
+	}
+	str := strings.Join(segs, "/")
+	if t.verb != "" {
+		str = fmt.Sprintf("%s:%s", str, t.verb)
+	}
+	return "/" + str
+}

+ 91 - 0
protoc-gen-openapiv2/internal/httprule/types_test.go

@@ -0,0 +1,91 @@
+package httprule
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestTemplateStringer(t *testing.T) {
+	for _, spec := range []struct {
+		segs []segment
+		want string
+	}{
+		{
+			segs: []segment{
+				literal("v1"),
+			},
+			want: "/v1",
+		},
+		{
+			segs: []segment{
+				wildcard{},
+			},
+			want: "/*",
+		},
+		{
+			segs: []segment{
+				deepWildcard{},
+			},
+			want: "/**",
+		},
+		{
+			segs: []segment{
+				variable{
+					path: "name",
+					segments: []segment{
+						literal("a"),
+					},
+				},
+			},
+			want: "/{name=a}",
+		},
+		{
+			segs: []segment{
+				variable{
+					path: "name",
+					segments: []segment{
+						literal("a"),
+						wildcard{},
+						literal("b"),
+					},
+				},
+			},
+			want: "/{name=a/*/b}",
+		},
+		{
+			segs: []segment{
+				literal("v1"),
+				variable{
+					path: "name",
+					segments: []segment{
+						literal("a"),
+						wildcard{},
+						literal("b"),
+					},
+				},
+				literal("c"),
+				variable{
+					path: "field.nested",
+					segments: []segment{
+						wildcard{},
+						literal("d"),
+					},
+				},
+				wildcard{},
+				literal("e"),
+				deepWildcard{},
+			},
+			want: "/v1/{name=a/*/b}/c/{field.nested=*/d}/*/e/**",
+		},
+	} {
+		tmpl := template{segments: spec.segs}
+		if got, want := tmpl.String(), spec.want; got != want {
+			t.Errorf("%#v.String() = %q; want %q", tmpl, got, want)
+		}
+
+		tmpl.verb = "LOCK"
+		if got, want := tmpl.String(), fmt.Sprintf("%s:LOCK", spec.want); got != want {
+			t.Errorf("%#v.String() = %q; want %q", tmpl, got, want)
+		}
+	}
+}

+ 228 - 0
protoc-gen-openapiv2/main.go

@@ -0,0 +1,228 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"strings"
+
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/codegenerator"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/descriptor"
+	"git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/genopenapi"
+	"github.com/golang/glog"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/pluginpb"
+)
+
+var (
+	importPrefix               = flag.String("import_prefix", "", "prefix to be added to go package paths for imported proto files")
+	file                       = flag.String("file", "-", "where to load data from")
+	allowDeleteBody            = flag.Bool("allow_delete_body", false, "unless set, HTTP DELETE methods may not have a body")
+	grpcAPIConfiguration       = flag.String("grpc_api_configuration", "", "path to file which describes the gRPC API Configuration in YAML format")
+	allowMerge                 = flag.Bool("allow_merge", false, "if set, generation one OpenAPI file out of multiple protos")
+	mergeFileName              = flag.String("merge_file_name", "apidocs", "target OpenAPI file name prefix after merge")
+	useJSONNamesForFields      = flag.Bool("json_names_for_fields", true, "if disabled, the original proto name will be used for generating OpenAPI definitions")
+	repeatedPathParamSeparator = flag.String("repeated_path_param_separator", "csv", "configures how repeated fields should be split. Allowed values are `csv`, `pipes`, `ssv` and `tsv`")
+	versionFlag                = flag.Bool("version", false, "print the current version")
+	allowRepeatedFieldsInBody  = flag.Bool("allow_repeated_fields_in_body", false, "allows to use repeated field in `body` and `response_body` field of `google.api.http` annotation option")
+	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")
+	useGoTemplate              = flag.Bool("use_go_templates", false, "if set, you can use Go templates in protofile comments")
+	disableDefaultErrors       = flag.Bool("disable_default_errors", false, "if set, disables generation of default errors. This is useful if you have defined custom error handling")
+	enumsAsInts                = flag.Bool("enums_as_ints", false, "whether to render enum values as integers, as opposed to string values")
+	simpleOperationIDs         = flag.Bool("simple_operation_ids", false, "whether to remove the service prefix in the operationID generation. Can introduce duplicate operationIDs, use with caution.")
+	proto3OptionalNullable     = flag.Bool("proto3_optional_nullable", false, "whether Proto3 Optional fields should be marked as x-nullable")
+	openAPIConfiguration       = flag.String("openapi_configuration", "", "path to file which describes the OpenAPI Configuration in YAML format")
+	generateUnboundMethods     = flag.Bool("generate_unbound_methods", false, "generate swagger metadata even for RPC methods that have no HttpRule annotation")
+	generateRPCMethods         = flag.Bool("generate_rpc_methods", false, "generate swagger metadata even for RPC methods without HttpRule annotation")
+	recursiveDepth             = flag.Int("recursive-depth", 1000, "maximum recursion count allowed for a field type")
+)
+
+// Variables set by goreleaser at build time
+var (
+	version = "dev123"
+	commit  = "unknown"
+	date    = "unknown"
+)
+
+func main() {
+	flag.Parse()
+	defer glog.Flush()
+
+	if *versionFlag {
+		fmt.Printf("Version %v, commit %v, built at %v\n", version, commit, date)
+		os.Exit(0)
+	}
+
+	reg := descriptor.NewRegistry()
+
+	glog.V(1).Info("Processing code generator request")
+	f := os.Stdin
+	if *file != "-" {
+		var err error
+		f, err = os.Open(*file)
+		if err != nil {
+			glog.Fatal(err)
+		}
+	}
+	glog.V(1).Info("Parsing code generator request")
+	req, err := codegenerator.ParseRequest(f)
+	if err != nil {
+		glog.Fatal(err)
+	}
+	glog.V(1).Info("Parsed code generator request")
+	pkgMap := make(map[string]string)
+	if req.Parameter != nil {
+		err := parseReqParam(req.GetParameter(), flag.CommandLine, pkgMap)
+		if err != nil {
+			glog.Fatalf("Error parsing flags: %v", err)
+		}
+	}
+
+	reg.SetPrefix(*importPrefix)
+	reg.SetAllowDeleteBody(*allowDeleteBody)
+	reg.SetAllowMerge(*allowMerge)
+	reg.SetMergeFileName(*mergeFileName)
+	reg.SetUseJSONNamesForFields(*useJSONNamesForFields)
+	reg.SetAllowRepeatedFieldsInBody(*allowRepeatedFieldsInBody)
+	reg.SetIncludePackageInTags(*includePackageInTags)
+	reg.SetUseFQNForOpenAPIName(*useFQNForOpenAPIName)
+	reg.SetUseGoTemplate(*useGoTemplate)
+	reg.SetEnumsAsInts(*enumsAsInts)
+	reg.SetDisableDefaultErrors(*disableDefaultErrors)
+	reg.SetSimpleOperationIDs(*simpleOperationIDs)
+	reg.SetProto3OptionalNullable(*proto3OptionalNullable)
+	reg.SetGenerateUnboundMethods(*generateUnboundMethods)
+	reg.SetGenerateRPCMethods(*generateRPCMethods)
+	reg.SetRecursiveDepth(*recursiveDepth)
+	if err := reg.SetRepeatedPathParamSeparator(*repeatedPathParamSeparator); err != nil {
+		emitError(err)
+		return
+	}
+	for k, v := range pkgMap {
+		reg.AddPkgMap(k, v)
+	}
+
+	if *grpcAPIConfiguration != "" {
+		if err := reg.LoadGrpcAPIServiceFromYAML(*grpcAPIConfiguration); err != nil {
+			emitError(err)
+			return
+		}
+	}
+
+	g := genopenapi.New(reg)
+
+	if err := genopenapi.AddErrorDefs(reg); err != nil {
+		emitError(err)
+		return
+	}
+
+	if err := reg.Load(req); err != nil {
+		emitError(err)
+		return
+	}
+
+	if *openAPIConfiguration != "" {
+		if err := reg.LoadOpenAPIConfigFromYAML(*openAPIConfiguration); err != nil {
+			emitError(err)
+			return
+		}
+	}
+
+	var targets []*descriptor.File
+	for _, target := range req.FileToGenerate {
+		f, err := reg.LookupFile(target)
+		if err != nil {
+			glog.Fatal(err)
+		}
+		targets = append(targets, f)
+	}
+
+	out, err := g.Generate(targets)
+	glog.V(1).Info("Processed code generator request")
+	if err != nil {
+		emitError(err)
+		return
+	}
+	emitFiles(out)
+}
+
+func emitFiles(out []*descriptor.ResponseFile) {
+	files := make([]*pluginpb.CodeGeneratorResponse_File, len(out))
+	for idx, item := range out {
+		files[idx] = item.CodeGeneratorResponse_File
+	}
+	resp := &pluginpb.CodeGeneratorResponse{File: files}
+	codegenerator.SetSupportedFeaturesOnCodeGeneratorResponse(resp)
+	emitResp(resp)
+}
+
+func emitError(err error) {
+	emitResp(&pluginpb.CodeGeneratorResponse{Error: proto.String(err.Error())})
+}
+
+func emitResp(resp *pluginpb.CodeGeneratorResponse) {
+	buf, err := proto.Marshal(resp)
+	if err != nil {
+		glog.Fatal(err)
+	}
+	if _, err := os.Stdout.Write(buf); err != nil {
+		glog.Fatal(err)
+	}
+}
+
+// parseReqParam parses a CodeGeneratorRequest parameter and adds the
+// extracted values to the given FlagSet and pkgMap. Returns a non-nil
+// error if setting a flag failed.
+func parseReqParam(param string, f *flag.FlagSet, pkgMap map[string]string) error {
+	if param == "" {
+		return nil
+	}
+	for _, p := range strings.Split(param, ",") {
+		spec := strings.SplitN(p, "=", 2)
+		if len(spec) == 1 {
+			if spec[0] == "allow_delete_body" {
+				err := f.Set(spec[0], "true")
+				if err != nil {
+					return fmt.Errorf("cannot set flag %s: %v", p, err)
+				}
+				continue
+			}
+			if spec[0] == "allow_merge" {
+				err := f.Set(spec[0], "true")
+				if err != nil {
+					return fmt.Errorf("cannot set flag %s: %v", p, err)
+				}
+				continue
+			}
+			if spec[0] == "allow_repeated_fields_in_body" {
+				err := f.Set(spec[0], "true")
+				if err != nil {
+					return fmt.Errorf("cannot set flag %s: %v", p, err)
+				}
+				continue
+			}
+			if spec[0] == "include_package_in_tags" {
+				err := f.Set(spec[0], "true")
+				if err != nil {
+					return fmt.Errorf("cannot set flag %s: %v", p, err)
+				}
+				continue
+			}
+			err := f.Set(spec[0], "")
+			if err != nil {
+				return fmt.Errorf("cannot set flag %s: %v", p, err)
+			}
+			continue
+		}
+		name, value := spec[0], spec[1]
+		if strings.HasPrefix(name, "M") {
+			pkgMap[name[1:]] = value
+			continue
+		}
+		if err := f.Set(name, value); err != nil {
+			return fmt.Errorf("cannot set flag %s: %v", p, err)
+		}
+	}
+	return nil
+}

+ 186 - 0
protoc-gen-openapiv2/main_test.go

@@ -0,0 +1,186 @@
+package main
+
+import (
+	"errors"
+	"flag"
+	"reflect"
+	"testing"
+)
+
+func TestParseReqParam(t *testing.T) {
+
+	testcases := []struct {
+		name                       string
+		expected                   map[string]string
+		request                    string
+		expectedError              error
+		allowDeleteBodyV           bool
+		allowMergeV                bool
+		allowRepeatedFieldsInBodyV bool
+		includePackageInTagsV      bool
+		fileV                      string
+		importPathV                string
+		mergeFileNameV             string
+		useFQNForOpenAPINameV      bool
+	}{
+		{
+			// this one must be first - with no leading clearFlags call it
+			// verifies our expectation of default values as we reset by
+			// clearFlags
+			name:             "Test 0",
+			expected:         map[string]string{},
+			request:          "",
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
+			fileV: "-", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 1",
+			expected:         map[string]string{"google/api/annotations.proto": "github.com/googleapis/googleapis/google/api"},
+			request:          "allow_delete_body,allow_merge,allow_repeated_fields_in_body,include_package_in_tags,file=./foo.pb,import_prefix=/bar/baz,Mgoogle/api/annotations.proto=github.com/googleapis/googleapis/google/api",
+			allowDeleteBodyV: true, allowMergeV: true, allowRepeatedFieldsInBodyV: true, includePackageInTagsV: true,
+			fileV: "./foo.pb", importPathV: "/bar/baz", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 2",
+			expected:         map[string]string{"google/api/annotations.proto": "github.com/googleapis/googleapis/google/api"},
+			request:          "allow_delete_body=true,allow_merge=true,allow_repeated_fields_in_body=true,include_package_in_tags=true,merge_file_name=test_name,file=./foo.pb,import_prefix=/bar/baz,Mgoogle/api/annotations.proto=github.com/googleapis/googleapis/google/api",
+			allowDeleteBodyV: true, allowMergeV: true, allowRepeatedFieldsInBodyV: true, includePackageInTagsV: true,
+			fileV: "./foo.pb", importPathV: "/bar/baz", mergeFileNameV: "test_name",
+		},
+		{
+			name:             "Test 3",
+			expected:         map[string]string{"a/b/c.proto": "github.com/x/y/z", "f/g/h.proto": "github.com/1/2/3/"},
+			request:          "allow_delete_body=false,allow_merge=false,Ma/b/c.proto=github.com/x/y/z,Mf/g/h.proto=github.com/1/2/3/",
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
+			fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 4",
+			expected:         map[string]string{},
+			request:          "",
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
+			fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 5",
+			expected:         map[string]string{},
+			request:          "unknown_param=17",
+			expectedError:    errors.New("cannot set flag unknown_param=17: no such flag -unknown_param"),
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
+			fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 6",
+			expected:         map[string]string{},
+			request:          "Mfoo",
+			expectedError:    errors.New("cannot set flag Mfoo: no such flag -Mfoo"),
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
+			fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 7",
+			expected:         map[string]string{},
+			request:          "allow_delete_body,file,import_prefix,allow_merge,allow_repeated_fields_in_body,include_package_in_tags,merge_file_name",
+			allowDeleteBodyV: true, allowMergeV: true, allowRepeatedFieldsInBodyV: true, includePackageInTagsV: true,
+			fileV: "", importPathV: "", mergeFileNameV: "",
+		},
+		{
+			name:             "Test 8",
+			expected:         map[string]string{},
+			request:          "allow_delete_body,file,import_prefix,allow_merge,allow_repeated_fields_in_body=3,merge_file_name",
+			expectedError:    errors.New(`cannot set flag allow_repeated_fields_in_body=3: parse error`),
+			allowDeleteBodyV: true, allowMergeV: true, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
+			fileV: "", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 9",
+			expected:         map[string]string{},
+			request:          "include_package_in_tags=3",
+			expectedError:    errors.New(`cannot set flag include_package_in_tags=3: parse error`),
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
+			fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 10",
+			expected:         map[string]string{},
+			request:          "fqn_for_openapi_name=3",
+			expectedError:    errors.New(`cannot set flag fqn_for_openapi_name=3: parse error`),
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false, useFQNForOpenAPINameV: false,
+			fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
+		},
+		{
+			name:             "Test 11",
+			expected:         map[string]string{},
+			request:          "fqn_for_openapi_name=true",
+			allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false, useFQNForOpenAPINameV: true,
+			fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
+		},
+	}
+
+	for i, tc := range testcases {
+		t.Run(tc.name, func(tt *testing.T) {
+			f := flag.CommandLine
+			pkgMap := make(map[string]string)
+			err := parseReqParam(tc.request, f, pkgMap)
+			if tc.expectedError == nil {
+				if err != nil {
+					tt.Errorf("unexpected parse error '%v'", err)
+				}
+				if !reflect.DeepEqual(pkgMap, tc.expected) {
+					tt.Errorf("pkgMap parse error, expected '%v', got '%v'", tc.expected, pkgMap)
+				}
+			} else {
+				if err == nil {
+					tt.Error("expected parse error not returned")
+				}
+				if !reflect.DeepEqual(pkgMap, tc.expected) {
+					tt.Errorf("pkgMap parse error, expected '%v', got '%v'", tc.expected, pkgMap)
+				}
+				if err.Error() != tc.expectedError.Error() {
+					tt.Errorf("expected error malformed, expected %q, got %q", tc.expectedError.Error(), err.Error())
+				}
+			}
+			checkFlags(tc.allowDeleteBodyV, tc.allowMergeV, tc.allowRepeatedFieldsInBodyV, tc.includePackageInTagsV, tc.useFQNForOpenAPINameV, tc.fileV, tc.importPathV, tc.mergeFileNameV, tt, i)
+
+			clearFlags()
+		})
+	}
+
+}
+
+func checkFlags(allowDeleteV, allowMergeV, allowRepeatedFieldsInBodyV, includePackageInTagsV bool, useFQNForOpenAPINameV bool, fileV, importPathV, mergeFileNameV string, t *testing.T, tid int) {
+	if *importPrefix != importPathV {
+		t.Errorf("Test %v: import_prefix misparsed, expected '%v', got '%v'", tid, importPathV, *importPrefix)
+	}
+	if *file != fileV {
+		t.Errorf("Test %v: file misparsed, expected '%v', got '%v'", tid, fileV, *file)
+	}
+	if *allowDeleteBody != allowDeleteV {
+		t.Errorf("Test %v: allow_delete_body misparsed, expected '%v', got '%v'", tid, allowDeleteV, *allowDeleteBody)
+	}
+	if *allowMerge != allowMergeV {
+		t.Errorf("Test %v: allow_merge misparsed, expected '%v', got '%v'", tid, allowMergeV, *allowMerge)
+	}
+	if *mergeFileName != mergeFileNameV {
+		t.Errorf("Test %v: merge_file_name misparsed, expected '%v', got '%v'", tid, mergeFileNameV, *mergeFileName)
+	}
+	if *allowRepeatedFieldsInBody != allowRepeatedFieldsInBodyV {
+		t.Errorf("Test %v: allow_repeated_fields_in_body misparsed, expected '%v', got '%v'", tid, allowRepeatedFieldsInBodyV, *allowRepeatedFieldsInBody)
+	}
+	if *includePackageInTags != includePackageInTagsV {
+		t.Errorf("Test %v: include_package_in_tags misparsed, expected '%v', got '%v'", tid, includePackageInTagsV, *includePackageInTags)
+	}
+	if *useFQNForOpenAPIName != useFQNForOpenAPINameV {
+		t.Errorf("Test %v: fqn_for_openapi_name misparsed, expected '%v', got '%v'", tid, useFQNForOpenAPINameV, *useFQNForOpenAPIName)
+	}
+}
+
+func clearFlags() {
+	*importPrefix = ""
+	*file = "stdin"
+	*allowDeleteBody = false
+	*allowMerge = false
+	*allowRepeatedFieldsInBody = false
+	*includePackageInTags = false
+	*mergeFileName = "apidocs"
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است