github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/protoc-gen-openapiv2/main.go (about) 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 // Variables set by goreleaser at build time 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 // Set the naming strategy either directly from the flag, or via the value of the legacy fqn_for_openapi_name 115 // flag. 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 // parseReqParam parses a CodeGeneratorRequest parameter and adds the 244 // extracted values to the given FlagSet and pkgMap. Returns a non-nil 245 // error if setting a flag failed. 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 }