| """Generated an open-api spec for a grpc api spec. |
| |
| Reads the the api spec in protobuf format and generate an open-api spec. |
| Optionally applies settings from the grpc-service configuration. |
| """ |
| |
| load("@rules_proto//proto:defs.bzl", "ProtoInfo") |
| |
| # TODO(yannic): Replace with |proto_common.direct_source_infos| when |
| # https://github.com/bazelbuild/rules_proto/pull/22 lands. |
| def _direct_source_infos(proto_info, provided_sources = []): |
| """Returns sequence of `ProtoFileInfo` for `proto_info`'s direct sources. |
| |
| Files that are both in `proto_info`'s direct sources and in |
| `provided_sources` are skipped. This is useful, e.g., for well-known |
| protos that are already provided by the Protobuf runtime. |
| |
| Args: |
| proto_info: An instance of `ProtoInfo`. |
| provided_sources: Optional. A sequence of files to ignore. |
| Usually, these files are already provided by the |
| Protocol Buffer runtime (e.g. Well-Known protos). |
| |
| Returns: A sequence of `ProtoFileInfo` containing information about |
| `proto_info`'s direct sources. |
| """ |
| |
| source_root = proto_info.proto_source_root |
| if "." == source_root: |
| return [struct(file = src, import_path = src.path) for src in proto_info.direct_sources] |
| |
| offset = len(source_root) + 1 # + '/'. |
| |
| infos = [] |
| for src in proto_info.direct_sources: |
| # TODO(yannic): Remove this hack when we drop support for Bazel < 1.0. |
| local_offset = offset |
| if src.root.path and not source_root.startswith(src.root.path): |
| # Before Bazel 1.0, `proto_source_root` wasn't guaranteed to be a |
| # prefix of `src.path`. This could happend, e.g., if `file` was |
| # generated (https://github.com/bazelbuild/bazel/issues/9215). |
| local_offset += len(src.root.path) + 1 # + '/'. |
| infos.append(struct(file = src, import_path = src.path[local_offset:])) |
| |
| return infos |
| |
| def _run_proto_gen_swagger( |
| actions, |
| proto_info, |
| target_name, |
| transitive_proto_srcs, |
| protoc, |
| protoc_gen_swagger, |
| grpc_api_configuration, |
| single_output, |
| json_names_for_fields): |
| args = actions.args() |
| |
| args.add("--plugin", "protoc-gen-swagger=%s" % protoc_gen_swagger.path) |
| |
| args.add("--swagger_opt", "logtostderr=true") |
| args.add("--swagger_opt", "allow_repeated_fields_in_body=true") |
| |
| extra_inputs = [] |
| if grpc_api_configuration: |
| extra_inputs.append(grpc_api_configuration) |
| args.add("--swagger_opt", "grpc_api_configuration=%s" % grpc_api_configuration.path) |
| |
| if json_names_for_fields: |
| args.add("--swagger_opt", "json_names_for_fields=true") |
| |
| proto_file_infos = _direct_source_infos(proto_info) |
| |
| # TODO(yannic): Use |proto_info.transitive_descriptor_sets| when |
| # https://github.com/bazelbuild/bazel/issues/9337 is fixed. |
| args.add_all(proto_info.transitive_proto_path, format_each = "--proto_path=%s") |
| |
| if single_output: |
| args.add("--swagger_opt", "allow_merge=true") |
| args.add("--swagger_opt", "merge_file_name=%s" % target_name) |
| |
| swagger_file = actions.declare_file("%s.swagger.json" % target_name) |
| args.add("--swagger_out", swagger_file.dirname) |
| |
| args.add_all([f.import_path for f in proto_file_infos]) |
| |
| actions.run( |
| executable = protoc, |
| tools = [protoc_gen_swagger], |
| inputs = depset( |
| direct = extra_inputs, |
| transitive = [transitive_proto_srcs], |
| ), |
| outputs = [swagger_file], |
| arguments = [args], |
| ) |
| |
| return [swagger_file] |
| |
| # TODO(yannic): We may be able to generate all files in a single action, |
| # but that will change at least the semantics of `use_go_template.proto`. |
| swagger_files = [] |
| for proto_file_info in proto_file_infos: |
| # TODO(yannic): This probably doesn't work as expected: we only add this |
| # option after we have seen it, so `.proto` sources that happen to be |
| # in the list of `.proto` files before `use_go_template.proto` will be |
| # compiled without this option, and all sources that get compiled after |
| # `use_go_template.proto` will have this option on. |
| if proto_file_info.file.basename == "use_go_template.proto": |
| args.add("--swagger_opt", "use_go_templates=true") |
| |
| file_name = "%s.swagger.json" % proto_file_info.import_path[:-len(".proto")] |
| swagger_file = actions.declare_file( |
| "_virtual_imports/%s/%s" % (target_name, file_name), |
| ) |
| |
| file_args = actions.args() |
| |
| offset = len(file_name) + 1 # + '/'. |
| file_args.add("--swagger_out", swagger_file.path[:-offset]) |
| |
| file_args.add(proto_file_info.import_path) |
| |
| actions.run( |
| executable = protoc, |
| tools = [protoc_gen_swagger], |
| inputs = depset( |
| direct = extra_inputs, |
| transitive = [transitive_proto_srcs], |
| ), |
| outputs = [swagger_file], |
| arguments = [args, file_args], |
| ) |
| swagger_files.append(swagger_file) |
| |
| return swagger_files |
| |
| def _proto_gen_swagger_impl(ctx): |
| proto = ctx.attr.proto[ProtoInfo] |
| return [ |
| DefaultInfo( |
| files = depset( |
| _run_proto_gen_swagger( |
| actions = ctx.actions, |
| proto_info = proto, |
| target_name = ctx.attr.name, |
| transitive_proto_srcs = depset( |
| direct = ctx.files._well_known_protos, |
| transitive = [proto.transitive_sources], |
| ), |
| protoc = ctx.executable._protoc, |
| protoc_gen_swagger = ctx.executable._protoc_gen_swagger, |
| grpc_api_configuration = ctx.file.grpc_api_configuration, |
| single_output = ctx.attr.single_output, |
| json_names_for_fields = ctx.attr.json_names_for_fields, |
| ), |
| ), |
| ), |
| ] |
| |
| protoc_gen_swagger = rule( |
| attrs = { |
| "proto": attr.label( |
| mandatory = True, |
| providers = [ProtoInfo], |
| ), |
| "grpc_api_configuration": attr.label( |
| allow_single_file = True, |
| mandatory = False, |
| ), |
| "single_output": attr.bool( |
| default = False, |
| mandatory = False, |
| ), |
| "json_names_for_fields": attr.bool( |
| default = False, |
| mandatory = False, |
| ), |
| "_protoc": attr.label( |
| default = "@com_google_protobuf//:protoc", |
| executable = True, |
| cfg = "host", |
| ), |
| "_well_known_protos": attr.label( |
| default = "@com_google_protobuf//:well_known_protos", |
| allow_files = True, |
| ), |
| "_protoc_gen_swagger": attr.label( |
| default = Label("//protoc-gen-swagger:protoc-gen-swagger"), |
| executable = True, |
| cfg = "host", |
| ), |
| }, |
| implementation = _proto_gen_swagger_impl, |
| ) |