main.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "strings"
  7. "github.com/grpc-ecosystem/grpc-gateway/v2/internal/codegenerator"
  8. "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor"
  9. "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/internal/genopenapi"
  10. "github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
  11. "google.golang.org/grpc/grpclog"
  12. "google.golang.org/protobuf/proto"
  13. "google.golang.org/protobuf/types/pluginpb"
  14. )
  15. var (
  16. importPrefix = flag.String("import_prefix", "", "prefix to be added to go package paths for imported proto files")
  17. file = flag.String("file", "-", "where to load data from")
  18. allowDeleteBody = flag.Bool("allow_delete_body", false, "unless set, HTTP DELETE methods may not have a body")
  19. grpcAPIConfiguration = flag.String("grpc_api_configuration", "", "path to file which describes the gRPC API Configuration in YAML format")
  20. allowMerge = flag.Bool("allow_merge", false, "if set, generation one OpenAPI file out of multiple protos")
  21. mergeFileName = flag.String("merge_file_name", "apidocs", "target OpenAPI file name prefix after merge")
  22. useJSONNamesForFields = flag.Bool("json_names_for_fields", true, "if disabled, the original proto name will be used for generating OpenAPI definitions")
  23. repeatedPathParamSeparator = flag.String("repeated_path_param_separator", "csv", "configures how repeated fields should be split. Allowed values are `csv`, `pipes`, `ssv` and `tsv`")
  24. versionFlag = flag.Bool("version", false, "print the current version")
  25. _ = flag.Bool("allow_repeated_fields_in_body", true, "allows to use repeated field in `body` and `response_body` field of `google.api.http` annotation option. DEPRECATED: the value is ignored and always behaves as `true`.")
  26. 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")
  27. useFQNForOpenAPIName = flag.Bool("fqn_for_openapi_name", false, "if set, the object's OpenAPI names will use the fully qualified names from the proto definition (ie my.package.MyMessage.MyInnerMessage). DEPRECATED: prefer `openapi_naming_strategy=fqn`")
  28. openAPINamingStrategy = flag.String("openapi_naming_strategy", "", "use the given OpenAPI naming strategy. Allowed values are `legacy`, `fqn`, `simple`. If unset, either `legacy` or `fqn` are selected, depending on the value of the `fqn_for_openapi_name` flag")
  29. useGoTemplate = flag.Bool("use_go_templates", false, "if set, you can use Go templates in protofile comments")
  30. goTemplateArgs = utilities.StringArrayFlag(flag.CommandLine, "go_template_args", "provide a custom value that can override a key in the Go template. Requires the `use_go_templates` option to be set")
  31. ignoreComments = flag.Bool("ignore_comments", false, "if set, all protofile comments are excluded from output")
  32. removeInternalComments = flag.Bool("remove_internal_comments", false, "if set, removes all substrings in comments that start with `(--` and end with `--)` as specified in https://google.aip.dev/192#internal-comments")
  33. disableDefaultErrors = flag.Bool("disable_default_errors", false, "if set, disables generation of default errors. This is useful if you have defined custom error handling")
  34. enumsAsInts = flag.Bool("enums_as_ints", false, "whether to render enum values as integers, as opposed to string values")
  35. simpleOperationIDs = flag.Bool("simple_operation_ids", false, "whether to remove the service prefix in the operationID generation. Can introduce duplicate operationIDs, use with caution.")
  36. proto3OptionalNullable = flag.Bool("proto3_optional_nullable", false, "whether Proto3 Optional fields should be marked as x-nullable")
  37. openAPIConfiguration = flag.String("openapi_configuration", "", "path to file which describes the OpenAPI Configuration in YAML format")
  38. generateUnboundMethods = flag.Bool("generate_unbound_methods", false, "generate swagger metadata even for RPC methods that have no HttpRule annotation")
  39. recursiveDepth = flag.Int("recursive-depth", 1000, "maximum recursion count allowed for a field type")
  40. omitEnumDefaultValue = flag.Bool("omit_enum_default_value", false, "if set, omit default enum value")
  41. outputFormat = flag.String("output_format", string(genopenapi.FormatJSON), fmt.Sprintf("output content format. Allowed values are: `%s`, `%s`", genopenapi.FormatJSON, genopenapi.FormatYAML))
  42. visibilityRestrictionSelectors = utilities.StringArrayFlag(flag.CommandLine, "visibility_restriction_selectors", "list of `google.api.VisibilityRule` visibility labels to include in the generated output when a visibility annotation is defined. Repeat this option to supply multiple values. Elements without visibility annotations are unaffected by this setting.")
  43. disableServiceTags = flag.Bool("disable_service_tags", false, "if set, disables generation of service tags. This is useful if you do not want to expose the names of your backend grpc services.")
  44. disableDefaultResponses = flag.Bool("disable_default_responses", false, "if set, disables generation of default responses. Useful if you have to support custom response codes that are not 200.")
  45. useAllOfForRefs = flag.Bool("use_allof_for_refs", false, "if set, will use allOf as container for $ref to preserve same-level properties.")
  46. allowPatchFeature = flag.Bool("allow_patch_feature", true, "whether to hide update_mask fields in PATCH requests from the generated swagger file.")
  47. preserveRPCOrder = flag.Bool("preserve_rpc_order", false, "if true, will ensure the order of paths emitted in openapi swagger files mirror the order of RPC methods found in proto files. If false, emitted paths will be ordered alphabetically.")
  48. _ = flag.Bool("logtostderr", false, "Legacy glog compatibility. This flag is a no-op, you can safely remove it")
  49. )
  50. // Variables set by goreleaser at build time
  51. var (
  52. version = "dev"
  53. commit = "unknown"
  54. date = "unknown"
  55. )
  56. func main() {
  57. flag.Parse()
  58. if *versionFlag {
  59. fmt.Printf("Version %v, commit %v, built at %v\n", version, commit, date)
  60. os.Exit(0)
  61. }
  62. reg := descriptor.NewRegistry()
  63. if grpclog.V(1) {
  64. grpclog.Info("Processing code generator request")
  65. }
  66. f := os.Stdin
  67. if *file != "-" {
  68. var err error
  69. f, err = os.Open(*file)
  70. if err != nil {
  71. grpclog.Fatal(err)
  72. }
  73. }
  74. if grpclog.V(1) {
  75. grpclog.Info("Parsing code generator request")
  76. }
  77. req, err := codegenerator.ParseRequest(f)
  78. if err != nil {
  79. grpclog.Fatal(err)
  80. }
  81. if grpclog.V(1) {
  82. grpclog.Info("Parsed code generator request")
  83. }
  84. pkgMap := make(map[string]string)
  85. if req.Parameter != nil {
  86. if err := parseReqParam(req.GetParameter(), flag.CommandLine, pkgMap); err != nil {
  87. grpclog.Fatalf("Error parsing flags: %v", err)
  88. }
  89. }
  90. reg.SetPrefix(*importPrefix)
  91. reg.SetAllowDeleteBody(*allowDeleteBody)
  92. reg.SetAllowMerge(*allowMerge)
  93. reg.SetMergeFileName(*mergeFileName)
  94. reg.SetUseJSONNamesForFields(*useJSONNamesForFields)
  95. flag.Visit(func(f *flag.Flag) {
  96. if f.Name == "allow_repeated_fields_in_body" {
  97. grpclog.Warning("The `allow_repeated_fields_in_body` flag is deprecated and will always behave as `true`.")
  98. }
  99. })
  100. reg.SetIncludePackageInTags(*includePackageInTags)
  101. reg.SetUseFQNForOpenAPIName(*useFQNForOpenAPIName)
  102. // Set the naming strategy either directly from the flag, or via the value of the legacy fqn_for_openapi_name
  103. // flag.
  104. namingStrategy := *openAPINamingStrategy
  105. if *useFQNForOpenAPIName {
  106. if namingStrategy != "" {
  107. grpclog.Fatal("The deprecated `fqn_for_openapi_name` flag must remain unset if `openapi_naming_strategy` is set.")
  108. }
  109. grpclog.Warning("The `fqn_for_openapi_name` flag is deprecated. Please use `openapi_naming_strategy=fqn` instead.")
  110. namingStrategy = "fqn"
  111. } else if namingStrategy == "" {
  112. namingStrategy = "legacy"
  113. }
  114. if strategyFn := genopenapi.LookupNamingStrategy(namingStrategy); strategyFn == nil {
  115. emitError(fmt.Errorf("invalid naming strategy %q", namingStrategy))
  116. return
  117. }
  118. if *useGoTemplate && *ignoreComments {
  119. emitError(fmt.Errorf("`ignore_comments` and `use_go_templates` are mutually exclusive and cannot be enabled at the same time"))
  120. return
  121. }
  122. reg.SetUseGoTemplate(*useGoTemplate)
  123. reg.SetIgnoreComments(*ignoreComments)
  124. reg.SetRemoveInternalComments(*removeInternalComments)
  125. if len(*goTemplateArgs) > 0 && !*useGoTemplate {
  126. emitError(fmt.Errorf("`go_template_args` requires `use_go_templates` to be enabled"))
  127. return
  128. }
  129. reg.SetGoTemplateArgs(*goTemplateArgs)
  130. reg.SetOpenAPINamingStrategy(namingStrategy)
  131. reg.SetEnumsAsInts(*enumsAsInts)
  132. reg.SetDisableDefaultErrors(*disableDefaultErrors)
  133. reg.SetSimpleOperationIDs(*simpleOperationIDs)
  134. reg.SetProto3OptionalNullable(*proto3OptionalNullable)
  135. reg.SetGenerateUnboundMethods(*generateUnboundMethods)
  136. reg.SetRecursiveDepth(*recursiveDepth)
  137. reg.SetOmitEnumDefaultValue(*omitEnumDefaultValue)
  138. reg.SetVisibilityRestrictionSelectors(*visibilityRestrictionSelectors)
  139. reg.SetDisableServiceTags(*disableServiceTags)
  140. reg.SetDisableDefaultResponses(*disableDefaultResponses)
  141. reg.SetUseAllOfForRefs(*useAllOfForRefs)
  142. reg.SetAllowPatchFeature(*allowPatchFeature)
  143. reg.SetPreserveRPCOrder(*preserveRPCOrder)
  144. if err := reg.SetRepeatedPathParamSeparator(*repeatedPathParamSeparator); err != nil {
  145. emitError(err)
  146. return
  147. }
  148. for k, v := range pkgMap {
  149. reg.AddPkgMap(k, v)
  150. }
  151. if *grpcAPIConfiguration != "" {
  152. if err := reg.LoadGrpcAPIServiceFromYAML(*grpcAPIConfiguration); err != nil {
  153. emitError(err)
  154. return
  155. }
  156. }
  157. format := genopenapi.Format(*outputFormat)
  158. if err := format.Validate(); err != nil {
  159. emitError(err)
  160. return
  161. }
  162. g := genopenapi.New(reg, format)
  163. if err := genopenapi.AddErrorDefs(reg); err != nil {
  164. emitError(err)
  165. return
  166. }
  167. if err := reg.Load(req); err != nil {
  168. emitError(err)
  169. return
  170. }
  171. if *openAPIConfiguration != "" {
  172. if err := reg.LoadOpenAPIConfigFromYAML(*openAPIConfiguration); err != nil {
  173. emitError(err)
  174. return
  175. }
  176. }
  177. targets := make([]*descriptor.File, 0, len(req.FileToGenerate))
  178. for _, target := range req.FileToGenerate {
  179. f, err := reg.LookupFile(target)
  180. if err != nil {
  181. grpclog.Fatal(err)
  182. }
  183. targets = append(targets, f)
  184. }
  185. out, err := g.Generate(targets)
  186. if grpclog.V(1) {
  187. grpclog.Info("Processed code generator request")
  188. }
  189. if err != nil {
  190. emitError(err)
  191. return
  192. }
  193. emitFiles(out)
  194. }
  195. func emitFiles(out []*descriptor.ResponseFile) {
  196. files := make([]*pluginpb.CodeGeneratorResponse_File, len(out))
  197. for idx, item := range out {
  198. files[idx] = item.CodeGeneratorResponse_File
  199. }
  200. resp := &pluginpb.CodeGeneratorResponse{File: files}
  201. codegenerator.SetSupportedFeaturesOnCodeGeneratorResponse(resp)
  202. emitResp(resp)
  203. }
  204. func emitError(err error) {
  205. emitResp(&pluginpb.CodeGeneratorResponse{Error: proto.String(err.Error())})
  206. }
  207. func emitResp(resp *pluginpb.CodeGeneratorResponse) {
  208. buf, err := proto.Marshal(resp)
  209. if err != nil {
  210. grpclog.Fatal(err)
  211. }
  212. if _, err := os.Stdout.Write(buf); err != nil {
  213. grpclog.Fatal(err)
  214. }
  215. }
  216. // parseReqParam parses a CodeGeneratorRequest parameter and adds the
  217. // extracted values to the given FlagSet and pkgMap. Returns a non-nil
  218. // error if setting a flag failed.
  219. func parseReqParam(param string, f *flag.FlagSet, pkgMap map[string]string) error {
  220. if param == "" {
  221. return nil
  222. }
  223. for _, p := range strings.Split(param, ",") {
  224. spec := strings.SplitN(p, "=", 2)
  225. if len(spec) == 1 {
  226. switch spec[0] {
  227. case "allow_delete_body":
  228. if err := f.Set(spec[0], "true"); err != nil {
  229. return fmt.Errorf("cannot set flag %s: %w", p, err)
  230. }
  231. continue
  232. case "allow_merge":
  233. if err := f.Set(spec[0], "true"); err != nil {
  234. return fmt.Errorf("cannot set flag %s: %w", p, err)
  235. }
  236. continue
  237. case "allow_repeated_fields_in_body":
  238. if err := f.Set(spec[0], "true"); err != nil {
  239. return fmt.Errorf("cannot set flag %s: %w", p, err)
  240. }
  241. continue
  242. case "include_package_in_tags":
  243. if err := f.Set(spec[0], "true"); err != nil {
  244. return fmt.Errorf("cannot set flag %s: %w", p, err)
  245. }
  246. continue
  247. }
  248. if err := f.Set(spec[0], ""); err != nil {
  249. return fmt.Errorf("cannot set flag %s: %w", p, err)
  250. }
  251. continue
  252. }
  253. name, value := spec[0], spec[1]
  254. if strings.HasPrefix(name, "M") {
  255. pkgMap[name[1:]] = value
  256. continue
  257. }
  258. if err := f.Set(name, value); err != nil {
  259. return fmt.Errorf("cannot set flag %s: %w", p, err)
  260. }
  261. }
  262. return nil
  263. }