validate: initial constraints support. (#226)

* Import @com_lyft_protoc_gen_validate for validate.proto annotations.

* Example annotation in address.proto (BindConfig).

* Process optional/required annotations in protodoc.

Signed-off-by: Harvey Tuch <htuch@google.com>
pull/228/head
htuch 7 years ago committed by GitHub
parent 7d9e737e85
commit 29278b52a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 2
      WORKSPACE
  3. 2
      api/BUILD
  4. 4
      api/address.proto
  5. 6
      bazel/api_build_system.bzl
  6. 9
      bazel/repositories.bzl
  7. 2
      ci/do_ci.sh
  8. BIN
      tools/generate_listeners.pyc
  9. 5
      tools/protodoc/BUILD
  10. 34
      tools/protodoc/protodoc.py

1
.gitignore vendored

@ -1,3 +1,4 @@
bazel-* bazel-*
*.pyc
build_docs/ build_docs/
generated/ generated/

@ -42,6 +42,8 @@ git_repository(
commit = "4374be38e9a75ff5957c3922adb155d32086fe14", commit = "4374be38e9a75ff5957c3922adb155d32086fe14",
) )
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
load("@com_lyft_protoc_gen_validate//bazel:go_proto_library.bzl", "go_proto_repositories")
go_proto_repositories(shared=0)
go_rules_dependencies() go_rules_dependencies()
go_register_toolchains() go_register_toolchains()
load("@io_bazel_rules_go//proto:def.bzl", "proto_register_toolchains") load("@io_bazel_rules_go//proto:def.bzl", "proto_register_toolchains")

@ -30,6 +30,7 @@ proto_library(
"@com_google_protobuf//:struct_proto", "@com_google_protobuf//:struct_proto",
"@com_google_protobuf//:timestamp_proto", "@com_google_protobuf//:timestamp_proto",
"@com_google_protobuf//:wrappers_proto", "@com_google_protobuf//:wrappers_proto",
"@com_lyft_protoc_gen_validate//validate:validate_proto",
"@googleapis//:http_api_protos_lib", "@googleapis//:http_api_protos_lib",
], ],
) )
@ -46,6 +47,7 @@ go_grpc_library(
"@com_github_golang_protobuf//ptypes/timestamp:go_default_library", "@com_github_golang_protobuf//ptypes/timestamp:go_default_library",
"@com_github_golang_protobuf//ptypes/wrappers:go_default_library", "@com_github_golang_protobuf//ptypes/wrappers:go_default_library",
"@org_golang_google_genproto//googleapis/api/annotations:go_default_library", "@org_golang_google_genproto//googleapis/api/annotations:go_default_library",
"@com_lyft_protoc_gen_validate//validate:go_default_library",
], ],
) )

@ -4,6 +4,8 @@ package envoy.api.v2;
import "google/protobuf/wrappers.proto"; import "google/protobuf/wrappers.proto";
import "validate/validate.proto";
// protodoc-title: Network related configuration elements // protodoc-title: Network related configuration elements
// [V2-API-DIFF] Addresses now have .proto structure. // [V2-API-DIFF] Addresses now have .proto structure.
@ -37,7 +39,7 @@ message SocketAddress {
message BindConfig { message BindConfig {
// The address to bind to when creating a socket. // The address to bind to when creating a socket.
SocketAddress source_address = 1; SocketAddress source_address = 1 [(validate.rules).message.required = true];
} }
// Addresses specify either a logical or physical address and port, which are // Addresses specify either a logical or physical address and port, which are

@ -17,7 +17,10 @@ def api_py_proto_library(name, srcs = [], deps = [], has_services = 0):
srcs = srcs, srcs = srcs,
default_runtime = "@com_google_protobuf//:protobuf_python", default_runtime = "@com_google_protobuf//:protobuf_python",
protoc = "@com_google_protobuf//:protoc", protoc = "@com_google_protobuf//:protoc",
deps = [_PySuffix(d) for d in deps] + ["@googleapis//:http_api_protos_py"], deps = [_PySuffix(d) for d in deps] + [
"@com_lyft_protoc_gen_validate//validate:validate_py",
"@googleapis//:http_api_protos_py",
],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
@ -35,6 +38,7 @@ def api_proto_library(name, srcs = [], deps = [], has_services = 0, require_py =
"@com_google_protobuf//:timestamp_proto", "@com_google_protobuf//:timestamp_proto",
"@com_google_protobuf//:wrappers_proto", "@com_google_protobuf//:wrappers_proto",
"@googleapis//:http_api_protos_lib", "@googleapis//:http_api_protos_lib",
"@com_lyft_protoc_gen_validate//validate:validate_proto",
], ],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )

