From 69478db4f28811d7742f450545aa690a10f9c0f1 Mon Sep 17 00:00:00 2001 From: Sri Krishna Date: Fri, 13 Jun 2025 17:04:28 +0530 Subject: [PATCH 1/2] Add implicit `IGNORE_IF_UNPOPULATED` for `MessageOneofRule` fields Signed-off-by: Sri Krishna --- Makefile | 2 +- buf.lock | 8 +++---- buf.yaml | 4 ++-- .../conformance/cases/messages_pb2.py | 20 +++++++++++++++- .../conformance/cases/messages_pb2.pyi | 24 +++++++++++++++++++ protovalidate/internal/rules.py | 10 +++++++- 6 files changed, 59 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index d4e613ed..5eae2af7 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ ADD_LICENSE_HEADER := $(BIN)/license-header \ --copyright-holder "Buf Technologies, Inc." \ --year-range "2023-2025" # This version should be kept in sync with the version in buf.yaml -PROTOVALIDATE_VERSION ?= v0.13.1 +PROTOVALIDATE_VERSION ?= v0.13.3 # Version of the cel-spec that this implementation is conformant with # This should be kept in sync with the version in format_test.py CEL_SPEC_VERSION ?= v0.24.0 diff --git a/buf.lock b/buf.lock index 588649e3..1abc9fc8 100644 --- a/buf.lock +++ b/buf.lock @@ -2,8 +2,8 @@ version: v2 deps: - name: buf.build/bufbuild/protovalidate - commit: c6451e2c05a64a659a0835011a72516c - digest: b5:826b8be86c1bc691afb751fb33c513cb90a9b9d03699b366a7812737e72fe06132d13ca4b2f5fdb9c5784594b0f65b20b850003349a556e8f020b84ce6787bd1 + commit: 9f2d3c737feb481a83375159c0733275 + digest: b5:19d3b83f7df2d284ff5935f4622d7f27e7464a93c210edb536e92a52bcc69b2a18da1312e96b5461601eba7b3764d5e90321bd62e6966870e7dbc2e4dedd98d6 - name: buf.build/bufbuild/protovalidate-testing - commit: 33dd956ff33d4a4085b2fa05cd7a14b8 - digest: b5:d142307939a7b8486fe7698e7d244c7e9c4a9c7ef812c97b46e8a7cc362f9c666f94f84dd66ad72fdb272ec23ed0cc415deed365318efdc9b24b92ef4e9ec415 + commit: 64cb206b26e840bcaacabae7b19bdf33 + digest: b5:f0abbad11668a70061ec8870c8c6b71f56e033ff97e0c22264648504e9a9c9f636304a01e27485eeacf7256f9bf25abc48503836948609f537a0fbc136be839b diff --git a/buf.yaml b/buf.yaml index 608c755f..9f4a0162 100644 --- a/buf.yaml +++ b/buf.yaml @@ -2,8 +2,8 @@ version: v2 modules: - path: proto deps: - - buf.build/bufbuild/protovalidate:v0.13.1 - - buf.build/bufbuild/protovalidate-testing:v0.13.1 + - buf.build/bufbuild/protovalidate:v0.13.3 + - buf.build/bufbuild/protovalidate-testing:v0.13.3 lint: use: - STANDARD diff --git a/gen/buf/validate/conformance/cases/messages_pb2.py b/gen/buf/validate/conformance/cases/messages_pb2.py index 4074d6e7..b54ad283 100644 --- a/gen/buf/validate/conformance/cases/messages_pb2.py +++ b/gen/buf/validate/conformance/cases/messages_pb2.py @@ -40,7 +40,7 @@ from buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n-buf/validate/conformance/cases/messages.proto\x12\x1e\x62uf.validate.conformance.cases\x1a\x38\x62uf/validate/conformance/cases/other_package/embed.proto\x1a\x1b\x62uf/validate/validate.proto\"l\n\x07TestMsg\x12 \n\x05\x63onst\x18\x01 \x01(\tB\n\xbaH\x07r\x05\n\x03\x66ooR\x05\x63onst\x12?\n\x06nested\x18\x02 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgR\x06nested\"_\n\x0bMessageNone\x12\x45\n\x03val\x18\x01 \x01(\x0b\x32\x33.buf.validate.conformance.cases.MessageNone.NoneMsgR\x03val\x1a\t\n\x07NoneMsg\"3\n\x0fMessageDisabled\x12\x19\n\x03val\x18\x01 \x01(\x04\x42\x07\xbaH\x04\x32\x02 {R\x03val:\x05\xbaH\x02\x08\x01\"D\n\x07Message\x12\x39\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgR\x03val\"\\\n\x13MessageCrossPackage\x12\x45\n\x03val\x18\x01 \x01(\x0b\x32\x33.buf.validate.conformance.cases.other_package.EmbedR\x03val\"P\n\x0bMessageSkip\x12\x41\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xd8\x01\x03R\x03val\"T\n\x0fMessageRequired\x12\x41\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01R\x03val\"l\n\x1aMessageRequiredButOptional\x12\x46\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01H\x00R\x03val\x88\x01\x01\x42\x06\n\x04_val\"i\n\x14MessageRequiredOneof\x12\x43\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01H\x00R\x03valB\x0c\n\x03one\x12\x05\xbaH\x02\x08\x01\"\x15\n\x13MessageWith3dInside\"g\n\x17MessageOneofSingleField\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x10\xbaH\r\"\x0b\n\tstr_field\"q\n\x1fMessageOneofSingleFieldRequired\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x12\xbaH\x0f\"\r\n\tstr_field\x10\x01\"v\n\x1aMessageOneofMultipleFields\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x1c\xbaH\x19\"\x17\n\tstr_field\n\nbool_field\"\x80\x01\n\"MessageOneofMultipleFieldsRequired\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x1e\xbaH\x1b\"\x19\n\tstr_field\n\nbool_field\x10\x01\"\xb5\x01\n MessageOneofMultipleSharedFields\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField\x12\x1b\n\tint_field\x18\x03 \x01(\x05R\x08intField:8\xbaH5\"\x19\n\tstr_field\n\nbool_field\x10\x01\"\x18\n\tstr_field\n\tint_field\x10\x01\"G\n\x1cMessageOneofUnknownFieldName\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField:\n\xbaH\x07\"\x05\n\x03xxx\"\x81\x01\n\x1aMessageOneofDuplicateField\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\'\xbaH$\"\"\n\tstr_field\n\nbool_field\n\tstr_field\"[\n\x16MessageOneofZeroFields\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x05\xbaH\x02\"\x00\"h\n\x19MessageOneofUnsatisfiable\x12\x0c\n\x01\x61\x18\x01 \x01(\x08R\x01\x61\x12\x0c\n\x01\x62\x18\x02 \x01(\x08R\x01\x62\x12\x0c\n\x01\x63\x18\x03 \x01(\x08R\x01\x63:!\xbaH\x1e\"\x08\n\x01\x61\n\x01\x62\x10\x01\"\x08\n\x01\x62\n\x01\x63\x10\x01\"\x08\n\x01\x61\n\x01\x63\x10\x01\x42\xcf\x01\n\"com.buf.validate.conformance.casesB\rMessagesProtoP\x01\xa2\x02\x04\x42VCC\xaa\x02\x1e\x42uf.Validate.Conformance.Cases\xca\x02\x1e\x42uf\\Validate\\Conformance\\Cases\xe2\x02*Buf\\Validate\\Conformance\\Cases\\GPBMetadata\xea\x02!Buf::Validate::Conformance::Casesb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n-buf/validate/conformance/cases/messages.proto\x12\x1e\x62uf.validate.conformance.cases\x1a\x38\x62uf/validate/conformance/cases/other_package/embed.proto\x1a\x1b\x62uf/validate/validate.proto\"l\n\x07TestMsg\x12 \n\x05\x63onst\x18\x01 \x01(\tB\n\xbaH\x07r\x05\n\x03\x66ooR\x05\x63onst\x12?\n\x06nested\x18\x02 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgR\x06nested\"_\n\x0bMessageNone\x12\x45\n\x03val\x18\x01 \x01(\x0b\x32\x33.buf.validate.conformance.cases.MessageNone.NoneMsgR\x03val\x1a\t\n\x07NoneMsg\"3\n\x0fMessageDisabled\x12\x19\n\x03val\x18\x01 \x01(\x04\x42\x07\xbaH\x04\x32\x02 {R\x03val:\x05\xbaH\x02\x08\x01\"D\n\x07Message\x12\x39\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgR\x03val\"\\\n\x13MessageCrossPackage\x12\x45\n\x03val\x18\x01 \x01(\x0b\x32\x33.buf.validate.conformance.cases.other_package.EmbedR\x03val\"P\n\x0bMessageSkip\x12\x41\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xd8\x01\x03R\x03val\"T\n\x0fMessageRequired\x12\x41\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01R\x03val\"l\n\x1aMessageRequiredButOptional\x12\x46\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01H\x00R\x03val\x88\x01\x01\x42\x06\n\x04_val\"i\n\x14MessageRequiredOneof\x12\x43\n\x03val\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01H\x00R\x03valB\x0c\n\x03one\x12\x05\xbaH\x02\x08\x01\"\x15\n\x13MessageWith3dInside\"g\n\x17MessageOneofSingleField\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x10\xbaH\r\"\x0b\n\tstr_field\"q\n\x1fMessageOneofSingleFieldRequired\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x12\xbaH\x0f\"\r\n\tstr_field\x10\x01\"v\n\x1aMessageOneofMultipleFields\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x1c\xbaH\x19\"\x17\n\tstr_field\n\nbool_field\"\x80\x01\n\"MessageOneofMultipleFieldsRequired\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x1e\xbaH\x1b\"\x19\n\tstr_field\n\nbool_field\x10\x01\"\xb5\x01\n MessageOneofMultipleSharedFields\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField\x12\x1b\n\tint_field\x18\x03 \x01(\x05R\x08intField:8\xbaH5\"\x19\n\tstr_field\n\nbool_field\x10\x01\"\x18\n\tstr_field\n\tint_field\x10\x01\"G\n\x1cMessageOneofUnknownFieldName\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField:\n\xbaH\x07\"\x05\n\x03xxx\"\x81\x01\n\x1aMessageOneofDuplicateField\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\'\xbaH$\"\"\n\tstr_field\n\nbool_field\n\tstr_field\"[\n\x16MessageOneofZeroFields\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x05\xbaH\x02\"\x00\"h\n\x19MessageOneofUnsatisfiable\x12\x0c\n\x01\x61\x18\x01 \x01(\x08R\x01\x61\x12\x0c\n\x01\x62\x18\x02 \x01(\x08R\x01\x62\x12\x0c\n\x01\x63\x18\x03 \x01(\x08R\x01\x63:!\xbaH\x1e\"\x08\n\x01\x61\n\x01\x62\x10\x01\"\x08\n\x01\x62\n\x01\x63\x10\x01\"\x08\n\x01\x61\n\x01\x63\x10\x01\"\x82\x01\n\x1dMessageOneofIgnoreUnpopulated\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12&\n\nbool_field\x18\x02 \x01(\x08\x42\x07\xbaH\x04j\x02\x08\x01R\tboolField:\x1c\xbaH\x19\"\x17\n\tstr_field\n\nbool_field\"\x8c\x01\n%MessageOneofIgnoreUnpopulatedRequired\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField\x12&\n\nbool_field\x18\x02 \x01(\x08\x42\x07\xbaH\x04j\x02\x08\x01R\tboolField:\x1e\xbaH\x1b\"\x19\n\tstr_field\n\nbool_field\x10\x01\"\xa7\x01\n\x1aMessageOneofIgnoreOverride\x12L\n\tmsg_field\x18\x01 \x01(\x0b\x32\'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xd8\x01\x03R\x08msgField\x12\x1d\n\nbool_field\x18\x02 \x01(\x08R\tboolField:\x1c\xbaH\x19\"\x17\n\tmsg_field\n\nbool_fieldB\xcf\x01\n\"com.buf.validate.conformance.casesB\rMessagesProtoP\x01\xa2\x02\x04\x42VCC\xaa\x02\x1e\x42uf.Validate.Conformance.Cases\xca\x02\x1e\x42uf\\Validate\\Conformance\\Cases\xe2\x02*Buf\\Validate\\Conformance\\Cases\\GPBMetadata\xea\x02!Buf::Validate::Conformance::Casesb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -82,6 +82,18 @@ _globals['_MESSAGEONEOFZEROFIELDS']._serialized_options = b'\272H\002\"\000' _globals['_MESSAGEONEOFUNSATISFIABLE']._loaded_options = None _globals['_MESSAGEONEOFUNSATISFIABLE']._serialized_options = b'\272H\036\"\010\n\001a\n\001b\020\001\"\010\n\001b\n\001c\020\001\"\010\n\001a\n\001c\020\001' + _globals['_MESSAGEONEOFIGNOREUNPOPULATED'].fields_by_name['bool_field']._loaded_options = None + _globals['_MESSAGEONEOFIGNOREUNPOPULATED'].fields_by_name['bool_field']._serialized_options = b'\272H\004j\002\010\001' + _globals['_MESSAGEONEOFIGNOREUNPOPULATED']._loaded_options = None + _globals['_MESSAGEONEOFIGNOREUNPOPULATED']._serialized_options = b'\272H\031\"\027\n\tstr_field\n\nbool_field' + _globals['_MESSAGEONEOFIGNOREUNPOPULATEDREQUIRED'].fields_by_name['bool_field']._loaded_options = None + _globals['_MESSAGEONEOFIGNOREUNPOPULATEDREQUIRED'].fields_by_name['bool_field']._serialized_options = b'\272H\004j\002\010\001' + _globals['_MESSAGEONEOFIGNOREUNPOPULATEDREQUIRED']._loaded_options = None + _globals['_MESSAGEONEOFIGNOREUNPOPULATEDREQUIRED']._serialized_options = b'\272H\033\"\031\n\tstr_field\n\nbool_field\020\001' + _globals['_MESSAGEONEOFIGNOREOVERRIDE'].fields_by_name['msg_field']._loaded_options = None + _globals['_MESSAGEONEOFIGNOREOVERRIDE'].fields_by_name['msg_field']._serialized_options = b'\272H\003\330\001\003' + _globals['_MESSAGEONEOFIGNOREOVERRIDE']._loaded_options = None + _globals['_MESSAGEONEOFIGNOREOVERRIDE']._serialized_options = b'\272H\031\"\027\n\tmsg_field\n\nbool_field' _globals['_TESTMSG']._serialized_start=168 _globals['_TESTMSG']._serialized_end=276 _globals['_MESSAGENONE']._serialized_start=278 @@ -122,4 +134,10 @@ _globals['_MESSAGEONEOFZEROFIELDS']._serialized_end=1951 _globals['_MESSAGEONEOFUNSATISFIABLE']._serialized_start=1953 _globals['_MESSAGEONEOFUNSATISFIABLE']._serialized_end=2057 + _globals['_MESSAGEONEOFIGNOREUNPOPULATED']._serialized_start=2060 + _globals['_MESSAGEONEOFIGNOREUNPOPULATED']._serialized_end=2190 + _globals['_MESSAGEONEOFIGNOREUNPOPULATEDREQUIRED']._serialized_start=2193 + _globals['_MESSAGEONEOFIGNOREUNPOPULATEDREQUIRED']._serialized_end=2333 + _globals['_MESSAGEONEOFIGNOREOVERRIDE']._serialized_start=2336 + _globals['_MESSAGEONEOFIGNOREOVERRIDE']._serialized_end=2503 # @@protoc_insertion_point(module_scope) diff --git a/gen/buf/validate/conformance/cases/messages_pb2.pyi b/gen/buf/validate/conformance/cases/messages_pb2.pyi index 618c724b..987257ff 100644 --- a/gen/buf/validate/conformance/cases/messages_pb2.pyi +++ b/gen/buf/validate/conformance/cases/messages_pb2.pyi @@ -157,3 +157,27 @@ class MessageOneofUnsatisfiable(_message.Message): b: bool c: bool def __init__(self, a: bool = ..., b: bool = ..., c: bool = ...) -> None: ... + +class MessageOneofIgnoreUnpopulated(_message.Message): + __slots__ = ("str_field", "bool_field") + STR_FIELD_FIELD_NUMBER: _ClassVar[int] + BOOL_FIELD_FIELD_NUMBER: _ClassVar[int] + str_field: str + bool_field: bool + def __init__(self, str_field: _Optional[str] = ..., bool_field: bool = ...) -> None: ... + +class MessageOneofIgnoreUnpopulatedRequired(_message.Message): + __slots__ = ("str_field", "bool_field") + STR_FIELD_FIELD_NUMBER: _ClassVar[int] + BOOL_FIELD_FIELD_NUMBER: _ClassVar[int] + str_field: str + bool_field: bool + def __init__(self, str_field: _Optional[str] = ..., bool_field: bool = ...) -> None: ... + +class MessageOneofIgnoreOverride(_message.Message): + __slots__ = ("msg_field", "bool_field") + MSG_FIELD_FIELD_NUMBER: _ClassVar[int] + BOOL_FIELD_FIELD_NUMBER: _ClassVar[int] + msg_field: TestMsg + bool_field: bool + def __init__(self, msg_field: _Optional[_Union[TestMsg, _Mapping]] = ..., bool_field: bool = ...) -> None: ... diff --git a/protovalidate/internal/rules.py b/protovalidate/internal/rules.py index 55d76408..f1ec17a1 100644 --- a/protovalidate/internal/rules.py +++ b/protovalidate/internal/rules.py @@ -1047,10 +1047,13 @@ def _new_field_rule( def _new_rules(self, desc: descriptor.Descriptor) -> list[Rules]: result: list[Rules] = [] rule: typing.Optional[Rules] = None + all_msg_oneof_fields = set() if validate_pb2.message in desc.GetOptions().Extensions: message_level = desc.GetOptions().Extensions[validate_pb2.message] if message_level.disabled: return [] + for oneof in message_level.oneof: + all_msg_oneof_fields.update(oneof.fields) if rule := self._new_message_rule(message_level, desc): result.append(rule) @@ -1059,9 +1062,14 @@ def _new_rules(self, desc: descriptor.Descriptor) -> list[Rules]: if rule := OneofRules(oneof, oneof.GetOptions().Extensions[validate_pb2.oneof]): result.append(rule) - for field in desc.fields: + for field in desc.fields: if validate_pb2.field in field.GetOptions().Extensions: field_level = field.GetOptions().Extensions[validate_pb2.field] + if not field_level.HasField("ignore") and field.name in all_msg_oneof_fields : + field_level_override = validate_pb2.FieldRules() + field_level_override.CopyFrom(field_level) + field_level_override.ignore = validate_pb2.IGNORE_IF_UNPOPULATED + field_level = field_level_override if field_level.ignore == validate_pb2.IGNORE_ALWAYS: continue result.append(self._new_field_rule(field, field_level)) From d18bfa2104f91cb0b4283ae21ce050b940dcc79a Mon Sep 17 00:00:00 2001 From: Sri Krishna Date: Fri, 13 Jun 2025 17:07:20 +0530 Subject: [PATCH 2/2] fmt Signed-off-by: Sri Krishna --- protovalidate/internal/rules.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protovalidate/internal/rules.py b/protovalidate/internal/rules.py index f1ec17a1..7f726412 100644 --- a/protovalidate/internal/rules.py +++ b/protovalidate/internal/rules.py @@ -1062,13 +1062,13 @@ def _new_rules(self, desc: descriptor.Descriptor) -> list[Rules]: if rule := OneofRules(oneof, oneof.GetOptions().Extensions[validate_pb2.oneof]): result.append(rule) - for field in desc.fields: + for field in desc.fields: if validate_pb2.field in field.GetOptions().Extensions: field_level = field.GetOptions().Extensions[validate_pb2.field] - if not field_level.HasField("ignore") and field.name in all_msg_oneof_fields : - field_level_override = validate_pb2.FieldRules() + if not field_level.HasField("ignore") and field.name in all_msg_oneof_fields: + field_level_override = validate_pb2.FieldRules() field_level_override.CopyFrom(field_level) - field_level_override.ignore = validate_pb2.IGNORE_IF_UNPOPULATED + field_level_override.ignore = validate_pb2.IGNORE_IF_UNPOPULATED field_level = field_level_override if field_level.ignore == validate_pb2.IGNORE_ALWAYS: continue