github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/protoc-gen-openapiv2/defs.bzl (about)

     1  """Generated an open-api spec for a grpc api spec.
     2  
     3  Reads the the api spec in protobuf format and generate an open-api spec.
     4  Optionally applies settings from the grpc-service configuration.
     5  """
     6  
     7  load("@rules_proto//proto:defs.bzl", "ProtoInfo")
     8  
     9  # TODO(yannic): Replace with |proto_common.direct_source_infos| when
    10  # https://github.com/bazelbuild/rules_proto/pull/22 lands.
    11  def _direct_source_infos(proto_info, provided_sources = []):
    12      """Returns sequence of `ProtoFileInfo` for `proto_info`'s direct sources.
    13  
    14      Files that are both in `proto_info`'s direct sources and in
    15      `provided_sources` are skipped. This is useful, e.g., for well-known
    16      protos that are already provided by the Protobuf runtime.
    17  
    18      Args:
    19        proto_info: An instance of `ProtoInfo`.
    20        provided_sources: Optional. A sequence of files to ignore.
    21            Usually, these files are already provided by the
    22            Protocol Buffer runtime (e.g. Well-Known protos).
    23  
    24      Returns: A sequence of `ProtoFileInfo` containing information about
    25          `proto_info`'s direct sources.
    26      """
    27  
    28      source_root = proto_info.proto_source_root
    29      if "." == source_root:
    30          return [struct(file = src, import_path = src.path) for src in proto_info.check_deps_sources.to_list()]
    31  
    32      offset = len(source_root) + 1  # + '/'.
    33  
    34      infos = []
    35      for src in proto_info.check_deps_sources.to_list():
    36          # TODO(yannic): Remove this hack when we drop support for Bazel < 1.0.
    37          local_offset = offset
    38          if src.root.path and not source_root.startswith(src.root.path):
    39              # Before Bazel 1.0, `proto_source_root` wasn't guaranteed to be a
    40              # prefix of `src.path`. This could happend, e.g., if `file` was
    41              # generated (https://github.com/bazelbuild/bazel/issues/9215).
    42              local_offset += len(src.root.path) + 1  # + '/'.
    43          infos.append(struct(file = src, import_path = src.path[local_offset:]))
    44  
    45      return infos
    46  
    47  def _run_proto_gen_openapi(
    48          actions,
    49          proto_info,
    50          target_name,
    51          transitive_proto_srcs,
    52          protoc,
    53          protoc_gen_openapiv2,
    54          single_output,
    55          allow_delete_body,
    56          grpc_api_configuration,
    57          json_names_for_fields,
    58          repeated_path_param_separator,
    59          include_package_in_tags,
    60          fqn_for_openapi_name,
    61          openapi_naming_strategy,
    62          use_go_templates,
    63          go_template_args,
    64          ignore_comments,
    65          remove_internal_comments,
    66          disable_default_errors,
    67          disable_service_tags,
    68          enums_as_ints,
    69          omit_enum_default_value,
    70          output_format,
    71          simple_operation_ids,
    72          proto3_optional_nullable,
    73          openapi_configuration,
    74          generate_unbound_methods,
    75          visibility_restriction_selectors,
    76          use_allof_for_refs):
    77      args = actions.args()
    78  
    79      args.add("--plugin", "protoc-gen-openapiv2=%s" % protoc_gen_openapiv2.path)
    80  
    81      extra_inputs = []
    82      if grpc_api_configuration:
    83          extra_inputs.append(grpc_api_configuration)
    84          args.add("--openapiv2_opt", "grpc_api_configuration=%s" % grpc_api_configuration.path)
    85  
    86      if openapi_configuration:
    87          extra_inputs.append(openapi_configuration)
    88          args.add("--openapiv2_opt", "openapi_configuration=%s" % openapi_configuration.path)
    89  
    90      if not json_names_for_fields:
    91          args.add("--openapiv2_opt", "json_names_for_fields=false")
    92  
    93      if fqn_for_openapi_name:
    94          args.add("--openapiv2_opt", "fqn_for_openapi_name=true")
    95  
    96      if openapi_naming_strategy:
    97          args.add("--openapiv2_opt", "openapi_naming_strategy=%s" % openapi_naming_strategy)
    98  
    99      if generate_unbound_methods:
   100          args.add("--openapiv2_opt", "generate_unbound_methods=true")
   101  
   102      if simple_operation_ids:
   103          args.add("--openapiv2_opt", "simple_operation_ids=true")
   104  
   105      if allow_delete_body:
   106          args.add("--openapiv2_opt", "allow_delete_body=true")
   107  
   108      if include_package_in_tags:
   109          args.add("--openapiv2_opt", "include_package_in_tags=true")
   110  
   111      if use_go_templates:
   112          args.add("--openapiv2_opt", "use_go_templates=true")
   113  
   114      for go_template_arg in go_template_args:
   115          args.add("--openapiv2_opt", "go_template_args=%s" % go_template_arg)
   116  
   117      if ignore_comments:
   118          args.add("--openapiv2_opt", "ignore_comments=true")
   119  
   120      if remove_internal_comments:
   121          args.add("--openapiv2_opt", "remove_internal_comments=true")
   122  
   123      if disable_default_errors:
   124          args.add("--openapiv2_opt", "disable_default_errors=true")
   125  
   126      if disable_service_tags:
   127          args.add("--openapiv2_opt", "disable_service_tags=true")
   128  
   129      if enums_as_ints:
   130          args.add("--openapiv2_opt", "enums_as_ints=true")
   131  
   132      if omit_enum_default_value:
   133          args.add("--openapiv2_opt", "omit_enum_default_value=true")
   134  
   135      if output_format:
   136          args.add("--openapiv2_opt", "output_format=%s" % output_format)
   137  
   138      if proto3_optional_nullable:
   139          args.add("--openapiv2_opt", "proto3_optional_nullable=true")
   140  
   141      for visibility_restriction_selector in visibility_restriction_selectors:
   142          args.add("--openapiv2_opt", "visibility_restriction_selectors=%s" % visibility_restriction_selector)
   143  
   144      if use_allof_for_refs:
   145          args.add("--openapiv2_opt", "use_allof_for_refs=true")
   146  
   147      args.add("--openapiv2_opt", "repeated_path_param_separator=%s" % repeated_path_param_separator)
   148  
   149      proto_file_infos = _direct_source_infos(proto_info)
   150  
   151      # TODO(yannic): Use |proto_info.transitive_descriptor_sets| when
   152      # https://github.com/bazelbuild/bazel/issues/9337 is fixed.
   153      args.add_all(proto_info.transitive_proto_path, format_each = "--proto_path=%s")
   154  
   155      if single_output:
   156          args.add("--openapiv2_opt", "allow_merge=true")
   157          args.add("--openapiv2_opt", "merge_file_name=%s" % target_name)
   158  
   159          openapi_file = actions.declare_file("%s.swagger.json" % target_name)
   160          args.add("--openapiv2_out", openapi_file.dirname)
   161  
   162          args.add_all([f.import_path for f in proto_file_infos])
   163  
   164          actions.run(
   165              executable = protoc,
   166              tools = [protoc_gen_openapiv2],
   167              inputs = depset(
   168                  direct = extra_inputs,
   169                  transitive = [transitive_proto_srcs],
   170              ),
   171              outputs = [openapi_file],
   172              arguments = [args],
   173          )
   174  
   175          return [openapi_file]
   176  
   177      # TODO(yannic): We may be able to generate all files in a single action,
   178      # but that will change at least the semantics of `use_go_template.proto`.
   179      openapi_files = []
   180      for proto_file_info in proto_file_infos:
   181          # TODO(yannic): This probably doesn't work as expected: we only add this
   182          # option after we have seen it, so `.proto` sources that happen to be
   183          # in the list of `.proto` files before `use_go_template.proto` will be
   184          # compiled without this option, and all sources that get compiled after
   185          # `use_go_template.proto` will have this option on.
   186          if proto_file_info.file.basename == "use_go_template.proto":
   187              args.add("--openapiv2_opt", "use_go_templates=true")
   188  
   189          file_name = "%s.swagger.json" % proto_file_info.import_path[:-len(".proto")]
   190          openapi_file = actions.declare_file(
   191              "_virtual_imports/%s/%s" % (target_name, file_name),
   192          )
   193  
   194          file_args = actions.args()
   195  
   196          offset = len(file_name) + 1  # + '/'.
   197          file_args.add("--openapiv2_out", openapi_file.path[:-offset])
   198  
   199          file_args.add(proto_file_info.import_path)
   200  
   201          actions.run(
   202              executable = protoc,
   203              tools = [protoc_gen_openapiv2],
   204              inputs = depset(
   205                  direct = extra_inputs,
   206                  transitive = [transitive_proto_srcs],
   207              ),
   208              outputs = [openapi_file],
   209              arguments = [args, file_args],
   210          )
   211          openapi_files.append(openapi_file)
   212  
   213      return openapi_files
   214  
   215  def _proto_gen_openapi_impl(ctx):
   216      proto = ctx.attr.proto[ProtoInfo]
   217      return [
   218          DefaultInfo(
   219              files = depset(
   220                  _run_proto_gen_openapi(
   221                      actions = ctx.actions,
   222                      proto_info = proto,
   223                      target_name = ctx.attr.name,
   224                      transitive_proto_srcs = depset(
   225                          direct = ctx.files._well_known_protos,
   226                          transitive = [proto.transitive_sources],
   227                      ),
   228                      protoc = ctx.executable._protoc,
   229                      protoc_gen_openapiv2 = ctx.executable._protoc_gen_openapi,
   230                      single_output = ctx.attr.single_output,
   231                      allow_delete_body = ctx.attr.allow_delete_body,
   232                      grpc_api_configuration = ctx.file.grpc_api_configuration,
   233                      json_names_for_fields = ctx.attr.json_names_for_fields,
   234                      repeated_path_param_separator = ctx.attr.repeated_path_param_separator,
   235                      include_package_in_tags = ctx.attr.include_package_in_tags,
   236                      fqn_for_openapi_name = ctx.attr.fqn_for_openapi_name,
   237                      openapi_naming_strategy = ctx.attr.openapi_naming_strategy,
   238                      use_go_templates = ctx.attr.use_go_templates,
   239                      go_template_args = ctx.attr.go_template_args,
   240                      ignore_comments = ctx.attr.ignore_comments,
   241                      remove_internal_comments = ctx.attr.remove_internal_comments,
   242                      disable_default_errors = ctx.attr.disable_default_errors,
   243                      disable_service_tags = ctx.attr.disable_service_tags,
   244                      enums_as_ints = ctx.attr.enums_as_ints,
   245                      omit_enum_default_value = ctx.attr.omit_enum_default_value,
   246                      output_format = ctx.attr.output_format,
   247                      simple_operation_ids = ctx.attr.simple_operation_ids,
   248                      proto3_optional_nullable = ctx.attr.proto3_optional_nullable,
   249                      openapi_configuration = ctx.file.openapi_configuration,
   250                      generate_unbound_methods = ctx.attr.generate_unbound_methods,
   251                      visibility_restriction_selectors = ctx.attr.visibility_restriction_selectors,
   252                      use_allof_for_refs = ctx.attr.use_allof_for_refs,
   253                  ),
   254              ),
   255          ),
   256      ]
   257  
   258  protoc_gen_openapiv2 = rule(
   259      attrs = {
   260          "proto": attr.label(
   261              mandatory = True,
   262              providers = [ProtoInfo],
   263          ),
   264          "single_output": attr.bool(
   265              default = False,
   266              mandatory = False,
   267              doc = "if set, the rule will generate a single OpenAPI file",
   268          ),
   269          "allow_delete_body": attr.bool(
   270              default = False,
   271              mandatory = False,
   272              doc = "unless set, HTTP DELETE methods may not have a body",
   273          ),
   274          "grpc_api_configuration": attr.label(
   275              allow_single_file = True,
   276              mandatory = False,
   277              doc = "path to file which describes the gRPC API Configuration in YAML format",
   278          ),
   279          "json_names_for_fields": attr.bool(
   280              default = True,
   281              mandatory = False,
   282              doc = "if disabled, the original proto name will be used for generating OpenAPI definitions",
   283          ),
   284          "repeated_path_param_separator": attr.string(
   285              default = "csv",
   286              mandatory = False,
   287              values = ["csv", "pipes", "ssv", "tsv"],
   288              doc = "configures how repeated fields should be split." +
   289                    " Allowed values are `csv`, `pipes`, `ssv` and `tsv`",
   290          ),
   291          "include_package_in_tags": attr.bool(
   292              default = False,
   293              mandatory = False,
   294              doc = "if unset, the gRPC service name is added to the `Tags`" +
   295                    " field of each operation. If set and the `package` directive" +
   296                    " is shown in the proto file, the package name will be " +
   297                    " prepended to the service name",
   298          ),
   299          "fqn_for_openapi_name": attr.bool(
   300              default = False,
   301              mandatory = False,
   302              doc = "if set, the object's OpenAPI names will use the fully" +
   303                    " qualified names from the proto definition" +
   304                    " (ie my.package.MyMessage.MyInnerMessage",
   305          ),
   306          "openapi_naming_strategy": attr.string(
   307              default = "",
   308              mandatory = False,
   309              values = ["", "simple", "legacy", "fqn"],
   310              doc = "configures how OpenAPI names are determined." +
   311                    " Allowed values are `` (empty), `simple`, `legacy` and `fqn`." +
   312                    " If unset, either `legacy` or `fqn` are selected, depending" +
   313                    " on the value of the `fqn_for_openapi_name` setting",
   314          ),
   315          "use_go_templates": attr.bool(
   316              default = False,
   317              mandatory = False,
   318              doc = "if set, you can use Go templates in protofile comments",
   319          ),
   320          "go_template_args": attr.string_list(
   321              mandatory = False,
   322              doc = "specify a key value pair as inputs to the Go template of the protofile" +
   323                    " comments. Repeat this option to specify multiple template arguments." +
   324                    " Requires the `use_go_templates` option to be set.",
   325          ),
   326          "ignore_comments": attr.bool(
   327              default = False,
   328              mandatory = False,
   329              doc = "if set, all protofile comments are excluded from output",
   330          ),
   331          "remove_internal_comments": attr.bool(
   332              default = False,
   333              mandatory = False,
   334              doc = "if set, removes all substrings in comments that start with " +
   335                    "`(--` and end with `--)` as specified in " +
   336                    "https://google.aip.dev/192#internal-comments",
   337          ),
   338          "disable_default_errors": attr.bool(
   339              default = False,
   340              mandatory = False,
   341              doc = "if set, disables generation of default errors." +
   342                    " This is useful if you have defined custom error handling",
   343          ),
   344          "disable_service_tags": attr.bool(
   345              default = False,
   346              mandatory = False,
   347              doc = "if set, disables generation of service tags." +
   348                    " This is useful if you do not want to expose the names of your backend grpc services.",
   349          ),
   350          "enums_as_ints": attr.bool(
   351              default = False,
   352              mandatory = False,
   353              doc = "whether to render enum values as integers, as opposed to string values",
   354          ),
   355          "omit_enum_default_value": attr.bool(
   356              default = False,
   357              mandatory = False,
   358              doc = "if set, omit default enum value",
   359          ),
   360          "output_format": attr.string(
   361              default = "json",
   362              mandatory = False,
   363              values = ["json", "yaml"],
   364              doc = "output content format. Allowed values are: `json`, `yaml`",
   365          ),
   366          "simple_operation_ids": attr.bool(
   367              default = False,
   368              mandatory = False,
   369              doc = "whether to remove the service prefix in the operationID" +
   370                    " generation. Can introduce duplicate operationIDs, use with caution.",
   371          ),
   372          "proto3_optional_nullable": attr.bool(
   373              default = False,
   374              mandatory = False,
   375              doc = "whether Proto3 Optional fields should be marked as x-nullable",
   376          ),
   377          "openapi_configuration": attr.label(
   378              allow_single_file = True,
   379              mandatory = False,
   380              doc = "path to file which describes the OpenAPI Configuration in YAML format",
   381          ),
   382          "generate_unbound_methods": attr.bool(
   383              default = False,
   384              mandatory = False,
   385              doc = "generate swagger metadata even for RPC methods that have" +
   386                    " no HttpRule annotation",
   387          ),
   388          "visibility_restriction_selectors": attr.string_list(
   389              mandatory = False,
   390              doc = "list of `google.api.VisibilityRule` visibility labels to include" +
   391                    " in the generated output when a visibility annotation is defined." +
   392                    " Repeat this option to supply multiple values. Elements without" +
   393                    " visibility annotations are unaffected by this setting.",
   394          ),
   395          "use_allof_for_refs": attr.bool(
   396              default = False,
   397              mandatory = False,
   398              doc = "if set, will use allOf as container for $ref to preserve" +
   399                    " same-level properties.",
   400          ),
   401          "_protoc": attr.label(
   402              default = "@com_google_protobuf//:protoc",
   403              executable = True,
   404              cfg = "exec",
   405          ),
   406          "_well_known_protos": attr.label(
   407              default = "@com_google_protobuf//:well_known_type_protos",
   408              allow_files = True,
   409          ),
   410          "_protoc_gen_openapi": attr.label(
   411              default = Label("//protoc-gen-openapiv2:protoc-gen-openapiv2"),
   412              executable = True,
   413              cfg = "exec",
   414          ),
   415      },
   416      implementation = _proto_gen_openapi_impl,
   417  )