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