@ -1,7 +1,16 @@
GOOGLEAPIS_SHA = "5c6df0cd18c6a429eab739fb711c27f6e1393366" # May 14, 2017 GOOGLEAPIS_SHA = "5c6df0cd18c6a429eab739fb711c27f6e1393366" # May 14, 2017
PROMETHEUS_SHA = "6f3806018612930941127f2a7c6c453ba2c527d2" # Nov 02, 2017 PROMETHEUS_SHA = "6f3806018612930941127f2a7c6c453ba2c527d2" # Nov 02, 2017
PGV_GIT_SHA = "f3332cbd75bb28f711377dfb84761ef0d52eca0f"
PGV_TAR_SHA = "039ffa842eb62495b6aca305a4eb3d6dc3ac1dd056e228fba5e720161ddfb9c1"
def api_dependencies(): def api_dependencies():
native.http_archive(
name = "com_lyft_protoc_gen_validate",
strip_prefix = "protoc-gen-validate-" + PGV_GIT_SHA,
sha256 = PGV_TAR_SHA,
url = "https://github.com/lyft/protoc-gen-validate/archive/" + PGV_GIT_SHA + ".tar.gz",
)
native.new_http_archive( native.new_http_archive(
name = "googleapis", name = "googleapis",
strip_prefix = "googleapis-" + GOOGLEAPIS_SHA, strip_prefix = "googleapis-" + GOOGLEAPIS_SHA,

@ -17,6 +17,6 @@ elif [[ "$1" == "bazel.docs" ]]; then
echo "generating docs..." echo "generating docs..."
./docs/build.sh ./docs/build.sh
else else
echo "Invalid do_ci.sh target. The only valid target is bazel.build." echo "Invalid do_ci.sh target. The only valid targets are bazel.{docs,test}."
exit 1 exit 1
fi fi

Binary file not shown.

@ -3,6 +3,9 @@ licenses(["notice"]) # Apache 2
py_binary( py_binary(
name = "protodoc", name = "protodoc",
srcs = ["protodoc.py"], srcs = ["protodoc.py"],
deps = ["@com_google_protobuf//:protobuf_python"], deps = [
"@com_lyft_protoc_gen_validate//validate:validate_py",
"@com_google_protobuf//:protobuf_python",
],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )

@ -10,6 +10,7 @@ import sys
import re import re
from google.protobuf.compiler import plugin_pb2 from google.protobuf.compiler import plugin_pb2
from validate import validate_pb2
# Namespace prefix for Envoy APIs. # Namespace prefix for Envoy APIs.
ENVOY_API_NAMESPACE_PREFIX = '.envoy.api.v2.' ENVOY_API_NAMESPACE_PREFIX = '.envoy.api.v2.'
@ -23,6 +24,7 @@ UNICODE_INVISIBLE_SEPARATOR = u'\u2063'
# Page/section titles with special prefixes in the proto comments # Page/section titles with special prefixes in the proto comments
DOC_TITLE_REGEX = 'protodoc-title:\s([^\n]+)\n\n?' DOC_TITLE_REGEX = 'protodoc-title:\s([^\n]+)\n\n?'
class ProtodocError(Exception): class ProtodocError(Exception):
"""Base error class for the protodoc module.""" """Base error class for the protodoc module."""
@ -151,9 +153,11 @@ def FormatHeaderFromFile(style, file_level_comment, alt):
m = re.search(DOC_TITLE_REGEX, file_level_comment) m = re.search(DOC_TITLE_REGEX, file_level_comment)
if m: if m:
# remove title hint and any new lines that follow # remove title hint and any new lines that follow
return FormatHeader(style, m.group(1)), re.sub(DOC_TITLE_REGEX, '', file_level_comment) return FormatHeader(style, m.group(1)), re.sub(DOC_TITLE_REGEX, '',
file_level_comment)
return FormatHeader(style, alt), file_level_comment return FormatHeader(style, alt), file_level_comment
def FormatFieldTypeAsJson(type_context, field): def FormatFieldTypeAsJson(type_context, field):
"""Format FieldDescriptorProto.Type as a pseudo-JSON string. """Format FieldDescriptorProto.Type as a pseudo-JSON string.
@ -242,8 +246,14 @@ def FormatFieldType(type_context, field):
field.TYPE_DOUBLE: 'double', field.TYPE_DOUBLE: 'double',
field.TYPE_FLOAT: 'float', field.TYPE_FLOAT: 'float',
field.TYPE_INT32: 'int32', field.TYPE_INT32: 'int32',
field.TYPE_SFIXED32: 'int32',
field.TYPE_SINT32: 'int32',
field.TYPE_FIXED32: 'uint32',
field.TYPE_UINT32: 'uint32', field.TYPE_UINT32: 'uint32',
field.TYPE_INT64: 'int64', field.TYPE_INT64: 'int64',
field.TYPE_SFIXED64: 'int64',
field.TYPE_SINT64: 'int64',
field.TYPE_FIXED64: 'uint64',
field.TYPE_UINT64: 'uint64', field.TYPE_UINT64: 'uint64',
field.TYPE_BOOL: 'bool', field.TYPE_BOOL: 'bool',
field.TYPE_STRING: 'string', field.TYPE_STRING: 'string',
@ -301,8 +311,15 @@ def FormatFieldAsDefinitionListItem(type_context, field):
else: else:
oneof_comment = '' oneof_comment = ''
anchor = FormatAnchor(FieldCrossRefLabel(type_context.name)) anchor = FormatAnchor(FieldCrossRefLabel(type_context.name))
comment = '(%s) ' % FormatFieldType( annotations = []
type_context, field) + type_context.LeadingCommentPathLookup() if field.options.HasExtension(validate_pb2.rules):
rule = field.options.Extensions[validate_pb2.rules]
if rule.HasField('message'):
if rule.message.required:
annotations.append('*REQUIRED*')
comment = '(%s) ' % ', '.join(
[FormatFieldType(type_context, field)] + annotations
) + type_context.LeadingCommentPathLookup()
return anchor + field.name + '\n' + MapLines( return anchor + field.name + '\n' + MapLines(
functools.partial(Indent, 2), comment + oneof_comment) functools.partial(Indent, 2), comment + oneof_comment)
@ -345,7 +362,8 @@ def FormatMessage(type_context, msg):
map( map(
functools.partial(FormatFieldType, type_context), functools.partial(FormatFieldType, type_context),
nested_msg.field)) nested_msg.field))
for nested_msg in msg.nested_type if nested_msg.options.map_entry for nested_msg in msg.nested_type
if nested_msg.options.map_entry
} }
nested_msgs = '\n'.join( nested_msgs = '\n'.join(
FormatMessage( FormatMessage(
@ -425,16 +443,16 @@ def GenerateRst(proto_file):
source_code_info = SourceCodeInfo(proto_file.source_code_info) source_code_info = SourceCodeInfo(proto_file.source_code_info)
# Find the earliest detached comment, attribute it to file level. # Find the earliest detached comment, attribute it to file level.
# Also extract file level titles if any. # Also extract file level titles if any.
header, comment = FormatHeaderFromFile('=', source_code_info.file_level_comment, header, comment = FormatHeaderFromFile(
proto_file.name) '=', source_code_info.file_level_comment, proto_file.name)
msgs = '\n'.join( msgs = '\n'.join(
FormatMessage(TypeContext(source_code_info, [4, index], msg.name), msg) FormatMessage(TypeContext(source_code_info, [4, index], msg.name), msg)
for index, msg in enumerate(proto_file.message_type)) for index, msg in enumerate(proto_file.message_type))
enums = '\n'.join( enums = '\n'.join(
FormatEnum(TypeContext(source_code_info, [5, index], enum.name), enum) FormatEnum(TypeContext(source_code_info, [5, index], enum.name), enum)
for index, enum in enumerate(proto_file.enum_type)) for index, enum in enumerate(proto_file.enum_type))
#debug_proto = FormatProtoAsBlockComment(proto_file.source_code_info) debug_proto = FormatProtoAsBlockComment(proto_file)
return header + comment + msgs + enums #+ debug_proto return header + comment + msgs + enums #+ debug_proto
if __name__ == '__main__': if __name__ == '__main__':

Loading…
Cancel
Save