...

Source file src/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/main.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2

     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  }
   291  

View as plain text