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 )