diff --git a/Makefile b/Makefile index 30ae1c57..d4e613ed 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.0 +PROTOVALIDATE_VERSION ?= v0.13.1 # 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.yaml b/buf.yaml index 2acbae6d..608c755f 100644 --- a/buf.yaml +++ b/buf.yaml @@ -2,8 +2,8 @@ version: v2 modules: - path: proto deps: - - buf.build/bufbuild/protovalidate:v0.13.0 - - buf.build/bufbuild/protovalidate-testing:v0.13.0 + - buf.build/bufbuild/protovalidate:v0.13.1 + - buf.build/bufbuild/protovalidate-testing:v0.13.1 lint: use: - STANDARD diff --git a/gen/buf/validate/conformance/cases/messages_pb2.py b/gen/buf/validate/conformance/cases/messages_pb2.py index b1f30bc2..4074d6e7 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\"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\"G\n\x1cMessageOneofUnknownFieldName\x12\x1b\n\tstr_field\x18\x01 \x01(\tR\x08strField:\n\xbaH\x07\"\x05\n\x03xxxB\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\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') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -66,12 +66,22 @@ _globals['_MESSAGEREQUIREDONEOF'].fields_by_name['val']._serialized_options = b'\272H\003\310\001\001' _globals['_MESSAGEONEOFSINGLEFIELD']._loaded_options = None _globals['_MESSAGEONEOFSINGLEFIELD']._serialized_options = b'\272H\r\"\013\n\tstr_field' + _globals['_MESSAGEONEOFSINGLEFIELDREQUIRED']._loaded_options = None + _globals['_MESSAGEONEOFSINGLEFIELDREQUIRED']._serialized_options = b'\272H\017\"\r\n\tstr_field\020\001' _globals['_MESSAGEONEOFMULTIPLEFIELDS']._loaded_options = None _globals['_MESSAGEONEOFMULTIPLEFIELDS']._serialized_options = b'\272H\031\"\027\n\tstr_field\n\nbool_field' _globals['_MESSAGEONEOFMULTIPLEFIELDSREQUIRED']._loaded_options = None _globals['_MESSAGEONEOFMULTIPLEFIELDSREQUIRED']._serialized_options = b'\272H\033\"\031\n\tstr_field\n\nbool_field\020\001' + _globals['_MESSAGEONEOFMULTIPLESHAREDFIELDS']._loaded_options = None + _globals['_MESSAGEONEOFMULTIPLESHAREDFIELDS']._serialized_options = b'\272H5\"\031\n\tstr_field\n\nbool_field\020\001\"\030\n\tstr_field\n\tint_field\020\001' _globals['_MESSAGEONEOFUNKNOWNFIELDNAME']._loaded_options = None _globals['_MESSAGEONEOFUNKNOWNFIELDNAME']._serialized_options = b'\272H\007\"\005\n\003xxx' + _globals['_MESSAGEONEOFDUPLICATEFIELD']._loaded_options = None + _globals['_MESSAGEONEOFDUPLICATEFIELD']._serialized_options = b'\272H$\"\"\n\tstr_field\n\nbool_field\n\tstr_field' + _globals['_MESSAGEONEOFZEROFIELDS']._loaded_options = None + _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['_TESTMSG']._serialized_start=168 _globals['_TESTMSG']._serialized_end=276 _globals['_MESSAGENONE']._serialized_start=278 @@ -96,10 +106,20 @@ _globals['_MESSAGEWITH3DINSIDE']._serialized_end=998 _globals['_MESSAGEONEOFSINGLEFIELD']._serialized_start=1000 _globals['_MESSAGEONEOFSINGLEFIELD']._serialized_end=1103 - _globals['_MESSAGEONEOFMULTIPLEFIELDS']._serialized_start=1105 - _globals['_MESSAGEONEOFMULTIPLEFIELDS']._serialized_end=1223 - _globals['_MESSAGEONEOFMULTIPLEFIELDSREQUIRED']._serialized_start=1226 - _globals['_MESSAGEONEOFMULTIPLEFIELDSREQUIRED']._serialized_end=1354 - _globals['_MESSAGEONEOFUNKNOWNFIELDNAME']._serialized_start=1356 - _globals['_MESSAGEONEOFUNKNOWNFIELDNAME']._serialized_end=1427 + _globals['_MESSAGEONEOFSINGLEFIELDREQUIRED']._serialized_start=1105 + _globals['_MESSAGEONEOFSINGLEFIELDREQUIRED']._serialized_end=1218 + _globals['_MESSAGEONEOFMULTIPLEFIELDS']._serialized_start=1220 + _globals['_MESSAGEONEOFMULTIPLEFIELDS']._serialized_end=1338 + _globals['_MESSAGEONEOFMULTIPLEFIELDSREQUIRED']._serialized_start=1341 + _globals['_MESSAGEONEOFMULTIPLEFIELDSREQUIRED']._serialized_end=1469 + _globals['_MESSAGEONEOFMULTIPLESHAREDFIELDS']._serialized_start=1472 + _globals['_MESSAGEONEOFMULTIPLESHAREDFIELDS']._serialized_end=1653 + _globals['_MESSAGEONEOFUNKNOWNFIELDNAME']._serialized_start=1655 + _globals['_MESSAGEONEOFUNKNOWNFIELDNAME']._serialized_end=1726 + _globals['_MESSAGEONEOFDUPLICATEFIELD']._serialized_start=1729 + _globals['_MESSAGEONEOFDUPLICATEFIELD']._serialized_end=1858 + _globals['_MESSAGEONEOFZEROFIELDS']._serialized_start=1860 + _globals['_MESSAGEONEOFZEROFIELDS']._serialized_end=1951 + _globals['_MESSAGEONEOFUNSATISFIABLE']._serialized_start=1953 + _globals['_MESSAGEONEOFUNSATISFIABLE']._serialized_end=2057 # @@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 3f48780c..618c724b 100644 --- a/gen/buf/validate/conformance/cases/messages_pb2.pyi +++ b/gen/buf/validate/conformance/cases/messages_pb2.pyi @@ -92,6 +92,14 @@ class MessageOneofSingleField(_message.Message): bool_field: bool def __init__(self, str_field: _Optional[str] = ..., bool_field: bool = ...) -> None: ... +class MessageOneofSingleFieldRequired(_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 MessageOneofMultipleFields(_message.Message): __slots__ = ("str_field", "bool_field") STR_FIELD_FIELD_NUMBER: _ClassVar[int] @@ -108,8 +116,44 @@ class MessageOneofMultipleFieldsRequired(_message.Message): bool_field: bool def __init__(self, str_field: _Optional[str] = ..., bool_field: bool = ...) -> None: ... +class MessageOneofMultipleSharedFields(_message.Message): + __slots__ = ("str_field", "bool_field", "int_field") + STR_FIELD_FIELD_NUMBER: _ClassVar[int] + BOOL_FIELD_FIELD_NUMBER: _ClassVar[int] + INT_FIELD_FIELD_NUMBER: _ClassVar[int] + str_field: str + bool_field: bool + int_field: int + def __init__(self, str_field: _Optional[str] = ..., bool_field: bool = ..., int_field: _Optional[int] = ...) -> None: ... + class MessageOneofUnknownFieldName(_message.Message): __slots__ = ("str_field",) STR_FIELD_FIELD_NUMBER: _ClassVar[int] str_field: str def __init__(self, str_field: _Optional[str] = ...) -> None: ... + +class MessageOneofDuplicateField(_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 MessageOneofZeroFields(_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 MessageOneofUnsatisfiable(_message.Message): + __slots__ = ("a", "b", "c") + A_FIELD_NUMBER: _ClassVar[int] + B_FIELD_NUMBER: _ClassVar[int] + C_FIELD_NUMBER: _ClassVar[int] + a: bool + b: bool + c: bool + def __init__(self, a: bool = ..., b: bool = ..., c: bool = ...) -> None: ... diff --git a/protovalidate/internal/rules.py b/protovalidate/internal/rules.py index 015d281e..55d76408 100644 --- a/protovalidate/internal/rules.py +++ b/protovalidate/internal/rules.py @@ -454,9 +454,18 @@ def add_oneof( rule: validate_pb2.MessageOneofRule, ): fields = [] + seen = set() + if len(rule.fields) == 0: + msg = f"at least one field must be specified in oneof rule for the message {self._desc.full_name}" + raise CompilationError(msg) + for name in rule.fields: if name in self._desc.fields_by_name: + if name in seen: + msg = f"duplicate {name} in oneof rule for the message {self._desc.full_name}" + raise CompilationError(msg) fields.append(self._desc.fields_by_name[name]) + seen.add(name) else: msg = f'field "{name}" not found in message {self._desc.full_name}' raise CompilationError(msg)