...

Text file src/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/defs.bzl

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

     1"""Generated an open-api spec for a grpc api spec.
     2
     3Reads the the api spec in protobuf format and generate an open-api spec.
     4Optionally applies settings from the grpc-service configuration.
     5"""
     6
     7load("@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.
    11def _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
    47def _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
   215def _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
   258protoc_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)

View as plain text