diff --git a/.gitignore b/.gitignore index 08bc2485..1ff3954b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bazel-* +*.pyc build_docs/ generated/ diff --git a/WORKSPACE b/WORKSPACE index 6e03ec7d..d93f6054 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -42,6 +42,8 @@ git_repository( commit = "4374be38e9a75ff5957c3922adb155d32086fe14", ) 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_register_toolchains() load("@io_bazel_rules_go//proto:def.bzl", "proto_register_toolchains") diff --git a/api/BUILD b/api/BUILD index ff151704..4c3337fe 100644 --- a/api/BUILD +++ b/api/BUILD @@ -30,6 +30,7 @@ proto_library( "@com_google_protobuf//:struct_proto", "@com_google_protobuf//:timestamp_proto", "@com_google_protobuf//:wrappers_proto", + "@com_lyft_protoc_gen_validate//validate:validate_proto", "@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/wrappers:go_default_library", "@org_golang_google_genproto//googleapis/api/annotations:go_default_library", + "@com_lyft_protoc_gen_validate//validate:go_default_library", ], ) diff --git a/api/address.proto b/api/address.proto index 8879f11a..3d6ed30b 100644 --- a/api/address.proto +++ b/api/address.proto @@ -4,6 +4,8 @@ package envoy.api.v2; import "google/protobuf/wrappers.proto"; +import "validate/validate.proto"; + // protodoc-title: Network related configuration elements // [V2-API-DIFF] Addresses now have .proto structure. @@ -37,7 +39,7 @@ message SocketAddress { message BindConfig { // 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 diff --git a/bazel/api_build_system.bzl b/bazel/api_build_system.bzl index d53fd96b..f363486e 100644 --- a/bazel/api_build_system.bzl +++ b/bazel/api_build_system.bzl @@ -17,7 +17,10 @@ def api_py_proto_library(name, srcs = [], deps = [], has_services = 0): srcs = srcs, default_runtime = "@com_google_protobuf//:protobuf_python", 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"], ) @@ -35,6 +38,7 @@ def api_proto_library(name, srcs = [], deps = [], has_services = 0, require_py = "@com_google_protobuf//:timestamp_proto", "@com_google_protobuf//:wrappers_proto", "@googleapis//:http_api_protos_lib", + "@com_lyft_protoc_gen_validate//validate:validate_proto", ], visibility = ["//visibility:public"], ) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 79c1a35f..c921dcc1 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -1,7 +1,16 @@ GOOGLEAPIS_SHA = "5c6df0cd18c6a429eab739fb711c27f6e1393366" # May 14, 2017 PROMETHEUS_SHA = "6f3806018612930941127f2a7c6c453ba2c527d2" # Nov 02, 2017 +PGV_GIT_SHA = "f3332cbd75bb28f711377dfb84761ef0d52eca0f" +PGV_TAR_SHA = "039ffa842eb62495b6aca305a4eb3d6dc3ac1dd056e228fba5e720161ddfb9c1" + 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( name = "googleapis", strip_prefix = "googleapis-" + GOOGLEAPIS_SHA, diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 3ee9f971..882b2302 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -17,6 +17,6 @@ elif [[ "$1" == "bazel.docs" ]]; then echo "generating docs..." ./docs/build.sh 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 fi diff --git a/tools/generate_listeners.pyc b/tools/generate_listeners.pyc deleted file mode 100644 index 97e0b4da..00000000 Binary files a/tools/generate_listeners.pyc and /dev/null differ diff --git a/tools/protodoc/BUILD b/tools/protodoc/BUILD index eed8f236..c9b6a356 100644 --- a/tools/protodoc/BUILD +++ b/tools/protodoc/BUILD @@ -3,6 +3,9 @@ licenses(["notice"]) # Apache 2 py_binary( name = "protodoc", 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"], ) diff --git a/tools/protodoc/protodoc.py b/tools/protodoc/protodoc.py index 81f33f86..a0f9dd0c 100755 --- a/tools/protodoc/protodoc.py +++ b/tools/protodoc/protodoc.py @@ -10,6 +10,7 @@ import sys import re from google.protobuf.compiler import plugin_pb2 +from validate import validate_pb2 # Namespace prefix for Envoy APIs. 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 DOC_TITLE_REGEX = 'protodoc-title:\s([^\n]+)\n\n?' + class ProtodocError(Exception): """Base error class for the protodoc module.""" @@ -137,7 +139,7 @@ def FormatHeader(style, text): return '%s\n%s\n\n' % (text, style * len(text)) -def FormatHeaderFromFile(style, file_level_comment, alt): +def FormatHeaderFromFile(style, file_level_comment, alt): """Format RST header based on special file level title Args: @@ -151,9 +153,11 @@ def FormatHeaderFromFile(style, file_level_comment, alt): m = re.search(DOC_TITLE_REGEX, file_level_comment) if m: # 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 + def FormatFieldTypeAsJson(type_context, field): """Format FieldDescriptorProto.Type as a pseudo-JSON string. @@ -242,8 +246,14 @@ def FormatFieldType(type_context, field): field.TYPE_DOUBLE: 'double', field.TYPE_FLOAT: 'float', field.TYPE_INT32: 'int32', + field.TYPE_SFIXED32: 'int32', + field.TYPE_SINT32: 'int32', + field.TYPE_FIXED32: 'uint32', field.TYPE_UINT32: 'uint32', field.TYPE_INT64: 'int64', + field.TYPE_SFIXED64: 'int64', + field.TYPE_SINT64: 'int64', + field.TYPE_FIXED64: 'uint64', field.TYPE_UINT64: 'uint64', field.TYPE_BOOL: 'bool', field.TYPE_STRING: 'string', @@ -301,8 +311,15 @@ def FormatFieldAsDefinitionListItem(type_context, field): else: oneof_comment = '' anchor = FormatAnchor(FieldCrossRefLabel(type_context.name)) - comment = '(%s) ' % FormatFieldType( - type_context, field) + type_context.LeadingCommentPathLookup() + annotations = [] + 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( functools.partial(Indent, 2), comment + oneof_comment) @@ -345,7 +362,8 @@ def FormatMessage(type_context, msg): map( functools.partial(FormatFieldType, type_context), 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( FormatMessage( @@ -425,16 +443,16 @@ def GenerateRst(proto_file): source_code_info = SourceCodeInfo(proto_file.source_code_info) # Find the earliest detached comment, attribute it to file level. # Also extract file level titles if any. - header, comment = FormatHeaderFromFile('=', source_code_info.file_level_comment, - proto_file.name) + header, comment = FormatHeaderFromFile( + '=', source_code_info.file_level_comment, proto_file.name) msgs = '\n'.join( FormatMessage(TypeContext(source_code_info, [4, index], msg.name), msg) for index, msg in enumerate(proto_file.message_type)) enums = '\n'.join( FormatEnum(TypeContext(source_code_info, [5, index], enum.name), enum) for index, enum in enumerate(proto_file.enum_type)) - #debug_proto = FormatProtoAsBlockComment(proto_file.source_code_info) - return header + comment + msgs + enums #+ debug_proto + debug_proto = FormatProtoAsBlockComment(proto_file) + return header + comment + msgs + enums #+ debug_proto if __name__ == '__main__':