From 52cf9a25e3a53d7b5533275f64864f27f126bcf2 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Mon, 24 Mar 2025 12:01:16 -0400 Subject: [PATCH 01/13] Enhance `isEmail` validation (#269) This enhances the current `isEmail` validation by using a consistent regex that will be used across protovalidate implementations. --- protovalidate/internal/extra_func.py | 35 +++++++++++------------- tests/conformance/nonconforming.yaml | 40 ---------------------------- 2 files changed, 16 insertions(+), 59 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index cf211c75..ee9ed9aa 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -13,8 +13,8 @@ # limitations under the License. import math +import re import typing -from email.utils import parseaddr from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_address, ip_network from urllib import parse as urlparse @@ -23,6 +23,11 @@ from protovalidate.internal import string_format +# See https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address +_email_regex = re.compile( + r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" +) + def _validate_hostname(host): if not host: @@ -49,23 +54,6 @@ def _validate_hostname(host): return not all_digits -def validate_email(addr): - parts = parseaddr(addr) - if addr != parts[1]: - return False - - addr = parts[1] - if len(addr) > 254: - return False - - parts = addr.split("@") - if len(parts) != 2: - return False - if len(parts[0]) > 64: - return False - return _validate_hostname(parts[1]) - - def validate_host_and_port(string: str, *, port_required: bool) -> bool: if not string: return False @@ -157,10 +145,19 @@ def is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result: def is_email(string: celtypes.Value) -> celpy.Result: + """Returns true if the string is an email address, for example "foo@example.com". + + Conforms to the definition for a valid email address from the HTML standard. + Note that this standard willfully deviates from RFC 5322, which allows many + unexpected forms of email addresses and will easily match a typographical + error. + """ + if not isinstance(string, celtypes.StringType): msg = "invalid argument, expected string" raise celpy.CELEvalError(msg) - return celtypes.BoolType(validate_email(string)) + m = _email_regex.match(string) is not None + return celtypes.BoolType(m) def is_uri(string: celtypes.Value) -> celpy.Result: diff --git a/tests/conformance/nonconforming.yaml b/tests/conformance/nonconforming.yaml index 531cf553..9fc8ea2f 100644 --- a/tests/conformance/nonconforming.yaml +++ b/tests/conformance/nonconforming.yaml @@ -8,46 +8,6 @@ standard_constraints/well_known_types/timestamp: - gte_lte/invalid/above - lte/invalid -library/is_email: - - invalid/left_side_empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - invalid/non_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"ยต@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - invalid/quoted-string/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo bar\"@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - invalid/quoted-string/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo..bar\"@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - invalid/trailing_dot - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"foo@example.com."} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - valid/exhaust_atext - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'*+-/=?^_`{|}~@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # message: "" - # for_key: false - - valid/label_all_digits - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"foo@0.1.2.3.4.5.6.7.8.9"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # message: "" - # for_key: false library/is_host_and_port: - port_required/false/invalid/port_number_sign # input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"example.com:+0"} From 12fa3e5ae172182a7c4eaf41fdb91f39b59fa2e2 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Wed, 26 Mar 2025 12:31:54 -0400 Subject: [PATCH 02/13] Enhance `is_uri` and `is_uri_reference` validation (#271) This ports the validation logic from `protovalidate-go` for validating URIs and URI references. As a result, all conformance tests for 0.10.3 related to these validations are now passing. This uses a single underscore for protected class members and a double-underscore for private class methods to use name-mangling. There are varying recommendations on how to structure these (for example, Effective Python recommends public members, some recommendations suggest a single-underscore for class methods, etc.). The current pattern in protovalidate-python seems to use a single underscore for module-level functions, which we could certainly adopt for classes. I'm open to whatever is the established pattern that we should adopt. --- protovalidate/internal/extra_func.py | 832 ++++++++++++++++++++++++- tests/conformance/nonconforming.yaml | 881 --------------------------- 2 files changed, 814 insertions(+), 899 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index ee9ed9aa..09ba69e3 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -145,12 +145,21 @@ def is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result: def is_email(string: celtypes.Value) -> celpy.Result: - """Returns true if the string is an email address, for example "foo@example.com". + """Validate whether string is a valid email address. Conforms to the definition for a valid email address from the HTML standard. Note that this standard willfully deviates from RFC 5322, which allows many unexpected forms of email addresses and will easily match a typographical error. + + Args: + string (celTypes.Value): The string to validate. + + Returns: + True if the string is an email address, for example "foo@example.com". False otherwise. + + Raises: + celpy.CELEvalError: If string is not an instance of celtypes.StringType. """ if not isinstance(string, celtypes.StringType): @@ -161,28 +170,50 @@ def is_email(string: celtypes.Value) -> celpy.Result: def is_uri(string: celtypes.Value) -> celpy.Result: - url = urlparse.urlparse(str(string)) - # urlparse correctly reads the scheme from URNs but parses everything - # after (except the query string) as the path. - if url.scheme == "urn": - if not (url.path): - return celtypes.BoolType(False) - elif not all([url.scheme, url.netloc, url.path]): - return celtypes.BoolType(False) + """Validate whether string is a valid URI. + + URI is defined in the internet standard RFC 3986. + Zone Identifiers in IPv6 address literals are supported (RFC 6874). + + Args: + string (celTypes.Value): The string to validate. - # If the query string contains percent-encoding, then try to decode it. - # unquote will return the same string if it is improperly encoded. - if "%" in url.query: - return celtypes.BoolType(urlparse.unquote(url.query) != url.query) + Returns: + True if the string is a URI, for example "https://example.com/foo/bar?baz=quux#frag". False otherwise. - return celtypes.BoolType(True) + Raises: + celpy.CELEvalError: If string is not an instance of celtypes.StringType. + """ + + if not isinstance(string, celtypes.StringType): + msg = "invalid argument, expected string" + raise celpy.CELEvalError(msg) + valid = Uri(str(string)).uri() + return celtypes.BoolType(valid) def is_uri_ref(string: celtypes.Value) -> celpy.Result: - url = urlparse.urlparse(str(string)) - if not all([url.scheme, url.path]) and url.fragment: - return celtypes.BoolType(False) - return celtypes.BoolType(True) + """Validate whether string is a valid URI reference. + + URI, URI Reference, and Relative Reference are defined in the internet standard RFC 3986. + Zone Identifiers in IPv6 address literals are supported (RFC 6874). + + Args: + string (celTypes.Value): The string to validate. + + Returns: + True if the string is a URI Reference - a URI such as "https://example.com/foo/bar?baz=quux#frag" + or a Relative Reference such as "./foo/bar?query". False otherwise. + + Raises: + celpy.CELEvalError: If string is not an instance of celtypes.StringType. + """ + + if not isinstance(string, celtypes.StringType): + msg = "invalid argument, expected string" + raise celpy.CELEvalError(msg) + valid = Uri(str(string)).uri_reference() + return celtypes.BoolType(valid) def is_hostname(string: celtypes.Value) -> celpy.Result: @@ -234,6 +265,771 @@ def unique(val: celtypes.Value) -> celpy.Result: return celtypes.BoolType(len(val) == len(set(val))) +class Uri: + """Uri is a class used to parse a given string to determine if it is a valid URI or URI reference. + + Callers can validate a string by constructing an instance of this class and then calling one of its + public methods: + uri() + uri_reference() + + Each method will return True or False depending on whether it passes validation. + """ + + _string: str + _index: int + _pct_encoded_found: bool + + def __init__(self, string: str): + """Initialize a URI validation class with a given string + + Args: + string (str): String to validate as a URI or URI reference. + """ + + super().__init__() + self._string = string + self._index = 0 + + def uri(self) -> bool: + """Determine whether string is a valid URI. + + Method parses the rule: + + URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + """ + + start = self._index + if not (self.__scheme() and self.__take(":") and self.__hier_part()): + self._index = start + return False + + if self.__take("?") and not self.__query(): + return False + + if self.__take("#") and not self.__fragment(): + return False + + if self._index != len(self._string): + self._index = start + return False + + return True + + def uri_reference(self) -> bool: + """Determine whether string is a valid URI reference. + + Method parses the rule: + + URI-reference = URI / relative-ref + """ + + return self.uri() or self.__relative_ref() + + def __hier_part(self) -> bool: + """Determine whether string contains a valid hier-part. + + Method parses the rule: + + hier-part = "//" authority path-abempty. + / path-absolute + / path-rootless + / path-empty + """ + + start = self._index + if self.__take("/") and self.__take("/") and self.__authority() and self.__path_abempty(): + return True + + self._index = start + + return self.__path_absolute() or self.__path_rootless() or self.__path_empty() + + def __relative_ref(self) -> bool: + """Determine whether string contains a valid relative reference. + + Method parses the rule: + + relative-ref = relative-part [ "?" query ] [ "#" fragment ] + """ + + start = self._index + if not self.__relative_part(): + return False + + if self.__take("?") and not self.__query(): + self._index = start + return False + + if self.__take("#") and not self.__fragment(): + self._index = start + return False + + if self._index != len(self._string): + self._index = start + return False + + return True + + def __relative_part(self) -> bool: + """Determine whether string contains a valid relative part. + + Method parses the rule: + + relative-part = "//" authority path-abempty + / path-absolute + / path-noscheme + / path-empty + """ + + start = self._index + if self.__take("/") and self.__take("/") and self.__authority() and self.__path_abempty(): + return True + + self._index = start + + return self.__path_absolute() or self.__path_noscheme() or self.__path_empty() + + def __scheme(self) -> bool: + """Determine whether string contains a valid scheme. + + Method parses the rule: + + scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + + Terminated by ":". + """ + + start = self._index + if self.__alpha(): + while self.__alpha() or self.__digit() or self.__take("+") or self.__take("-") or self.__take("."): + pass + + if self._string[self._index] == ":": + return True + + self._index = start + return False + + def __authority(self) -> bool: + """Determine whether string contains a valid authority. + + Method parses the rule: + + authority = [ userinfo "@" ] host [ ":" port ] + + Lead by double slash ("") and terminated by "/", "?", "#", or end of URI. + """ + + start = self._index + if self.__userinfo(): + if not self.__take("@"): + self._index = start + return False + + if not self.__host(): + self._index = start + return False + + if self.__take(":"): + if not self.__port(): + self._index = start + return False + + if not self.__is_authority_end(): + self._index = start + return False + + return True + + def __is_authority_end(self) -> bool: + """Report whether the current position is the end of the authority. + + The authority component [...] is terminated by the next slash ("/"), + question mark ("?"), or number sign ("#") character, or by the + end of the URI. + """ + + return ( + self._index >= len(self._string) + or self._string[self._index] == "?" + or self._string[self._index] == "#" + or self._string[self._index] == "/" + ) + + def __userinfo(self) -> bool: + """Determine whether string contains a valid userinfo. + + Method parses the rule: + + userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + + Terminated by "@" in authority. + """ + + start = self._index + while True: + if self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take(":"): + continue + + if self._index < len(self._string): + if self._string[self._index] == "@": + return True + + self._index = start + return False + + def __check_host_pct_encoded(self, string: str) -> bool: + """Verify that string is correctly percent-encoded""" + try: + # unquote defaults to 'UTF-8' encoding. + urlparse.unquote(string, errors="strict") + except UnicodeError: + return False + + return True + + def __host(self) -> bool: + """Determine whether string contains a valid host. + + Method parses the rule: + + host = IP-literal / IPv4address / reg-name. + """ + + if self._index >= len(self._string): + return False + + start = self._index + self._pct_encoded_found = False + + # Note: IPv4address is a subset of reg-name + if (self._string[self._index] == "[" and self.__ip_literal()) or self.__reg_name(): + if self._pct_encoded_found: + raw_host = self._string[start : self._index] + # RFC 3986: + # > URI producing applications must not use percent-encoding in host + # > unless it is used to represent a UTF-8 character sequence. + if not self.__check_host_pct_encoded(raw_host): + return False + + return True + + return False + + def __port(self) -> bool: + """Determine whether string contains a valid port. + + Method parses the rule: + + port = *DIGIT + + Terminated by end of authority. + """ + + start = self._index + while True: + if self.__digit(): + continue + + if self.__is_authority_end(): + return True + + self._index = start + return False + + def __ip_literal(self) -> bool: + """Determine whether string contains a valid port. + + Method parses the rule from RFC 6874: + + IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]" + """ + + start = self._index + + if self.__take("["): + curr_idx = self._index + if self.__ipv6_address() and self.__take("]"): + return True + + self._index = curr_idx + + if self.__ipv6_addrz() and self.__take("]"): + return True + + self._index = curr_idx + + if self.__ip_vfuture() and self.__take("]"): + return True + + self._index = start + return False + + def __ipv6_address(self) -> bool: + """Determine whether string contains a valid ipv6 address. + + Method parses the rule "IPv6address". + + Relies on the implementation of validate_ip. + """ + + start = self._index + while self.__hex_dig() or self.__take(":"): + pass + + if validate_ip(self._string[start : self._index], 6): + return True + + self._index = start + return False + + def __ipv6_addrz(self) -> bool: + """Determine whether string contains a valid IPv6addrz. + + Method parses the rule from RFC 6874: + + IPv6addrz = IPv6address "%25" ZoneID + """ + + start = self._index + if self.__ipv6_address() and self.__take("%") and self.__take("2") and self.__take("5") and self.__zone_id(): + return True + + self._index = start + + return False + + def __zone_id(self) -> bool: + """Determine whether string contains a valid zone ID. + + Method parses the rule from RFC 6874: + + ZoneID = 1*( unreserved / pct-encoded ) + """ + + start = self._index + while self.__unreserved() or self.__pct_encoded(): + pass + + if self._index - start > 0: + return True + + self._index = start + + return False + + def __ip_vfuture(self) -> bool: + """Determine whether string contains a valid ipvFuture. + + Method parses the rule: + + IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + """ + + start = self._index + + if self.__take("v") and self.__hex_dig(): + while self.__hex_dig(): + pass + + if self.__take("."): + j = 0 + while self.__unreserved() or self.__sub_delims() or self.__take(":"): + j += 1 + + if j >= 1: + return True + + self._index = start + + return False + + def __reg_name(self) -> bool: + """Determine whether string contains a valid reg-name. + + Method parses the rule: + + reg-name = *( unreserved / pct-encoded / sub-delims ) + + Terminates on start of port (":") or end of authority. + """ + + start = self._index + while True: + if self.__unreserved() or self.__pct_encoded() or self.__sub_delims(): + continue + + if self.__is_authority_end(): + # End of authority + return True + + if self._string[self._index] == ":": + return True + + self._index = start + + return False + + def __is_path_end(self) -> bool: + """Determine whether the current index has reached the end of path. + + > The path is terminated by the first question mark ("?") or + > number sign ("#") character, or by the end of the URI. + """ + + return self._index >= len(self._string) or self._string[self._index] == "?" or self._string[self._index] == "#" + + def __path_abempty(self) -> bool: + """Determine whether string contains a path-abempty. + + Method parses the rule: + + path-abempty = *( "/" segment ) + + Terminated by end of path: "?", "#", or end of URI. + """ + + start = self._index + while self.__take("/") and self.__segment(): + pass + + if self.__is_path_end(): + return True + + self._index = start + + return False + + def __path_absolute(self) -> bool: + """Determine whether string contains a path-absolute. + + Method parses the rule: + + path-absolute = "/" [ segment-nz *( "/" segment ) ] + + Terminated by end of path: "?", "#", or end of URI. + """ + + start = self._index + + if self.__take("/"): + if self.__segment_nz(): + while self.__take("/") and self.__segment(): + pass + + if self.__is_path_end(): + return True + + self._index = start + + return False + + def __path_noscheme(self) -> bool: + """Determine whether string contains a path-noscheme. + + Method parses the rule: + + path-noscheme = segment-nz-nc *( "/" segment ) + + Terminated by end of path: "?", "#", or end of URI. + """ + + start = self._index + if self.__segment_nz_nc(): + while self.__take("/") and self.__segment(): + pass + + if self.__is_path_end(): + return True + + self._index = start + + return True + + def __path_rootless(self) -> bool: + """Determine whether string contains a path-rootless. + + Method parses the rule: + + path-rootless = segment-nz *( "/" segment ) + + Terminated by end of path: "?", "#", or end of URI. + """ + + start = self._index + + if self.__segment_nz(): + while self.__take("/") and self.__segment(): + pass + + if self.__is_path_end(): + return True + + self._index = start + + return True + + def __path_empty(self) -> bool: + """Determine whether string contains a path-empty. + + Method parses the rule: + + path-empty = 0 + + Terminated by end of path: "?", "#", or end of URI. + """ + + return self.__is_path_end() + + def __segment(self) -> bool: + """Determine whether string contains a segment. + + Method parses the rule: + + segment = *pchar + """ + + while self.__pchar(): + pass + + return True + + def __segment_nz(self) -> bool: + """Determine whether string contains a segment-nz. + + Method parses the rule: + + segment-nz = 1*pchar + """ + + start = self._index + + if self.__pchar(): + while self.__pchar(): + pass + + return True + + self._index = start + + return False + + def __segment_nz_nc(self) -> bool: + """Determine whether string contains a segment-nz-nc. + + Method parses the rule: + + segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) + ; non-zero-length segment without any colon ":" + """ + + start = self._index + + while self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take("@"): + pass + + if self._index - start > 0: + return True + + self._index = start + + return False + + def __pchar(self) -> bool: + """Report whether the current position is a pchar. + + Method parses the rule: + + pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + """ + + return ( + self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take(":") or self.__take("@") + ) + + def __query(self) -> bool: + """Determine whether string contains a valid query. + + Method parses the rule: + + query = *( pchar / "/" / "?" ) + + Terminated by "#" or end of URI. + """ + + start = self._index + + while True: + if self.__pchar() or self.__take("/") or self.__take("?"): + continue + + if self._index == len(self._string) or self._string[self._index] == "#": + return True + + self._index = start + + return False + + def __fragment(self) -> bool: + """Determine whether string contains a valid fragment. + + Method parses the rule: + + fragment = *( pchar / "/" / "?" ) + + Terminated by end of URI. + """ + + start = self._index + + while True: + if self.__pchar() or self.__take("/") or self.__take("?"): + continue + + if self._index == len(self._string): + return True + + self._index = start + + return False + + def __pct_encoded(self) -> bool: + """Determine whether string contains a valid percent encoding. + + Method parses the rule: + + pct-encoded = "%" HEXDIG HEXDIG + + Sets `_pct_encoded_found` to true if a valid triplet was found + """ + + start = self._index + + if self.__take("%") and self.__hex_dig() and self.__hex_dig(): + self._pct_encoded_found = True + return True + + self._index = start + + return False + + def __unreserved(self) -> bool: + """Report whether the current position is an unreserved character. + + Method parses the rule: + + unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + """ + + return ( + self.__alpha() + or self.__digit() + or self.__take("-") + or self.__take("_") + or self.__take(".") + or self.__take("~") + ) + + def __sub_delims(self) -> bool: + """Report whether the current position is a sub-delim. + + Method parses the rule: + + sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" + """ + + return ( + self.__take("!") + or self.__take("$") + or self.__take("&") + or self.__take("'") + or self.__take("(") + or self.__take(")") + or self.__take("*") + or self.__take("+") + or self.__take(",") + or self.__take(";") + or self.__take("=") + ) + + def __alpha(self) -> bool: + """Report whether the current position is an alpha character. + + Method parses the rule: + + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + """ + + if self._index >= len(self._string): + return False + + c = self._string[self._index] + if ("A" <= c <= "Z") or ("a" <= c <= "z"): + self._index += 1 + return True + + return False + + def __digit(self) -> bool: + """Report whether the current position is a digit. + + Method parses the rule: + + DIGIT = %x30-39 ; 0-9 + """ + + if self._index >= len(self._string): + return False + + c = self._string[self._index] + if "0" <= c <= "9": + self._index += 1 + return True + + return False + + def __hex_dig(self) -> bool: + """Report whether the current position is a hex digit. + + Method parses the rule: + + HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + """ + + if self._index >= len(self._string): + return False + + c = self._string[self._index] + + if ("0" <= c <= "9") or ("a" <= c <= "f") or ("A" <= c <= "F") or ("0" <= c <= "9"): + self._index += 1 + + return True + + return False + + def __take(self, char: str) -> bool: + """Take the given char at the current index. + + If char is at the current index, increment the index. + + Returns: + True if char is at the current index. False if char is not at the + current index or the end of string has been reached. + """ + + if self._index >= len(self._string): + return False + + if self._string[self._index] == char: + self._index += 1 + return True + + return False + + def make_extra_funcs(locale: str) -> dict[str, celpy.CELFunction]: # TODO(#257): Fix types and add tests for StringFormat. # For now, ignoring the type. diff --git a/tests/conformance/nonconforming.yaml b/tests/conformance/nonconforming.yaml index 9fc8ea2f..7ac7fe88 100644 --- a/tests/conformance/nonconforming.yaml +++ b/tests/conformance/nonconforming.yaml @@ -126,887 +126,6 @@ library/is_ip_prefix: # want: validation error (1 violation) # 1. constraint_id: "library.is_ip_prefix" # got: valid -library/is_uri: - - invalid/authority_path-abempty_segment_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo://example.com/^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/authority_path-abempty_segment_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo://example.com/\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/authority_path-abempty_segment_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo://example.com/%x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_ipfuture - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[v1x]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ('IPvFuture address is invalid',)) - - invalid/host_ipv6/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[2001::0370::7334]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'2001::0370::7334' does not appear to be an IPv4 or IPv6 address",)) - - invalid/host_ipv6_zone-id_bad_pct-encoded/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'::1%25foo%' does not appear to be an IPv4 or IPv6 address",)) - - invalid/host_ipv6_zone-id_bad_pct-encoded/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%2x]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'::1%25foo%2x' does not appear to be an IPv4 or IPv6 address",)) - - invalid/host_ipv6_zone-id_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%c3x%96]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'::1%25foo%c3x%96' does not appear to be an IPv4 or IPv6 address",)) - - invalid/userinfo_reserved_square_bracket_close - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://]@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ('Invalid IPv6 URL',)) - - invalid/userinfo_reserved_square_bracket_open - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ('Invalid IPv6 URL',)) - - valid/authority_path-abempty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo://example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/example - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/fragment_pchar_extra - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#/?"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/fragment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/fragment_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/fragment_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/fragment_sub-delims - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#!$&'()*+,;="} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ip4v_bad_octet - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://256.0.0.1"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ipfuture_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[vF.-!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ipfuture_long - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[v1234AF.x]"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ipfuture_short - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[v1.x]"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ipv4 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://127.0.0.1"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ipv6 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ipv6_zone-id - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25eth0]"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_ipv6_zone-id_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%61%20%23]"} - # want: valid - # got: runtime error: ('return error for overflow', , ("'::1%25foo%61%20%23' does not appear to be an IPv4 or IPv6 address",)) - - valid/host_ipv6_zone-id_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%c3%96]"} - # want: valid - # got: runtime error: ('return error for overflow', , ("'::1%25foo%c3%96' does not appear to be an IPv4 or IPv6 address",)) - - valid/host_reg-name - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_reg-name_empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://:8080"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_reg-name_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_reg-name_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/host_reg-name_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_exhaust_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_exhaust_segment-nz - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment-nz-pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment-nz-pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment-nz-pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/%c3%96%c3"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_empty_pchar - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_query_and_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz?q#f"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/a"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_segments - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz//segment//segment/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-empty_with_query_and_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:?q#f"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_empty_pchar - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_with_query_and_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz?q#f"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_with_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/a"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_with_segments - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz//segment//segment/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/port_0 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:0"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/port_1 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:1"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/port_65535 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:65535"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/port_65536 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:65536"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/port_8080 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:8080"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/port_empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/port_empty_reg-name_empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?baz=quux"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_extra - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?/?"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_pchar_extra - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?:@"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?%c3%96%c3"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_sub-delim_semicolon - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?;"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_sub-delims - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?!$&'()*+,="} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/query_unusual_key_value_structure - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?a=b&c&&=1&=="} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/scheme_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo0123456789azAZ+-.://example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/scheme_ftp - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"ftp://example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_extra - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://:@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_multiple_colons - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://:::@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_name - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://user@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_name_password - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://user:password@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://%61%20%23@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_pct-encoded_invalid-utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://%c3x%963@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://%c3%963@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_reserved_hash_parses_as_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://#@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_reserved_questionmark_parses_as_query - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://?@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_reserved_slash_parses_as_path-abempty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https:///@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_sub-delims - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://!$&'()*+,;=@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_unreserved - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false -library/is_uri_ref: - - invalid/authority_path-abempty_segment_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/bad_relative-part - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:":"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/leading_space - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:" ./foo"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-abempty_query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host?^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-abempty_query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-abempty_query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host?%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-abempty_segment_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/%x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/foo/\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/?^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/?%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_segment-nz_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_segment-nz_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_segment-nz_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/%x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_segment_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/nz/^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_segment_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/nz/\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_segment_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/nz/%x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-empty_query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"?^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-empty_query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-empty_query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"?%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./foo/\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".?^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".?%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_segment-bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_segment-nz_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_segment-nz_bad_colon - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:":"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_segment-nz_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_segment-nz_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"%x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_segment_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_segment_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./%x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/space - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:" "} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/trailing_space - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./foo "} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/uri_with_bad_scheme - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"1foo://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - valid/authority_path-abempty_with_segment_query_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/foo?baz=quux#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/extreme - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//userinfo0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@host!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789/path0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20//foo/?query0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?#fragment0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-abempty_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-abempty_with_fragment/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-abempty_with_fragment/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/foo/bar#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-absolute_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-absolute_with_fragment/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-absolute_with_fragment/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/foo/bar#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-empty_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-noscheme_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-noscheme_with_fragment/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-noscheme_with_fragment/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./foo/bar#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-noscheme_with_segment_query_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./foo/bar?baz=quux#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false standard_constraints/required: - proto2/scalar/optional/unset # input: [type.googleapis.com/buf.validate.conformance.cases.RequiredProto2ScalarOptional]:{} From bb6012df879e8525c943fc2b9440b4bb11b10572 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Thu, 27 Mar 2025 08:52:48 -0400 Subject: [PATCH 03/13] Propose docstring style for repo (#272) Here is a proposal for how to structure docstrings in protovalidate-python going forward (at least when porting the rest of the validation implementations). I think this strikes the right balance of adhering to best practices while not being onerous. * Functions being added to the library should get the bulk of the comment description in a docstring. No need to define `Args:`, `Raises:`, etc. though. * Any other public function, class, etc. should preferably have docstrings, though not necessary. * Be as succinct as possible while also keeping comments across implementations consistently worded. For validation following an RFC / spec: * Always include any relevant ABNF grammar and keep aligned. * Indent grammar inside docstring. One suggestion outside the scope of this PR might be to always have full docstring (`Args`, `Raises`, etc.) on any public method the user interacts with (i.e. stuff in `validator.py`) --- protovalidate/internal/extra_func.py | 209 +++++++++++---------------- 1 file changed, 82 insertions(+), 127 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index 09ba69e3..3ad72b1c 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -145,23 +145,14 @@ def is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result: def is_email(string: celtypes.Value) -> celpy.Result: - """Validate whether string is a valid email address. + """Return true if the string is an email address, for example "foo@example.com". Conforms to the definition for a valid email address from the HTML standard. Note that this standard willfully deviates from RFC 5322, which allows many unexpected forms of email addresses and will easily match a typographical error. - Args: - string (celTypes.Value): The string to validate. - - Returns: - True if the string is an email address, for example "foo@example.com". False otherwise. - - Raises: - celpy.CELEvalError: If string is not an instance of celtypes.StringType. """ - if not isinstance(string, celtypes.StringType): msg = "invalid argument, expected string" raise celpy.CELEvalError(msg) @@ -170,21 +161,12 @@ def is_email(string: celtypes.Value) -> celpy.Result: def is_uri(string: celtypes.Value) -> celpy.Result: - """Validate whether string is a valid URI. + """Return true if the string is a URI, for example "https://example.com/foo/bar?baz=quux#frag". URI is defined in the internet standard RFC 3986. Zone Identifiers in IPv6 address literals are supported (RFC 6874). - Args: - string (celTypes.Value): The string to validate. - - Returns: - True if the string is a URI, for example "https://example.com/foo/bar?baz=quux#frag". False otherwise. - - Raises: - celpy.CELEvalError: If string is not an instance of celtypes.StringType. """ - if not isinstance(string, celtypes.StringType): msg = "invalid argument, expected string" raise celpy.CELEvalError(msg) @@ -193,22 +175,13 @@ def is_uri(string: celtypes.Value) -> celpy.Result: def is_uri_ref(string: celtypes.Value) -> celpy.Result: - """Validate whether string is a valid URI reference. + """Return true if the string is a URI Reference - a URI such as "https://example.com/foo/bar?baz=quux#frag" or + a Relative Reference such as "./foo/bar?query". URI, URI Reference, and Relative Reference are defined in the internet standard RFC 3986. Zone Identifiers in IPv6 address literals are supported (RFC 6874). - Args: - string (celTypes.Value): The string to validate. - - Returns: - True if the string is a URI Reference - a URI such as "https://example.com/foo/bar?baz=quux#frag" - or a Relative Reference such as "./foo/bar?query". False otherwise. - - Raises: - celpy.CELEvalError: If string is not an instance of celtypes.StringType. """ - if not isinstance(string, celtypes.StringType): msg = "invalid argument, expected string" raise celpy.CELEvalError(msg) @@ -266,27 +239,14 @@ def unique(val: celtypes.Value) -> celpy.Result: class Uri: - """Uri is a class used to parse a given string to determine if it is a valid URI or URI reference. - - Callers can validate a string by constructing an instance of this class and then calling one of its - public methods: - uri() - uri_reference() - - Each method will return True or False depending on whether it passes validation. - """ + """Uri is a class used to parse a given string to determine if it is a valid URI or URI reference.""" _string: str _index: int _pct_encoded_found: bool def __init__(self, string: str): - """Initialize a URI validation class with a given string - - Args: - string (str): String to validate as a URI or URI reference. - """ - + """Initialize a URI validation class with a given string.""" super().__init__() self._string = string self._index = 0 @@ -296,9 +256,9 @@ def uri(self) -> bool: Method parses the rule: - URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] - """ + URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + """ start = self._index if not (self.__scheme() and self.__take(":") and self.__hier_part()): self._index = start @@ -321,9 +281,9 @@ def uri_reference(self) -> bool: Method parses the rule: - URI-reference = URI / relative-ref - """ + URI-reference = URI / relative-ref + """ return self.uri() or self.__relative_ref() def __hier_part(self) -> bool: @@ -331,12 +291,12 @@ def __hier_part(self) -> bool: Method parses the rule: - hier-part = "//" authority path-abempty. - / path-absolute - / path-rootless - / path-empty - """ + hier-part = "//" authority path-abempty. + / path-absolute + / path-rootless + / path-empty + """ start = self._index if self.__take("/") and self.__take("/") and self.__authority() and self.__path_abempty(): return True @@ -350,9 +310,9 @@ def __relative_ref(self) -> bool: Method parses the rule: - relative-ref = relative-part [ "?" query ] [ "#" fragment ] - """ + relative-ref = relative-part [ "?" query ] [ "#" fragment ] + """ start = self._index if not self.__relative_part(): return False @@ -376,12 +336,12 @@ def __relative_part(self) -> bool: Method parses the rule: - relative-part = "//" authority path-abempty - / path-absolute - / path-noscheme - / path-empty - """ + relative-part = "//" authority path-abempty + / path-absolute + / path-noscheme + / path-empty + """ start = self._index if self.__take("/") and self.__take("/") and self.__authority() and self.__path_abempty(): return True @@ -395,11 +355,11 @@ def __scheme(self) -> bool: Method parses the rule: - scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) Terminated by ":". - """ + """ start = self._index if self.__alpha(): while self.__alpha() or self.__digit() or self.__take("+") or self.__take("-") or self.__take("."): @@ -416,11 +376,11 @@ def __authority(self) -> bool: Method parses the rule: - authority = [ userinfo "@" ] host [ ":" port ] + authority = [ userinfo "@" ] host [ ":" port ] Lead by double slash ("") and terminated by "/", "?", "#", or end of URI. - """ + """ start = self._index if self.__userinfo(): if not self.__take("@"): @@ -448,8 +408,8 @@ def __is_authority_end(self) -> bool: The authority component [...] is terminated by the next slash ("/"), question mark ("?"), or number sign ("#") character, or by the end of the URI. - """ + """ return ( self._index >= len(self._string) or self._string[self._index] == "?" @@ -462,11 +422,11 @@ def __userinfo(self) -> bool: Method parses the rule: - userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) Terminated by "@" in authority. - """ + """ start = self._index while True: if self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take(":"): @@ -480,7 +440,7 @@ def __userinfo(self) -> bool: return False def __check_host_pct_encoded(self, string: str) -> bool: - """Verify that string is correctly percent-encoded""" + """Verify that string is correctly percent-encoded.""" try: # unquote defaults to 'UTF-8' encoding. urlparse.unquote(string, errors="strict") @@ -494,9 +454,9 @@ def __host(self) -> bool: Method parses the rule: - host = IP-literal / IPv4address / reg-name. - """ + host = IP-literal / IPv4address / reg-name. + """ if self._index >= len(self._string): return False @@ -522,11 +482,11 @@ def __port(self) -> bool: Method parses the rule: - port = *DIGIT + port = *DIGIT Terminated by end of authority. - """ + """ start = self._index while True: if self.__digit(): @@ -543,9 +503,9 @@ def __ip_literal(self) -> bool: Method parses the rule from RFC 6874: - IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]" - """ + IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]" + """ start = self._index if self.__take("["): @@ -572,8 +532,8 @@ def __ipv6_address(self) -> bool: Method parses the rule "IPv6address". Relies on the implementation of validate_ip. - """ + """ start = self._index while self.__hex_dig() or self.__take(":"): pass @@ -589,9 +549,9 @@ def __ipv6_addrz(self) -> bool: Method parses the rule from RFC 6874: - IPv6addrz = IPv6address "%25" ZoneID - """ + IPv6addrz = IPv6address "%25" ZoneID + """ start = self._index if self.__ipv6_address() and self.__take("%") and self.__take("2") and self.__take("5") and self.__zone_id(): return True @@ -605,9 +565,9 @@ def __zone_id(self) -> bool: Method parses the rule from RFC 6874: - ZoneID = 1*( unreserved / pct-encoded ) - """ + ZoneID = 1*( unreserved / pct-encoded ) + """ start = self._index while self.__unreserved() or self.__pct_encoded(): pass @@ -624,9 +584,9 @@ def __ip_vfuture(self) -> bool: Method parses the rule: - IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) - """ + IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + """ start = self._index if self.__take("v") and self.__hex_dig(): @@ -650,11 +610,11 @@ def __reg_name(self) -> bool: Method parses the rule: - reg-name = *( unreserved / pct-encoded / sub-delims ) + reg-name = *( unreserved / pct-encoded / sub-delims ) Terminates on start of port (":") or end of authority. - """ + """ start = self._index while True: if self.__unreserved() or self.__pct_encoded() or self.__sub_delims(): @@ -676,8 +636,8 @@ def __is_path_end(self) -> bool: > The path is terminated by the first question mark ("?") or > number sign ("#") character, or by the end of the URI. - """ + """ return self._index >= len(self._string) or self._string[self._index] == "?" or self._string[self._index] == "#" def __path_abempty(self) -> bool: @@ -685,11 +645,11 @@ def __path_abempty(self) -> bool: Method parses the rule: - path-abempty = *( "/" segment ) + path-abempty = *( "/" segment ) Terminated by end of path: "?", "#", or end of URI. - """ + """ start = self._index while self.__take("/") and self.__segment(): pass @@ -706,11 +666,11 @@ def __path_absolute(self) -> bool: Method parses the rule: - path-absolute = "/" [ segment-nz *( "/" segment ) ] + path-absolute = "/" [ segment-nz *( "/" segment ) ] Terminated by end of path: "?", "#", or end of URI. - """ + """ start = self._index if self.__take("/"): @@ -730,11 +690,11 @@ def __path_noscheme(self) -> bool: Method parses the rule: - path-noscheme = segment-nz-nc *( "/" segment ) + path-noscheme = segment-nz-nc *( "/" segment ) Terminated by end of path: "?", "#", or end of URI. - """ + """ start = self._index if self.__segment_nz_nc(): while self.__take("/") and self.__segment(): @@ -752,11 +712,11 @@ def __path_rootless(self) -> bool: Method parses the rule: - path-rootless = segment-nz *( "/" segment ) + path-rootless = segment-nz *( "/" segment ) Terminated by end of path: "?", "#", or end of URI. - """ + """ start = self._index if self.__segment_nz(): @@ -775,11 +735,11 @@ def __path_empty(self) -> bool: Method parses the rule: - path-empty = 0 + path-empty = 0 Terminated by end of path: "?", "#", or end of URI. - """ + """ return self.__is_path_end() def __segment(self) -> bool: @@ -787,9 +747,9 @@ def __segment(self) -> bool: Method parses the rule: - segment = *pchar - """ + segment = *pchar + """ while self.__pchar(): pass @@ -800,9 +760,9 @@ def __segment_nz(self) -> bool: Method parses the rule: - segment-nz = 1*pchar - """ + segment-nz = 1*pchar + """ start = self._index if self.__pchar(): @@ -820,10 +780,10 @@ def __segment_nz_nc(self) -> bool: Method parses the rule: - segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) - ; non-zero-length segment without any colon ":" - """ + segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) + ; non-zero-length segment without any colon ":" + """ start = self._index while self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take("@"): @@ -841,9 +801,9 @@ def __pchar(self) -> bool: Method parses the rule: - pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - """ + pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + """ return ( self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take(":") or self.__take("@") ) @@ -853,11 +813,11 @@ def __query(self) -> bool: Method parses the rule: - query = *( pchar / "/" / "?" ) + query = *( pchar / "/" / "?" ) Terminated by "#" or end of URI. - """ + """ start = self._index while True: @@ -876,11 +836,11 @@ def __fragment(self) -> bool: Method parses the rule: - fragment = *( pchar / "/" / "?" ) + fragment = *( pchar / "/" / "?" ) Terminated by end of URI. - """ + """ start = self._index while True: @@ -899,11 +859,11 @@ def __pct_encoded(self) -> bool: Method parses the rule: - pct-encoded = "%" HEXDIG HEXDIG + pct-encoded = "%" HEXDIG HEXDIG Sets `_pct_encoded_found` to true if a valid triplet was found - """ + """ start = self._index if self.__take("%") and self.__hex_dig() and self.__hex_dig(): @@ -919,9 +879,9 @@ def __unreserved(self) -> bool: Method parses the rule: - unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - """ + unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + """ return ( self.__alpha() or self.__digit() @@ -936,10 +896,10 @@ def __sub_delims(self) -> bool: Method parses the rule: - sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - / "*" / "+" / "," / ";" / "=" - """ + sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" + """ return ( self.__take("!") or self.__take("$") @@ -959,9 +919,9 @@ def __alpha(self) -> bool: Method parses the rule: - ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - """ + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + """ if self._index >= len(self._string): return False @@ -977,9 +937,9 @@ def __digit(self) -> bool: Method parses the rule: - DIGIT = %x30-39 ; 0-9 - """ + DIGIT = %x30-39 ; 0-9 + """ if self._index >= len(self._string): return False @@ -995,9 +955,9 @@ def __hex_dig(self) -> bool: Method parses the rule: - HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - """ + HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + """ if self._index >= len(self._string): return False @@ -1014,12 +974,7 @@ def __take(self, char: str) -> bool: """Take the given char at the current index. If char is at the current index, increment the index. - - Returns: - True if char is at the current index. False if char is not at the - current index or the end of string has been reached. """ - if self._index >= len(self._string): return False From 8a0b929cf06378ae0884e00f082a0fab7ec3d025 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Fri, 28 Mar 2025 14:04:02 -0400 Subject: [PATCH 04/13] Enhance IP validation (#274) This beefs up the validation for the following: IP address (v4 and v6) IP prefix (v4 and v6) Hostname / Port The validation here adheres to the defined RFC standards for each entity and passes conformance for all new conformance tests defined in https://github.com/bufbuild/protovalidate/pull/320. --- protovalidate/internal/extra_func.py | 802 +++++++++++++++++++++++---- tests/conformance/nonconforming.yaml | 137 +---- 2 files changed, 700 insertions(+), 239 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index 3ad72b1c..54f825a2 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -15,7 +15,6 @@ import math import re import typing -from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_address, ip_network from urllib import parse as urlparse import celpy @@ -29,92 +28,70 @@ ) -def _validate_hostname(host): - if not host: - return False - if len(host) > 253: - return False +def cel_is_ip(val: celtypes.Value, ver: typing.Optional[celtypes.Value] = None) -> celpy.Result: + """Return True if the string is an IPv4 or IPv6 address, optionally limited to a specific version. - if host[-1] == ".": - host = host[:-1] + Version 0 or None means either 4 or 6. Passing a version other than 0, 4, or 6 always returns False. - all_digits = True - for part in host.split("."): - if len(part) == 0 or len(part) > 63: - return False + IPv4 addresses are expected in the dotted decimal format, for example "192.168.5.21". + IPv6 addresses are expected in their text representation, for example "::1" or "2001:0DB8:ABCD:0012::0". - # Host names cannot begin or end with hyphens - if part[0] == "-" or part[-1] == "-": - return False - all_digits = True - for r in part: - if (r < "A" or r > "Z") and (r < "a" or r > "z") and (r < "0" or r > "9") and r != "-": - return False - all_digits = all_digits and "0" <= r <= "9" - return not all_digits + Both formats are well-defined in the internet standard RFC 3986. Zone + identifiers for IPv6 addresses (for example "fe80::a%en1") are supported. + """ + if not isinstance(val, celtypes.StringType): + msg = "invalid argument, expected string" + raise celpy.CELEvalError(msg) + if not isinstance(ver, celtypes.IntType) and ver is not None: + msg = "invalid argument, expected int" + raise celpy.CELEvalError(msg) -def validate_host_and_port(string: str, *, port_required: bool) -> bool: - if not string: - return False + if ver is None: + version = 0 + else: + version = ver - split_idx = string.rfind(":") - if string[0] == "[": - end = string.find("]") - after_end = end + 1 - if after_end == len(string): # no port - return not port_required and validate_ip(string[1:end], 6) - if after_end == split_idx: # port - return validate_ip(string[1:end]) and validate_port(string[split_idx + 1 :]) - return False # malformed + return celtypes.BoolType(_is_ip(val, version)) - if split_idx == -1: - return not port_required and (_validate_hostname(string) or validate_ip(string, 4)) - host = string[:split_idx] - port = string[split_idx + 1 :] - return (_validate_hostname(host) or validate_ip(host, 4)) and validate_port(port) +def _is_ip(string: str, version: int) -> bool: + """Internal implementation""" + valid = False + if version == 6: + valid = Ipv6(string).address() + elif version == 4: + valid = Ipv4(string).address() + elif version == 0: + valid = Ipv4(string).address() or Ipv6(string).address() + return valid -def validate_port(val: str) -> bool: - try: - port = int(val) - return port <= 65535 - except ValueError: - return False +def cel_is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result: + """Return True if the string is a valid IP with prefix length, optionally + limited to a specific version (v4 or v6), and optionally requiring the host + portion to be all zeros. -def validate_ip(val: typing.Union[str, bytes], version: typing.Optional[int] = None) -> bool: - try: - if version is None: - ip_address(val) - elif version == 4: - IPv4Address(val) - elif version == 6: - IPv6Address(val) - else: - msg = "invalid argument, expected 4 or 6" - raise celpy.CELEvalError(msg) - return True - except ValueError: - return False + An address prefix divides an IP address into a network portion, and a host portion. + The prefix length specifies how many bits the network portion has. + For example, the IPv6 prefix "2001:db8:abcd:0012::0/64" designates the + left-most 64 bits as the network prefix. The range of the network is 2**64 + addresses, from 2001:db8:abcd:0012::0 to 2001:db8:abcd:0012:ffff:ffff:ffff:ffff. + An address prefix may include a specific host address, for example + "2001:db8:abcd:0012::1f/64". With strict = true, this is not permitted. The + host portion must be all zeros, as in "2001:db8:abcd:0012::0/64". -def is_ip(val: celtypes.Value, version: typing.Optional[celtypes.Value] = None) -> celpy.Result: - if not isinstance(val, (celtypes.BytesType, celtypes.StringType)): - msg = "invalid argument, expected string or bytes" - raise celpy.CELEvalError(msg) - if not isinstance(version, celtypes.IntType) and version is not None: - msg = "invalid argument, expected int" - raise celpy.CELEvalError(msg) - return celtypes.BoolType(validate_ip(val, version)) + The same principle applies to IPv4 addresses. "192.168.1.0/24" designates + the first 24 bits of the 32-bit IPv4 as the network prefix. + """ -def is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result: - if not isinstance(val, (celtypes.BytesType, celtypes.StringType)): + if not isinstance(val, celtypes.StringType): msg = "invalid argument, expected string or bytes" raise celpy.CELEvalError(msg) - version = None + version = 0 strict = celtypes.BoolType(False) if len(args) == 1 and isinstance(args[0], celtypes.BoolType): strict = args[0] @@ -129,23 +106,27 @@ def is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result: elif len(args) == 2 and (not isinstance(args[0], celtypes.IntType) or not isinstance(args[1], celtypes.BoolType)): msg = "invalid argument, expected int and bool" raise celpy.CELEvalError(msg) - try: - if version is None: - ip_network(val, strict=bool(strict)) - elif version == 4: - IPv4Network(val, strict=bool(strict)) - elif version == 6: - IPv6Network(val, strict=bool(strict)) - else: - msg = "invalid argument, expected 4 or 6" - raise celpy.CELEvalError(msg) - return celtypes.BoolType(True) - except ValueError: - return celtypes.BoolType(False) + return celtypes.BoolType(_is_ip_prefix(val, version, strict=strict)) -def is_email(string: celtypes.Value) -> celpy.Result: - """Return true if the string is an email address, for example "foo@example.com". + +def _is_ip_prefix(string: str, version: int, *, strict=False) -> bool: + """Internal implementation""" + valid = False + if version == 6: + v6 = Ipv6(string) + valid = v6.address_prefix() and (not strict or v6.is_prefix_only()) + elif version == 4: + v4 = Ipv4(string) + valid = v4.address_prefix() and (not strict or v4.is_prefix_only()) + elif version == 0: + valid = _is_ip_prefix(string, 6, strict=strict) or _is_ip_prefix(string, 4, strict=strict) + + return valid + + +def cel_is_email(string: celtypes.Value) -> celpy.Result: + """Return True if the string is an email address, for example "foo@example.com". Conforms to the definition for a valid email address from the HTML standard. Note that this standard willfully deviates from RFC 5322, which allows many @@ -160,8 +141,8 @@ def is_email(string: celtypes.Value) -> celpy.Result: return celtypes.BoolType(m) -def is_uri(string: celtypes.Value) -> celpy.Result: - """Return true if the string is a URI, for example "https://example.com/foo/bar?baz=quux#frag". +def cel_is_uri(string: celtypes.Value) -> celpy.Result: + """Return True if the string is a URI, for example "https://example.com/foo/bar?baz=quux#frag". URI is defined in the internet standard RFC 3986. Zone Identifiers in IPv6 address literals are supported (RFC 6874). @@ -174,8 +155,8 @@ def is_uri(string: celtypes.Value) -> celpy.Result: return celtypes.BoolType(valid) -def is_uri_ref(string: celtypes.Value) -> celpy.Result: - """Return true if the string is a URI Reference - a URI such as "https://example.com/foo/bar?baz=quux#frag" or +def cel_is_uri_ref(string: celtypes.Value) -> celpy.Result: + """Return True if the string is a URI Reference - a URI such as "https://example.com/foo/bar?baz=quux#frag" or a Relative Reference such as "./foo/bar?query". URI, URI Reference, and Relative Reference are defined in the internet standard RFC 3986. @@ -189,31 +170,131 @@ def is_uri_ref(string: celtypes.Value) -> celpy.Result: return celtypes.BoolType(valid) -def is_hostname(string: celtypes.Value) -> celpy.Result: - if not isinstance(string, celtypes.StringType): +def cel_is_hostname(val: celtypes.Value) -> celpy.Result: + """Returns True if the string is a valid hostname, for example "foo.example.com". + + A valid hostname follows the rules below: + - The name consists of one or more labels, separated by a dot ("."). + - Each label can be 1 to 63 alphanumeric characters. + - A label can contain hyphens ("-"), but must not start or end with a hyphen. + - The right-most label must not be digits only. + - The name can have a trailing dot, for example "foo.example.com.". + - The name can be 253 characters at most, excluding the optional trailing dot. + + """ + if not isinstance(val, celtypes.StringType): msg = "invalid argument, expected string" raise celpy.CELEvalError(msg) - return celtypes.BoolType(_validate_hostname(string)) + return celtypes.BoolType(_is_hostname(val)) + + +def _is_hostname(val: str) -> bool: + """Internal implementation""" + if len(val) > 253: + return False + + if val.endswith("."): + string = val[0 : len(val) - 1].lower() + else: + string = val.lower() + + all_digits = False + parts = string.lower().split(sep=".") + + # split hostname on '.' and validate each part + for part in parts: + all_digits = True + + # if part is empty, longer than 63 chars, or starts/ends with '-', it is invalid + part_len = len(part) + if part_len == 0 or part_len > 63 or part.startswith("-") or part.endswith("-"): + return False + + for c in part: + # if the character is not a-z, 0-9, or '-', it is invalid + if (c < "a" or c > "z") and (c < "0" or c > "9") and c != "-": + return False + + all_digits = all_digits and c >= "0" and c <= "9" + + # the last part cannot be all numbers + return not all_digits + + +def _is_port(val: str) -> bool: + if len(val) == 0: + return False + + for c in val: + if c < "0" or c > "9": + return False + + try: + return int(val) <= 65535 + + except ValueError: + # Error converting to number + return False + + +def cel_is_host_and_port(string: celtypes.Value, port_required: celtypes.Value) -> celpy.Result: + """Return True if the string is a valid host/port pair, for example "example.com:8080". -def is_host_and_port(string: celtypes.Value, port_required: celtypes.Value) -> celpy.Result: + If the argument `port_required` is True, the port is required. If the argument + is False, the port is optional. + + The host can be one of: + - An IPv4 address in dotted decimal format, for example "192.168.0.1". + - An IPv6 address enclosed in square brackets, for example "[::1]". + - A hostname, for example "example.com". + + The port is separated by a colon. It must be non-empty, with a decimal number in the range of 0-65535, inclusive. + """ if not isinstance(string, celtypes.StringType): msg = "invalid argument, expected string" raise celpy.CELEvalError(msg) if not isinstance(port_required, celtypes.BoolType): msg = "invalid argument, expected bool" raise celpy.CELEvalError(msg) - return celtypes.BoolType(validate_host_and_port(string, port_required=bool(port_required))) + return celtypes.BoolType(_is_host_and_port(string, port_required=bool(port_required))) + + +def _is_host_and_port(val: str, *, port_required=False) -> bool: + if len(val) == 0: + return False + split_idx = val.rfind(":") -def is_nan(val: celtypes.Value) -> celpy.Result: + if val[0] == "[": + end = val.rfind("]") + end_plus = end + 1 + + if end_plus == len(val): + return not port_required and _is_ip(val[1:end], 6) + elif end_plus == split_idx: + return _is_ip(val[1:end], 6) and _is_port(val[split_idx + 1 :]) + else: + # malformed + return False + + if split_idx < 0: + return not port_required and (_is_hostname(val) or _is_ip(val, 4)) + + host = val[0:split_idx] + port = val[split_idx + 1 :] + + return (_is_hostname(host) or _is_ip(host, 4)) and _is_port(port) + + +def cel_is_nan(val: celtypes.Value) -> celpy.Result: if not isinstance(val, celtypes.DoubleType): msg = "invalid argument, expected double" raise celpy.CELEvalError(msg) return celtypes.BoolType(math.isnan(val)) -def is_inf(val: celtypes.Value, sign: typing.Optional[celtypes.Value] = None) -> celpy.Result: +def cel_is_inf(val: celtypes.Value, sign: typing.Optional[celtypes.Value] = None) -> celpy.Result: if not isinstance(val, celtypes.DoubleType): msg = "invalid argument, expected double" raise celpy.CELEvalError(msg) @@ -231,13 +312,515 @@ def is_inf(val: celtypes.Value, sign: typing.Optional[celtypes.Value] = None) -> return celtypes.BoolType(math.isinf(val)) -def unique(val: celtypes.Value) -> celpy.Result: +def cel_unique(val: celtypes.Value) -> celpy.Result: if not isinstance(val, celtypes.ListType): msg = "invalid argument, expected list" raise celpy.CELEvalError(msg) return celtypes.BoolType(len(val) == len(set(val))) +class Ipv4: + """Ipv4 is a class used to parse a given string to determine if it is a valid IPv4 address or address prefix.""" + + _string: str + _index: int + _octets: bytearray + _prefix_len: int + + def __init__(self, string: str): + """Initialize an Ipv4 validation class with a given string.""" + + super().__init__() + self._string = string + self._index = 0 + self._octets = bytearray() + self._prefix_len = 0 + + def address(self) -> bool: + """Parses an IPv4 Address in dotted decimal notation.""" + return self.__address_part() and self._index == len(self._string) + + def address_prefix(self) -> bool: + """Parses an IPv4 Address prefix.""" + return ( + self.__address_part() and self.__take("/") and self.__prefix_length() and self._index == len(self._string) + ) + + def get_bits(self) -> int: + """Return the 32-bit value of an address parsed through address() or address_prefix(). + + Return -1 if no address was parsed successfully. + + """ + if len(self._octets) != 4: + return -1 + + return (self._octets[0] << 24) | (self._octets[1] << 16) | (self._octets[2] << 8) | self._octets[3] + + def is_prefix_only(self) -> bool: + """Return True if all bits to the right of the prefix-length are all zeros. + + Behavior is undefined if address_prefix() has not been called before, or has returned False. + + """ + bits = self.get_bits() + + mask: int + if self._prefix_len == 32: + mask = 0xFFFFFFFF + else: + mask = ~(0xFFFFFFFF >> self._prefix_len) + + masked = bits & mask + + return bits == masked + + def __prefix_length(self) -> bool: + """Store value in `prefix_len`""" + + start = self._index + + while True: + if self._index >= len(self._string) or not self.__digit(): + break + + if self._index - start > 2: + # max prefix-length is 32 bits, so anything more than 2 digits is invalid + return False + + string = self._string[start : self._index] + if len(string) == 0: + # too short + return False + + if len(string) > 1 and string[0] == "0": + # bad leading 0 + return False + + try: + value = int(string) + + if value > 32: + # max 32 bits + return False + + self._prefix_len = value + + return True + + except ValueError: + # Error converting to number + return False + + def __address_part(self) -> bool: + start = self._index + + if ( + self.__dec_octet() + and self.__take(".") + and self.__dec_octet() + and self.__take(".") + and self.__dec_octet() + and self.__take(".") + and self.__dec_octet() + ): + return True + + self._index = start + + return False + + def __dec_octet(self) -> bool: + start = self._index + + while True: + if self._index >= len(self._string) or not self.__digit(): + break + + if self._index - start > 3: + # decimal octet can be three characters at most + return False + + string = self._string[start : self._index] + + if len(string) == 0: + # too short + return False + + if len(string) > 1 and string[0] == "0": + # bad leading 0 + return False + + try: + value = int(string) + + if value > 255: + return False + + self._octets.append(value) + + return True + + except ValueError: + # Error converting to number + return False + + def __digit(self) -> bool: + """Report whether the current position is a digit. + + Method parses the rule: + + DIGIT = %x30-39 ; 0-9 + + """ + + if self._index >= len(self._string): + return False + + c = self._string[self._index] + if "0" <= c <= "9": + self._index += 1 + return True + + return False + + def __take(self, char: str) -> bool: + """Take the given char at the current index. + + If char is at the current index, increment the index. + + """ + if self._index >= len(self._string): + return False + + if self._string[self._index] == char: + self._index += 1 + return True + + return False + + +class Ipv6: + """Ipv6 is a class used to parse a given string to determine if it is a valid IPv6 address or address prefix.""" + + _string: str + _index: int + _pieces: list[int] # 16-bit pieces found + _double_colon_at: int # Number of 16-bit pieces found when double colon was found. + _double_colon_seen: bool + _dotted_raw: str # Dotted notation for right-most 32 bits. + _dotted_addr: typing.Optional[Ipv4] # Dotted notation successfully parsed as Ipv4. + _zone_id_found: bool + _prefix_len: int # 0 -128 + + def __init__(self, string: str): + """Initialize a URI validation class with a given string.""" + + super().__init__() + self._string = string + self._index = 0 + self._pieces = [] + self._double_colon_at = -1 + self._double_colon_seen = False + self._dotted_raw = "" + self._dotted_addr = None + self._zone_id_found = False + + def get_bits(self) -> int: + """Return the 128-bit value of an address parsed through address() or address_prefix(). + + Return 0 if no address was parsed successfully. + + """ + p16 = self._pieces + + # Handle dotted decimal, add to p16 + if self._dotted_addr is not None: + # Right-most 32 bits + dotted32 = self._dotted_addr.get_bits() + # High 16 bits + p16.append(dotted32 >> 16) + # Low 16 bits + p16.append(dotted32) + + # Handle double colon, fill pieces with 0 + if self._double_colon_seen: + while True: + if len(p16) >= 8: + break + + # Delete 0 entries at pos, insert a 0 + p16.insert(self._double_colon_at, 0x00000000) + + if len(p16) != 8: + return 0 + + return ( + p16[0] << 112 + | p16[1] << 96 + | p16[2] << 80 + | p16[3] << 64 + | p16[4] << 48 + | p16[5] << 32 + | p16[6] << 16 + | p16[7] + ) + + def is_prefix_only(self) -> bool: + """Return True if all bits to the right of the prefix-length are all zeros. + + Behavior is undefined if address_prefix() has not been called before, or has returned False. + + """ + bits = self.get_bits() + mask: int + if self._prefix_len >= 128: + mask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + elif self._prefix_len < 0: + mask = 0x00000000000000000000000000000000 + else: + mask = ~(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF >> self._prefix_len) + + masked = bits & mask + if bits != masked: + return False + + return True + + def address(self) -> bool: + """Parse an IPv6 Address following RFC 4291, with optional zone id following RFC 4007.""" + + return self.__address_part() and self._index == len(self._string) + + def address_prefix(self) -> bool: + """Parse an IPv6 Address Prefix following RFC 4291. Zone id is not permitted.""" + + return ( + self.__address_part() + and not self._zone_id_found + and self.__take("/") + and self.__prefix_length() + and self._index == len(self._string) + ) + + def __prefix_length(self) -> bool: + """Store value in `prefix_len`.""" + start = self._index + + while True: + if self._index >= len(self._string) or not self.__digit(): + break + + if self._index - start > 3: + return False + + string = self._string[start : self._index] + + if len(string) == 0: + # too short + return False + + if len(string) > 1 and string[0] == "0": + # bad leading 0 + return False + + try: + value = int(string) + + if value > 128: + # max 128 bits + return False + + self._prefix_len = value + + return True + + except ValueError: + # Error converting to number + return False + + def __address_part(self) -> bool: + """Store dotted notation for right-most 32 bits in dotted_raw / dotted_addr if found.""" + + while True: + if self._index >= len(self._string): + break + + # dotted notation for right-most 32 bits, e.g. 0:0:0:0:0:ffff:192.1.56.10 + if (self._double_colon_seen or len(self._pieces) == 6) and self.__dotted(): + dotted = Ipv4(self._dotted_raw) + + if dotted.address(): + self._dotted_addr = dotted + return True + + return False + + if self.__h16(): + continue + + if self.__take(":"): + if self.__take(":"): + if self._double_colon_seen: + return False + + self._double_colon_seen = True + self._double_colon_at = len(self._pieces) + + if self.__take(":"): + return False + + continue + + if self._string[self._index] == "%" and not self.__zone_id(): + return False + + break + + return self._double_colon_seen or len(self._pieces) == 8 + + def __zone_id(self) -> bool: + """Determine whether string contains a zoneID. + + There is no definition for the character set allowed in the zone + identifier. RFC 4007 permits basically any non-null string. + + RFC 6874: ZoneID = 1*( unreserved / pct-encoded ) + + """ + start = self._index + + if self.__take("%"): + if len(self._string) - self._index > 0: + # permit any non-null string + self._index = len(self._string) + self._zone_id_found = True + + return True + + self._index = start + self._zone_id_found = False + + return False + + def __dotted(self) -> bool: + """Determine whether string contains a dotted address. + + Method parses the rule: + + 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT + + Stores match in _dotted_raw. + """ + + start = self._index + self._dotted_raw = "" + + while True: + if self._index < len(self._string) and (self.__digit() or self.__take(".")): + continue + + break + + if self._index - start >= 7: + self._dotted_raw = self._string[start : self._index] + return True + + self._index = start + + return False + + def __h16(self) -> bool: + """Determine whether string contains an h16. + + Method parses the rule: + + h16 = 1*4HEXDIG + + Stores 16-bit value in _pieces. + """ + + start = self._index + + while True: + if self._index >= len(self._string) or not self.__hex_dig(): + break + + string = self._string[start : self._index] + + if len(string) == 0: + # too short + return False + + if len(string) > 4: + # too long + return False + + try: + value = int(string, 16) + + self._pieces.append(value) + + return True + + except ValueError: + # Error converting to number + return False + + def __hex_dig(self) -> bool: + """Report whether the current position is a hex digit. + + Method parses the rule: + + HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + + """ + if self._index >= len(self._string): + return False + + c = self._string[self._index] + + if ("0" <= c <= "9") or ("a" <= c <= "f") or ("A" <= c <= "F") or ("0" <= c <= "9"): + self._index += 1 + + return True + + return False + + def __digit(self) -> bool: + """Report whether the current position is a digit. + + Method parses the rule: + + DIGIT = %x30-39 ; 0-9 + + """ + if self._index >= len(self._string): + return False + + c = self._string[self._index] + if "0" <= c <= "9": + self._index += 1 + return True + + return False + + def __take(self, char: str) -> bool: + """Take the given char at the current index. + + If char is at the current index, increment the index. + + """ + if self._index >= len(self._string): + return False + + if self._string[self._index] == char: + self._index += 1 + return True + + return False + + class Uri: """Uri is a class used to parse a given string to determine if it is a valid URI or URI reference.""" @@ -531,14 +1114,14 @@ def __ipv6_address(self) -> bool: Method parses the rule "IPv6address". - Relies on the implementation of validate_ip. + Relies on the implementation of _is_ip. """ start = self._index while self.__hex_dig() or self.__take(":"): pass - if validate_ip(self._string[start : self._index], 6): + if _is_ip(self._string[start : self._index], 6): return True self._index = start @@ -861,7 +1444,7 @@ def __pct_encoded(self) -> bool: pct-encoded = "%" HEXDIG HEXDIG - Sets `_pct_encoded_found` to true if a valid triplet was found + Sets `_pct_encoded_found` to True if a valid triplet was found """ start = self._index @@ -974,6 +1557,7 @@ def __take(self, char: str) -> bool: """Take the given char at the current index. If char is at the current index, increment the index. + """ if self._index >= len(self._string): return False @@ -993,16 +1577,16 @@ def make_extra_funcs(locale: str) -> dict[str, celpy.CELFunction]: # Missing standard functions "format": string_fmt.format, # protovalidate specific functions - "isNan": is_nan, - "isInf": is_inf, - "isIp": is_ip, - "isIpPrefix": is_ip_prefix, - "isEmail": is_email, - "isUri": is_uri, - "isUriRef": is_uri_ref, - "isHostname": is_hostname, - "isHostAndPort": is_host_and_port, - "unique": unique, + "isNan": cel_is_nan, + "isInf": cel_is_inf, + "isIp": cel_is_ip, + "isIpPrefix": cel_is_ip_prefix, + "isEmail": cel_is_email, + "isUri": cel_is_uri, + "isUriRef": cel_is_uri_ref, + "isHostname": cel_is_hostname, + "isHostAndPort": cel_is_host_and_port, + "unique": cel_unique, } diff --git a/tests/conformance/nonconforming.yaml b/tests/conformance/nonconforming.yaml index 7ac7fe88..349f4e71 100644 --- a/tests/conformance/nonconforming.yaml +++ b/tests/conformance/nonconforming.yaml @@ -8,136 +8,13 @@ standard_constraints/well_known_types/timestamp: - gte_lte/invalid/above - lte/invalid -library/is_host_and_port: - - port_required/false/invalid/port_number_sign - # input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"example.com:+0"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_host_and_port" - # got: valid - - port_required/false/valid/ipv6_zone-id_any_non_null_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"[::1%% :x\x1f]"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_host_and_port" - # message: "" - # for_key: false - - port_required/true/invalid/port_number_sign - # input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"example.com:+0" port_required:true} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_host_and_port" - # got: valid -library/is_ip: - - version/0/valid/ipv4 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"127.0.0.1" version:0} - # want: valid - # got: runtime error: invalid argument, expected 4 or 6 - - version/0/valid/ipv6 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1" version:0} - # want: valid - # got: runtime error: invalid argument, expected 4 or 6 - - version/1/invalid/empty_string - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{version:1} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: runtime error: invalid argument, expected 4 or 6 - - version/1/invalid/ipv4 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"127.0.0.1" version:1} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: runtime error: invalid argument, expected 4 or 6 - - version/1/invalid/ipv6 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1" version:1} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: runtime error: invalid argument, expected 4 or 6 - - version/5/invalid/ipv4 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"127.0.0.1" version:5} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: runtime error: invalid argument, expected 4 or 6 - - version/5/invalid/ipv6 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1" version:5} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: runtime error: invalid argument, expected 4 or 6 - - version/7/invalid/ipv4 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"127.0.0.1" version:7} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: runtime error: invalid argument, expected 4 or 6 - - version/7/invalid/ipv6 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1" version:7} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: runtime error: invalid argument, expected 4 or 6 - - version/omitted/valid/ipv6_zone-id_any_non_null_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::1%% :x\x1f"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # message: "" - # for_key: false -library/is_ip_prefix: - - version/0/strict/omitted/valid/ipv4_prefix - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"127.0.0.1/16" version:0} - # want: valid - # got: runtime error: invalid argument, expected 4 or 6 - - version/0/strict/omitted/valid/ipv6_prefix - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"::1/64" version:0} - # want: valid - # got: runtime error: invalid argument, expected 4 or 6 - - version/1/strict/omitted/invalid/empty_string - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{version:1} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: runtime error: invalid argument, expected 4 or 6 - - version/5/strict/omitted/invalid/ipv6_prefix - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"::1/64" version:5} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: runtime error: invalid argument, expected 4 or 6 - - version/7/strict/omitted/invalid/ipv6_prefix - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"::1/64" version:7} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: runtime error: invalid argument, expected 4 or 6 - - version/omitted/strict/omitted/invalid/ipv4_bad_leading_zero_in_prefix-length - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"192.168.1.0/024"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: valid - - version/omitted/strict/omitted/invalid/ipv4_missing_prefix - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"192.168.1.0"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: valid - - version/omitted/strict/omitted/invalid/ipv6_bad_leading_zero_in_prefix-length - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF/024"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: valid - - version/omitted/strict/omitted/invalid/ipv6_missing_prefix - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: valid - - version/omitted/strict/omitted/invalid/ipv6_zone-id/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIpPrefix]:{val:"::1%en1/64"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip_prefix" - # got: valid standard_constraints/required: + # The below tests are failing due to a bug in the conformance runner. These + # proto messages are marked as IGNORE_ALWAYS which means they should always + # pass, which this implementation does correctly. However, the runner is + # expecting them to fail. + # See: + # https://github.com/bufbuild/protovalidate/blob/main/proto/protovalidate-testing/buf/validate/conformance/cases/required_field_proto2.proto#L24 + # https://github.com/bufbuild/protovalidate/blob/main/proto/protovalidate-testing/buf/validate/conformance/cases/required_field_proto2.proto#L31 - proto2/scalar/optional/unset - # input: [type.googleapis.com/buf.validate.conformance.cases.RequiredProto2ScalarOptional]:{} - # want: validation error (1 violation) - # 1. constraint_id: "required" - # field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_STRING} - # rule: "required" elements:{field_number:25 field_name:"required" field_type:TYPE_BOOL} - # got: valid - proto2/scalar/optional_with_default/unset - # input: [type.googleapis.com/buf.validate.conformance.cases.RequiredProto2ScalarOptionalDefault]:{} - # want: validation error (1 violation) - # 1. constraint_id: "required" - # field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_STRING} - # rule: "required" elements:{field_number:25 field_name:"required" field_type:TYPE_BOOL} - # got: valid From b272338581f8c7aae2c04681e536f3ca62ad4fd6 Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Fri, 4 Apr 2025 20:48:11 +0200 Subject: [PATCH 05/13] Fix docstrings in extra_func.py (#277) - Avoid ambiguity in "string contains" - Don't quote identifiers with backticks - KISS with "Parses the rule" Context: https://github.com/bufbuild/protovalidate-java/pull/257#discussion_r2028672192 Also see: https://github.com/bufbuild/protovalidate-go/pull/208 --- protovalidate/internal/extra_func.py | 163 +++++++++++++-------------- 1 file changed, 80 insertions(+), 83 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index 54f825a2..12f368fd 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -376,7 +376,7 @@ def is_prefix_only(self) -> bool: return bits == masked def __prefix_length(self) -> bool: - """Store value in `prefix_len`""" + """Store value in prefix_len""" start = self._index @@ -468,7 +468,7 @@ def __dec_octet(self) -> bool: def __digit(self) -> bool: """Report whether the current position is a digit. - Method parses the rule: + Parses the rule: DIGIT = %x30-39 ; 0-9 @@ -485,11 +485,8 @@ def __digit(self) -> bool: return False def __take(self, char: str) -> bool: - """Take the given char at the current index. - - If char is at the current index, increment the index. + """Take the given char at the current position, incrementing the index if necessary.""" - """ if self._index >= len(self._string): return False @@ -501,7 +498,7 @@ def __take(self, char: str) -> bool: class Ipv6: - """Ipv6 is a class used to parse a given string to determine if it is a valid IPv6 address or address prefix.""" + """Ipv6 is a class used to parse a given string to determine if it is a IPv6 address or address prefix.""" _string: str _index: int @@ -604,7 +601,7 @@ def address_prefix(self) -> bool: ) def __prefix_length(self) -> bool: - """Store value in `prefix_len`.""" + """Store value in prefix_len.""" start = self._index while True: @@ -680,7 +677,7 @@ def __address_part(self) -> bool: return self._double_colon_seen or len(self._pieces) == 8 def __zone_id(self) -> bool: - """Determine whether string contains a zoneID. + """Determine whether the current position is a zoneID. There is no definition for the character set allowed in the zone identifier. RFC 4007 permits basically any non-null string. @@ -704,9 +701,9 @@ def __zone_id(self) -> bool: return False def __dotted(self) -> bool: - """Determine whether string contains a dotted address. + """Determine whether the current position is a dotted address. - Method parses the rule: + Parses the rule: 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT @@ -731,9 +728,9 @@ def __dotted(self) -> bool: return False def __h16(self) -> bool: - """Determine whether string contains an h16. + """Determine whether the current position is a h16. - Method parses the rule: + Parses the rule: h16 = 1*4HEXDIG @@ -768,9 +765,9 @@ def __h16(self) -> bool: return False def __hex_dig(self) -> bool: - """Report whether the current position is a hex digit. + """Determine whether the current position is a hex digit. - Method parses the rule: + Parses the rule: HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" @@ -788,9 +785,9 @@ def __hex_dig(self) -> bool: return False def __digit(self) -> bool: - """Report whether the current position is a digit. + """Determine whether the current position is a digit. - Method parses the rule: + Parses the rule: DIGIT = %x30-39 ; 0-9 @@ -835,9 +832,9 @@ def __init__(self, string: str): self._index = 0 def uri(self) -> bool: - """Determine whether string is a valid URI. + """Determine whether _string is a URI. - Method parses the rule: + Parses the rule: URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] @@ -860,9 +857,9 @@ def uri(self) -> bool: return True def uri_reference(self) -> bool: - """Determine whether string is a valid URI reference. + """Determine whether _string is a URI reference. - Method parses the rule: + Parses the rule: URI-reference = URI / relative-ref @@ -870,9 +867,9 @@ def uri_reference(self) -> bool: return self.uri() or self.__relative_ref() def __hier_part(self) -> bool: - """Determine whether string contains a valid hier-part. + """Determine whether the current position is a hier-part. - Method parses the rule: + Parses the rule: hier-part = "//" authority path-abempty. / path-absolute @@ -889,9 +886,9 @@ def __hier_part(self) -> bool: return self.__path_absolute() or self.__path_rootless() or self.__path_empty() def __relative_ref(self) -> bool: - """Determine whether string contains a valid relative reference. + """Determine whether the current position is a relative reference. - Method parses the rule: + Parses the rule: relative-ref = relative-part [ "?" query ] [ "#" fragment ] @@ -915,9 +912,9 @@ def __relative_ref(self) -> bool: return True def __relative_part(self) -> bool: - """Determine whether string contains a valid relative part. + """Determine whether the current position is a relative part. - Method parses the rule: + Parses the rule: relative-part = "//" authority path-abempty / path-absolute @@ -934,9 +931,9 @@ def __relative_part(self) -> bool: return self.__path_absolute() or self.__path_noscheme() or self.__path_empty() def __scheme(self) -> bool: - """Determine whether string contains a valid scheme. + """Determine whether the current position is a scheme. - Method parses the rule: + Parses the rule: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) @@ -955,9 +952,9 @@ def __scheme(self) -> bool: return False def __authority(self) -> bool: - """Determine whether string contains a valid authority. + """Determine whether the current position is an authority. - Method parses the rule: + Parses the rule: authority = [ userinfo "@" ] host [ ":" port ] @@ -1001,9 +998,9 @@ def __is_authority_end(self) -> bool: ) def __userinfo(self) -> bool: - """Determine whether string contains a valid userinfo. + """Determine whether the current position is a userinfo. - Method parses the rule: + Parses the rule: userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) @@ -1033,9 +1030,9 @@ def __check_host_pct_encoded(self, string: str) -> bool: return True def __host(self) -> bool: - """Determine whether string contains a valid host. + """Determine whether the current position is a host. - Method parses the rule: + Parses the rule: host = IP-literal / IPv4address / reg-name. @@ -1061,9 +1058,9 @@ def __host(self) -> bool: return False def __port(self) -> bool: - """Determine whether string contains a valid port. + """Determine whether the current position is a port. - Method parses the rule: + Parses the rule: port = *DIGIT @@ -1082,9 +1079,9 @@ def __port(self) -> bool: return False def __ip_literal(self) -> bool: - """Determine whether string contains a valid port. + """Determine whether the current position is a IP-literal. - Method parses the rule from RFC 6874: + Parses the rule from RFC 6874: IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]" @@ -1110,9 +1107,9 @@ def __ip_literal(self) -> bool: return False def __ipv6_address(self) -> bool: - """Determine whether string contains a valid ipv6 address. + """Determine whether the current position is a IPv6address. - Method parses the rule "IPv6address". + Parses the rule "IPv6address". Relies on the implementation of _is_ip. @@ -1128,9 +1125,9 @@ def __ipv6_address(self) -> bool: return False def __ipv6_addrz(self) -> bool: - """Determine whether string contains a valid IPv6addrz. + """Determine whether the current position is a IPv6addrz. - Method parses the rule from RFC 6874: + Parses the rule from RFC 6874: IPv6addrz = IPv6address "%25" ZoneID @@ -1144,9 +1141,9 @@ def __ipv6_addrz(self) -> bool: return False def __zone_id(self) -> bool: - """Determine whether string contains a valid zone ID. + """Determine whether the current position is a ZoneID. - Method parses the rule from RFC 6874: + Parses the rule from RFC 6874: ZoneID = 1*( unreserved / pct-encoded ) @@ -1163,9 +1160,9 @@ def __zone_id(self) -> bool: return False def __ip_vfuture(self) -> bool: - """Determine whether string contains a valid ipvFuture. + """Determine whether the current position is a IPvFuture. - Method parses the rule: + Parses the rule: IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) @@ -1189,9 +1186,9 @@ def __ip_vfuture(self) -> bool: return False def __reg_name(self) -> bool: - """Determine whether string contains a valid reg-name. + """Determine whether the current position is a reg-name. - Method parses the rule: + Parses the rule: reg-name = *( unreserved / pct-encoded / sub-delims ) @@ -1224,9 +1221,9 @@ def __is_path_end(self) -> bool: return self._index >= len(self._string) or self._string[self._index] == "?" or self._string[self._index] == "#" def __path_abempty(self) -> bool: - """Determine whether string contains a path-abempty. + """Determine whether the current position is a path-abempty. - Method parses the rule: + Parses the rule: path-abempty = *( "/" segment ) @@ -1245,9 +1242,9 @@ def __path_abempty(self) -> bool: return False def __path_absolute(self) -> bool: - """Determine whether string contains a path-absolute. + """Determine whether the current position is a path-absolute. - Method parses the rule: + Parses the rule: path-absolute = "/" [ segment-nz *( "/" segment ) ] @@ -1269,9 +1266,9 @@ def __path_absolute(self) -> bool: return False def __path_noscheme(self) -> bool: - """Determine whether string contains a path-noscheme. + """Determine whether the current position is a path-noscheme. - Method parses the rule: + Parses the rule: path-noscheme = segment-nz-nc *( "/" segment ) @@ -1291,9 +1288,9 @@ def __path_noscheme(self) -> bool: return True def __path_rootless(self) -> bool: - """Determine whether string contains a path-rootless. + """Determine whether the current position is a path-rootless. - Method parses the rule: + Parses the rule: path-rootless = segment-nz *( "/" segment ) @@ -1314,9 +1311,9 @@ def __path_rootless(self) -> bool: return True def __path_empty(self) -> bool: - """Determine whether string contains a path-empty. + """Determine whether the current position is a path-empty. - Method parses the rule: + Parses the rule: path-empty = 0 @@ -1326,9 +1323,9 @@ def __path_empty(self) -> bool: return self.__is_path_end() def __segment(self) -> bool: - """Determine whether string contains a segment. + """Determine whether the current position is a segment. - Method parses the rule: + Parses the rule: segment = *pchar @@ -1339,9 +1336,9 @@ def __segment(self) -> bool: return True def __segment_nz(self) -> bool: - """Determine whether string contains a segment-nz. + """Determine whether the current position is a segment-nz. - Method parses the rule: + Parses the rule: segment-nz = 1*pchar @@ -1359,9 +1356,9 @@ def __segment_nz(self) -> bool: return False def __segment_nz_nc(self) -> bool: - """Determine whether string contains a segment-nz-nc. + """Determine whether the current position is a segment-nz-nc. - Method parses the rule: + Parses the rule: segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) ; non-zero-length segment without any colon ":" @@ -1380,9 +1377,9 @@ def __segment_nz_nc(self) -> bool: return False def __pchar(self) -> bool: - """Report whether the current position is a pchar. + """Determine whether the current position is a pchar. - Method parses the rule: + Parses the rule: pchar = unreserved / pct-encoded / sub-delims / ":" / "@" @@ -1392,9 +1389,9 @@ def __pchar(self) -> bool: ) def __query(self) -> bool: - """Determine whether string contains a valid query. + """Determine whether the current position is a valid query. - Method parses the rule: + Parses the rule: query = *( pchar / "/" / "?" ) @@ -1415,9 +1412,9 @@ def __query(self) -> bool: return False def __fragment(self) -> bool: - """Determine whether string contains a valid fragment. + """Determine whether the current position is a fragment. - Method parses the rule: + Parses the rule: fragment = *( pchar / "/" / "?" ) @@ -1438,9 +1435,9 @@ def __fragment(self) -> bool: return False def __pct_encoded(self) -> bool: - """Determine whether string contains a valid percent encoding. + """Determine whether the current position is a pct-encoded. - Method parses the rule: + Parses the rule: pct-encoded = "%" HEXDIG HEXDIG @@ -1458,9 +1455,9 @@ def __pct_encoded(self) -> bool: return False def __unreserved(self) -> bool: - """Report whether the current position is an unreserved character. + """Determine whether the current position is a unreserved character. - Method parses the rule: + Parses the rule: unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" @@ -1475,9 +1472,9 @@ def __unreserved(self) -> bool: ) def __sub_delims(self) -> bool: - """Report whether the current position is a sub-delim. + """Determine whether the current position is a sub-delim. - Method parses the rule: + Parses the rule: sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" @@ -1498,9 +1495,9 @@ def __sub_delims(self) -> bool: ) def __alpha(self) -> bool: - """Report whether the current position is an alpha character. + """Determine whether the current position is an alpha character. - Method parses the rule: + Parses the rule: ALPHA = %x41-5A / %x61-7A ; A-Z / a-z @@ -1516,9 +1513,9 @@ def __alpha(self) -> bool: return False def __digit(self) -> bool: - """Report whether the current position is a digit. + """Determine whether the current position is a digit. - Method parses the rule: + Parses the rule: DIGIT = %x30-39 ; 0-9 @@ -1534,9 +1531,9 @@ def __digit(self) -> bool: return False def __hex_dig(self) -> bool: - """Report whether the current position is a hex digit. + """Determine whether the current position is a hex digit. - Method parses the rule: + Parses the rule: HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" From dbdecb02be129354569e144422101b454400ebd4 Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Wed, 9 Apr 2025 18:05:46 +0200 Subject: [PATCH 06/13] Catch all exceptions in conformance runner.py (#282) --- tests/conformance/nonconforming.yaml | 22 ++++++++++++++++++++++ tests/conformance/runner.py | 2 ++ 2 files changed, 24 insertions(+) diff --git a/tests/conformance/nonconforming.yaml b/tests/conformance/nonconforming.yaml index 25744e50..fb2876bb 100644 --- a/tests/conformance/nonconforming.yaml +++ b/tests/conformance/nonconforming.yaml @@ -7,3 +7,25 @@ standard_constraints/well_known_types/duration: standard_constraints/well_known_types/timestamp: - gte_lte/invalid/above - lte/invalid + +library/is_ip: + - version/omitted/invalid/ipv6/g + # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:":0::0"} + # want: validation error (1 violation) + # 1. constraint_id: "library.is_ip" + # got: valid + - version/omitted/invalid/ipv6/h + # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"0::0:"} + # want: validation error (1 violation) + # 1. constraint_id: "library.is_ip" + # got: valid + - version/omitted/invalid/ipv6/i + # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"0::0:"} + # want: validation error (1 violation) + # 1. constraint_id: "library.is_ip" + # got: valid + - version/omitted/invalid/ipv6/j + # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::0000ffff"} + # want: validation error (1 violation) + # 1. constraint_id: "library.is_ip" + # got: unexpected error: string index out of range diff --git a/tests/conformance/runner.py b/tests/conformance/runner.py index 7bd6a4c1..392e97f2 100644 --- a/tests/conformance/runner.py +++ b/tests/conformance/runner.py @@ -72,6 +72,8 @@ def run_test_case(tc: typing.Any, result: typing.Optional[harness_pb2.TestResult result.runtime_error = str(e) except protovalidate.CompilationError as e: result.compilation_error = str(e) + except Exception as e: + result.unexpected_error = str(e) return result From 9b4e69c5b870cd6bda37e5892ed2143c549b8422 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Wed, 9 Apr 2025 12:25:43 -0400 Subject: [PATCH 07/13] Fix ipv6 validation corner cases (#281) This ports the ipv6 fix from https://github.com/bufbuild/protovalidate-go/pull/215. For context see the description on the above PR. The summary is that this fixes the validation for some corner cases of IPv6 address validation. Namely: * Adds a check that an IPv6 address can't begin or end on a single colon. * Adds a check to fail-fast on invalid hextets. --- protovalidate/internal/extra_func.py | 37 ++++++++++++++++++---------- tests/conformance/nonconforming.yaml | 22 ----------------- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index 12f368fd..d633dba4 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -653,8 +653,11 @@ def __address_part(self) -> bool: return False - if self.__h16(): - continue + try: + if self.__h16(): + continue + except ValueError: + return False if self.__take(":"): if self.__take(":"): @@ -666,6 +669,9 @@ def __address_part(self) -> bool: if self.__take(":"): return False + elif self._index == 1 or self._index == len(self._string): + # invalid - string cannot start or end on single colon + return False continue @@ -734,7 +740,11 @@ def __h16(self) -> bool: h16 = 1*4HEXDIG - Stores 16-bit value in _pieces. + If 1-4 hex digits are found, the parsed 16-bit unsigned integer is stored + in pieces and True is returned. + If 0 hex digits are found, returns False. + If more than 4 hex digits are found or the found hex digits cannot be + converted to an int, a ValueError is raised. """ start = self._index @@ -746,23 +756,24 @@ def __h16(self) -> bool: string = self._string[start : self._index] if len(string) == 0: - # too short + # too short, just return false + # this is not an error condition, it just means we didn't find any + # hex digits at the current position. return False if len(string) > 4: # too long - return False - - try: - value = int(string, 16) + # this is an error condition, it means we found a string of more than + # four valid hex digits, which is invalid in ipv6 addresses. + raise ValueError - self._pieces.append(value) + # Note that this will raise a ValueError also if string cannot be + # converted to an int. + value = int(string, 16) - return True + self._pieces.append(value) - except ValueError: - # Error converting to number - return False + return True def __hex_dig(self) -> bool: """Determine whether the current position is a hex digit. diff --git a/tests/conformance/nonconforming.yaml b/tests/conformance/nonconforming.yaml index fb2876bb..25744e50 100644 --- a/tests/conformance/nonconforming.yaml +++ b/tests/conformance/nonconforming.yaml @@ -7,25 +7,3 @@ standard_constraints/well_known_types/duration: standard_constraints/well_known_types/timestamp: - gte_lte/invalid/above - lte/invalid - -library/is_ip: - - version/omitted/invalid/ipv6/g - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:":0::0"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: valid - - version/omitted/invalid/ipv6/h - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"0::0:"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: valid - - version/omitted/invalid/ipv6/i - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"0::0:"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: valid - - version/omitted/invalid/ipv6/j - # input: [type.googleapis.com/buf.validate.conformance.cases.IsIp]:{val:"::0000ffff"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_ip" - # got: unexpected error: string index out of range From 3f03855454260dea4d9be7f0953125ab9ee88b27 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Mon, 14 Apr 2025 18:48:10 -0400 Subject: [PATCH 08/13] Update protovalidate/internal/extra_func.py Co-authored-by: Philip K. Warren --- protovalidate/internal/extra_func.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index d633dba4..ba8546fe 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -788,7 +788,7 @@ def __hex_dig(self) -> bool: c = self._string[self._index] - if ("0" <= c <= "9") or ("a" <= c <= "f") or ("A" <= c <= "F") or ("0" <= c <= "9"): + if ("0" <= c <= "9") or ("a" <= c <= "f") or ("A" <= c <= "F"): self._index += 1 return True From aef7b62ea2454c2ef136d6ff1a667ca4134251b1 Mon Sep 17 00:00:00 2001 From: "Philip K. Warren" Date: Tue, 15 Apr 2025 09:41:19 -0500 Subject: [PATCH 09/13] Regenerate code and some cleanups to cel funcs (#284) Update generated code to match runtime requirement (6.30.1). Make some small fixes to custom CEL functions. --- buf.gen.yaml | 4 +- .../validate/conformance/cases/bool_pb2.py | 8 +- .../validate/conformance/cases/bytes_pb2.py | 8 +- .../custom_constraints_pb2.py | 8 +- .../custom_constraints_pb2.pyi | 3 +- .../validate/conformance/cases/enums_pb2.py | 8 +- .../validate/conformance/cases/enums_pb2.pyi | 3 +- .../cases/filename_with_dash_pb2.py | 8 +- .../cases/ignore_empty_proto2_pb2.py | 8 +- .../cases/ignore_empty_proto2_pb2.pyi | 3 +- .../cases/ignore_empty_proto3_pb2.py | 8 +- .../cases/ignore_empty_proto3_pb2.pyi | 3 +- .../cases/ignore_empty_proto_editions_pb2.py | 8 +- .../cases/ignore_empty_proto_editions_pb2.pyi | 3 +- .../conformance/cases/ignore_proto2_pb2.py | 8 +- .../conformance/cases/ignore_proto2_pb2.pyi | 3 +- .../conformance/cases/ignore_proto3_pb2.py | 8 +- .../conformance/cases/ignore_proto3_pb2.pyi | 3 +- .../cases/ignore_proto_editions_pb2.py | 8 +- .../cases/ignore_proto_editions_pb2.pyi | 3 +- .../conformance/cases/kitchen_sink_pb2.py | 8 +- .../conformance/cases/kitchen_sink_pb2.pyi | 3 +- .../validate/conformance/cases/library_pb2.py | 8 +- .../validate/conformance/cases/maps_pb2.py | 8 +- .../validate/conformance/cases/maps_pb2.pyi | 3 +- .../conformance/cases/messages_pb2.py | 8 +- .../conformance/cases/messages_pb2.pyi | 3 +- .../validate/conformance/cases/numbers_pb2.py | 8 +- .../validate/conformance/cases/oneofs_pb2.py | 8 +- .../validate/conformance/cases/oneofs_pb2.pyi | 3 +- .../cases/other_package/embed_pb2.py | 8 +- .../cases/predefined_rules_proto2_pb2.py | 8 +- .../cases/predefined_rules_proto2_pb2.pyi | 3 +- .../cases/predefined_rules_proto3_pb2.py | 8 +- .../cases/predefined_rules_proto3_pb2.pyi | 3 +- .../predefined_rules_proto_editions_pb2.py | 8 +- .../predefined_rules_proto_editions_pb2.pyi | 3 +- .../conformance/cases/repeated_pb2.py | 8 +- .../conformance/cases/repeated_pb2.pyi | 3 +- .../cases/required_field_proto2_pb2.py | 8 +- .../cases/required_field_proto2_pb2.pyi | 3 +- .../cases/required_field_proto3_pb2.py | 8 +- .../cases/required_field_proto3_pb2.pyi | 3 +- .../required_field_proto_editions_pb2.py | 8 +- .../required_field_proto_editions_pb2.pyi | 3 +- .../validate/conformance/cases/strings_pb2.py | 8 +- .../cases/subdirectory/in_subdirectory_pb2.py | 8 +- .../validate/conformance/cases/wkt_any_pb2.py | 8 +- .../conformance/cases/wkt_any_pb2.pyi | 3 +- .../conformance/cases/wkt_duration_pb2.py | 8 +- .../conformance/cases/wkt_duration_pb2.pyi | 3 +- .../conformance/cases/wkt_nested_pb2.py | 8 +- .../conformance/cases/wkt_nested_pb2.pyi | 3 +- .../conformance/cases/wkt_timestamp_pb2.py | 8 +- .../conformance/cases/wkt_timestamp_pb2.pyi | 3 +- .../conformance/cases/wkt_wrappers_pb2.py | 8 +- .../conformance/cases/wkt_wrappers_pb2.pyi | 3 +- .../cases/yet_another_package/embed2_pb2.py | 8 +- .../conformance/harness/harness_pb2.py | 8 +- .../conformance/harness/harness_pb2.pyi | 3 +- .../conformance/harness/results_pb2.py | 8 +- .../conformance/harness/results_pb2.pyi | 3 +- gen/buf/validate/validate_pb2.py | 8 +- gen/buf/validate/validate_pb2.pyi | 3 +- protovalidate/internal/extra_func.py | 138 +++++++----------- 65 files changed, 250 insertions(+), 261 deletions(-) diff --git a/buf.gen.yaml b/buf.gen.yaml index 3eaf8521..52e23141 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -2,7 +2,7 @@ version: v2 managed: enabled: true plugins: - - remote: buf.build/protocolbuffers/python:v29.3 + - remote: buf.build/protocolbuffers/python:v30.1 out: gen - - remote: buf.build/protocolbuffers/pyi:v29.3 + - remote: buf.build/protocolbuffers/pyi:v30.1 out: gen diff --git a/gen/buf/validate/conformance/cases/bool_pb2.py b/gen/buf/validate/conformance/cases/bool_pb2.py index 86d58c91..c37bde2d 100644 --- a/gen/buf/validate/conformance/cases/bool_pb2.py +++ b/gen/buf/validate/conformance/cases/bool_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/bool.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/bool.proto' ) diff --git a/gen/buf/validate/conformance/cases/bytes_pb2.py b/gen/buf/validate/conformance/cases/bytes_pb2.py index dd02362c..7465ea1a 100644 --- a/gen/buf/validate/conformance/cases/bytes_pb2.py +++ b/gen/buf/validate/conformance/cases/bytes_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/bytes.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/bytes.proto' ) diff --git a/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.py b/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.py index 68e4c1d2..f65ec675 100644 --- a/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.py +++ b/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/custom_constraints/custom_constraints.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/custom_constraints/custom_constraints.proto' ) diff --git a/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.pyi b/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.pyi index 06e7a75a..8c26b165 100644 --- a/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.pyi +++ b/gen/buf/validate/conformance/cases/custom_constraints/custom_constraints_pb2.pyi @@ -17,7 +17,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/enums_pb2.py b/gen/buf/validate/conformance/cases/enums_pb2.py index d54a031a..5d7966b6 100644 --- a/gen/buf/validate/conformance/cases/enums_pb2.py +++ b/gen/buf/validate/conformance/cases/enums_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/enums.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/enums.proto' ) diff --git a/gen/buf/validate/conformance/cases/enums_pb2.pyi b/gen/buf/validate/conformance/cases/enums_pb2.pyi index 4f6eb993..122a3356 100644 --- a/gen/buf/validate/conformance/cases/enums_pb2.pyi +++ b/gen/buf/validate/conformance/cases/enums_pb2.pyi @@ -19,7 +19,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/filename_with_dash_pb2.py b/gen/buf/validate/conformance/cases/filename_with_dash_pb2.py index a6978e33..7db6af47 100644 --- a/gen/buf/validate/conformance/cases/filename_with_dash_pb2.py +++ b/gen/buf/validate/conformance/cases/filename_with_dash_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/filename-with-dash.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/filename-with-dash.proto' ) diff --git a/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.py b/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.py index 1784abe9..6cb042ab 100644 --- a/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.py +++ b/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/ignore_empty_proto2.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/ignore_empty_proto2.proto' ) diff --git a/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.pyi b/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.pyi index 066d77db..4885b64e 100644 --- a/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.pyi +++ b/gen/buf/validate/conformance/cases/ignore_empty_proto2_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.py b/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.py index c3372108..6ff055a2 100644 --- a/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.py +++ b/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/ignore_empty_proto3.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/ignore_empty_proto3.proto' ) diff --git a/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.pyi b/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.pyi index 416137f8..2044c560 100644 --- a/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.pyi +++ b/gen/buf/validate/conformance/cases/ignore_empty_proto3_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.py b/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.py index dc48e8cf..67028485 100644 --- a/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.py +++ b/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/ignore_empty_proto_editions.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/ignore_empty_proto_editions.proto' ) diff --git a/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.pyi b/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.pyi index e02504ad..13de7337 100644 --- a/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.pyi +++ b/gen/buf/validate/conformance/cases/ignore_empty_proto_editions_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/ignore_proto2_pb2.py b/gen/buf/validate/conformance/cases/ignore_proto2_pb2.py index 3cff91c1..51d7b8cb 100644 --- a/gen/buf/validate/conformance/cases/ignore_proto2_pb2.py +++ b/gen/buf/validate/conformance/cases/ignore_proto2_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/ignore_proto2.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/ignore_proto2.proto' ) diff --git a/gen/buf/validate/conformance/cases/ignore_proto2_pb2.pyi b/gen/buf/validate/conformance/cases/ignore_proto2_pb2.pyi index 041e2497..aaaf6707 100644 --- a/gen/buf/validate/conformance/cases/ignore_proto2_pb2.pyi +++ b/gen/buf/validate/conformance/cases/ignore_proto2_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/ignore_proto3_pb2.py b/gen/buf/validate/conformance/cases/ignore_proto3_pb2.py index 3afc4712..e1ebdff7 100644 --- a/gen/buf/validate/conformance/cases/ignore_proto3_pb2.py +++ b/gen/buf/validate/conformance/cases/ignore_proto3_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/ignore_proto3.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/ignore_proto3.proto' ) diff --git a/gen/buf/validate/conformance/cases/ignore_proto3_pb2.pyi b/gen/buf/validate/conformance/cases/ignore_proto3_pb2.pyi index f7715ac1..96179e7f 100644 --- a/gen/buf/validate/conformance/cases/ignore_proto3_pb2.pyi +++ b/gen/buf/validate/conformance/cases/ignore_proto3_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.py b/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.py index aaa46363..b4e0a1ad 100644 --- a/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.py +++ b/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/ignore_proto_editions.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/ignore_proto_editions.proto' ) diff --git a/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.pyi b/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.pyi index daea8881..4a04dc4e 100644 --- a/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.pyi +++ b/gen/buf/validate/conformance/cases/ignore_proto_editions_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/kitchen_sink_pb2.py b/gen/buf/validate/conformance/cases/kitchen_sink_pb2.py index e3ff2496..4549f7d2 100644 --- a/gen/buf/validate/conformance/cases/kitchen_sink_pb2.py +++ b/gen/buf/validate/conformance/cases/kitchen_sink_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/kitchen_sink.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/kitchen_sink.proto' ) diff --git a/gen/buf/validate/conformance/cases/kitchen_sink_pb2.pyi b/gen/buf/validate/conformance/cases/kitchen_sink_pb2.pyi index 880f4117..db56160b 100644 --- a/gen/buf/validate/conformance/cases/kitchen_sink_pb2.pyi +++ b/gen/buf/validate/conformance/cases/kitchen_sink_pb2.pyi @@ -21,7 +21,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/library_pb2.py b/gen/buf/validate/conformance/cases/library_pb2.py index dda813f8..c56e3e9b 100644 --- a/gen/buf/validate/conformance/cases/library_pb2.py +++ b/gen/buf/validate/conformance/cases/library_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/library.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/library.proto' ) diff --git a/gen/buf/validate/conformance/cases/maps_pb2.py b/gen/buf/validate/conformance/cases/maps_pb2.py index 6b0c76e0..8b459584 100644 --- a/gen/buf/validate/conformance/cases/maps_pb2.py +++ b/gen/buf/validate/conformance/cases/maps_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/maps.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/maps.proto' ) diff --git a/gen/buf/validate/conformance/cases/maps_pb2.pyi b/gen/buf/validate/conformance/cases/maps_pb2.pyi index 57fe562d..5c69543a 100644 --- a/gen/buf/validate/conformance/cases/maps_pb2.pyi +++ b/gen/buf/validate/conformance/cases/maps_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/messages_pb2.py b/gen/buf/validate/conformance/cases/messages_pb2.py index b16b68a8..d8ed573a 100644 --- a/gen/buf/validate/conformance/cases/messages_pb2.py +++ b/gen/buf/validate/conformance/cases/messages_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/messages.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/messages.proto' ) diff --git a/gen/buf/validate/conformance/cases/messages_pb2.pyi b/gen/buf/validate/conformance/cases/messages_pb2.pyi index 27b7d8fc..64deb5ed 100644 --- a/gen/buf/validate/conformance/cases/messages_pb2.pyi +++ b/gen/buf/validate/conformance/cases/messages_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate.conformance.cases.other_package import embed_pb2 as _embed_pb2 from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/numbers_pb2.py b/gen/buf/validate/conformance/cases/numbers_pb2.py index 8afaea86..25f5da71 100644 --- a/gen/buf/validate/conformance/cases/numbers_pb2.py +++ b/gen/buf/validate/conformance/cases/numbers_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/numbers.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/numbers.proto' ) diff --git a/gen/buf/validate/conformance/cases/oneofs_pb2.py b/gen/buf/validate/conformance/cases/oneofs_pb2.py index 1c9e08a7..a98d72ab 100644 --- a/gen/buf/validate/conformance/cases/oneofs_pb2.py +++ b/gen/buf/validate/conformance/cases/oneofs_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/oneofs.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/oneofs.proto' ) diff --git a/gen/buf/validate/conformance/cases/oneofs_pb2.pyi b/gen/buf/validate/conformance/cases/oneofs_pb2.pyi index cde307b7..0b80158b 100644 --- a/gen/buf/validate/conformance/cases/oneofs_pb2.pyi +++ b/gen/buf/validate/conformance/cases/oneofs_pb2.pyi @@ -15,7 +15,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/other_package/embed_pb2.py b/gen/buf/validate/conformance/cases/other_package/embed_pb2.py index 80a6c843..686c3384 100644 --- a/gen/buf/validate/conformance/cases/other_package/embed_pb2.py +++ b/gen/buf/validate/conformance/cases/other_package/embed_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/other_package/embed.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/other_package/embed.proto' ) diff --git a/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.py b/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.py index 4b7c400f..7ca7fb12 100644 --- a/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.py +++ b/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/predefined_rules_proto2.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/predefined_rules_proto2.proto' ) diff --git a/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.pyi b/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.pyi index b1ca825f..20f9ffa4 100644 --- a/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.pyi +++ b/gen/buf/validate/conformance/cases/predefined_rules_proto2_pb2.pyi @@ -20,7 +20,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor FLOAT_ABS_RANGE_PROTO2_FIELD_NUMBER: _ClassVar[int] diff --git a/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.py b/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.py index f863779d..729fd38f 100644 --- a/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.py +++ b/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/predefined_rules_proto3.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/predefined_rules_proto3.proto' ) diff --git a/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.pyi b/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.pyi index aaef8e0d..7049a3b2 100644 --- a/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.pyi +++ b/gen/buf/validate/conformance/cases/predefined_rules_proto3_pb2.pyi @@ -22,7 +22,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.py b/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.py index fab2ae91..c12615af 100644 --- a/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.py +++ b/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/predefined_rules_proto_editions.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/predefined_rules_proto_editions.proto' ) diff --git a/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.pyi b/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.pyi index a84ee9c5..59255583 100644 --- a/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.pyi +++ b/gen/buf/validate/conformance/cases/predefined_rules_proto_editions_pb2.pyi @@ -20,7 +20,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor FLOAT_ABS_RANGE_EDITION_2023_FIELD_NUMBER: _ClassVar[int] diff --git a/gen/buf/validate/conformance/cases/repeated_pb2.py b/gen/buf/validate/conformance/cases/repeated_pb2.py index e69162c9..36c8a5c1 100644 --- a/gen/buf/validate/conformance/cases/repeated_pb2.py +++ b/gen/buf/validate/conformance/cases/repeated_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/repeated.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/repeated.proto' ) diff --git a/gen/buf/validate/conformance/cases/repeated_pb2.pyi b/gen/buf/validate/conformance/cases/repeated_pb2.pyi index 349ee93b..f59d7e0f 100644 --- a/gen/buf/validate/conformance/cases/repeated_pb2.pyi +++ b/gen/buf/validate/conformance/cases/repeated_pb2.pyi @@ -20,7 +20,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/required_field_proto2_pb2.py b/gen/buf/validate/conformance/cases/required_field_proto2_pb2.py index 3888f3cd..bdcabc1a 100644 --- a/gen/buf/validate/conformance/cases/required_field_proto2_pb2.py +++ b/gen/buf/validate/conformance/cases/required_field_proto2_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/required_field_proto2.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/required_field_proto2.proto' ) diff --git a/gen/buf/validate/conformance/cases/required_field_proto2_pb2.pyi b/gen/buf/validate/conformance/cases/required_field_proto2_pb2.pyi index ee398420..5b0c6a76 100644 --- a/gen/buf/validate/conformance/cases/required_field_proto2_pb2.pyi +++ b/gen/buf/validate/conformance/cases/required_field_proto2_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/required_field_proto3_pb2.py b/gen/buf/validate/conformance/cases/required_field_proto3_pb2.py index ca360a5a..f3003576 100644 --- a/gen/buf/validate/conformance/cases/required_field_proto3_pb2.py +++ b/gen/buf/validate/conformance/cases/required_field_proto3_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/required_field_proto3.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/required_field_proto3.proto' ) diff --git a/gen/buf/validate/conformance/cases/required_field_proto3_pb2.pyi b/gen/buf/validate/conformance/cases/required_field_proto3_pb2.pyi index 44e6a792..6e053867 100644 --- a/gen/buf/validate/conformance/cases/required_field_proto3_pb2.pyi +++ b/gen/buf/validate/conformance/cases/required_field_proto3_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.py b/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.py index 7b33edc3..fe723bfc 100644 --- a/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.py +++ b/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/required_field_proto_editions.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/required_field_proto_editions.proto' ) diff --git a/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.pyi b/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.pyi index 79881986..a7b7eaee 100644 --- a/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.pyi +++ b/gen/buf/validate/conformance/cases/required_field_proto_editions_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/strings_pb2.py b/gen/buf/validate/conformance/cases/strings_pb2.py index 4b871dff..97ae7949 100644 --- a/gen/buf/validate/conformance/cases/strings_pb2.py +++ b/gen/buf/validate/conformance/cases/strings_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/strings.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/strings.proto' ) diff --git a/gen/buf/validate/conformance/cases/subdirectory/in_subdirectory_pb2.py b/gen/buf/validate/conformance/cases/subdirectory/in_subdirectory_pb2.py index b1865305..4598b8c6 100644 --- a/gen/buf/validate/conformance/cases/subdirectory/in_subdirectory_pb2.py +++ b/gen/buf/validate/conformance/cases/subdirectory/in_subdirectory_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/subdirectory/in_subdirectory.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/subdirectory/in_subdirectory.proto' ) diff --git a/gen/buf/validate/conformance/cases/wkt_any_pb2.py b/gen/buf/validate/conformance/cases/wkt_any_pb2.py index 424cbe6f..e07b2a40 100644 --- a/gen/buf/validate/conformance/cases/wkt_any_pb2.py +++ b/gen/buf/validate/conformance/cases/wkt_any_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/wkt_any.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/wkt_any.proto' ) diff --git a/gen/buf/validate/conformance/cases/wkt_any_pb2.pyi b/gen/buf/validate/conformance/cases/wkt_any_pb2.pyi index 4b5ffe5f..a047d05c 100644 --- a/gen/buf/validate/conformance/cases/wkt_any_pb2.pyi +++ b/gen/buf/validate/conformance/cases/wkt_any_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf import any_pb2 as _any_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/wkt_duration_pb2.py b/gen/buf/validate/conformance/cases/wkt_duration_pb2.py index 9cda2b72..34928d3c 100644 --- a/gen/buf/validate/conformance/cases/wkt_duration_pb2.py +++ b/gen/buf/validate/conformance/cases/wkt_duration_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/wkt_duration.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/wkt_duration.proto' ) diff --git a/gen/buf/validate/conformance/cases/wkt_duration_pb2.pyi b/gen/buf/validate/conformance/cases/wkt_duration_pb2.pyi index b957c78f..9ba67270 100644 --- a/gen/buf/validate/conformance/cases/wkt_duration_pb2.pyi +++ b/gen/buf/validate/conformance/cases/wkt_duration_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf import duration_pb2 as _duration_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/wkt_nested_pb2.py b/gen/buf/validate/conformance/cases/wkt_nested_pb2.py index 268730fe..5841286a 100644 --- a/gen/buf/validate/conformance/cases/wkt_nested_pb2.py +++ b/gen/buf/validate/conformance/cases/wkt_nested_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/wkt_nested.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/wkt_nested.proto' ) diff --git a/gen/buf/validate/conformance/cases/wkt_nested_pb2.pyi b/gen/buf/validate/conformance/cases/wkt_nested_pb2.pyi index 96d894d9..89c06261 100644 --- a/gen/buf/validate/conformance/cases/wkt_nested_pb2.pyi +++ b/gen/buf/validate/conformance/cases/wkt_nested_pb2.pyi @@ -15,7 +15,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.py b/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.py index 2790fb07..105280ec 100644 --- a/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.py +++ b/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/wkt_timestamp.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/wkt_timestamp.proto' ) diff --git a/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.pyi b/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.pyi index f7db1f81..0e0e97ab 100644 --- a/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.pyi +++ b/gen/buf/validate/conformance/cases/wkt_timestamp_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf import timestamp_pb2 as _timestamp_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.py b/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.py index 3e2e7ca8..52638b95 100644 --- a/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.py +++ b/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/wkt_wrappers.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/wkt_wrappers.proto' ) diff --git a/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.pyi b/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.pyi index 885ed053..1c13dc07 100644 --- a/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.pyi +++ b/gen/buf/validate/conformance/cases/wkt_wrappers_pb2.pyi @@ -16,7 +16,8 @@ from buf.validate import validate_pb2 as _validate_pb2 from google.protobuf import wrappers_pb2 as _wrappers_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/cases/yet_another_package/embed2_pb2.py b/gen/buf/validate/conformance/cases/yet_another_package/embed2_pb2.py index 796712c9..b3813032 100644 --- a/gen/buf/validate/conformance/cases/yet_another_package/embed2_pb2.py +++ b/gen/buf/validate/conformance/cases/yet_another_package/embed2_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/cases/yet_another_package/embed2.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/cases/yet_another_package/embed2.proto' ) diff --git a/gen/buf/validate/conformance/harness/harness_pb2.py b/gen/buf/validate/conformance/harness/harness_pb2.py index 6d2791f5..1210a445 100644 --- a/gen/buf/validate/conformance/harness/harness_pb2.py +++ b/gen/buf/validate/conformance/harness/harness_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/harness/harness.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/harness/harness.proto' ) diff --git a/gen/buf/validate/conformance/harness/harness_pb2.pyi b/gen/buf/validate/conformance/harness/harness_pb2.pyi index 751b7a37..4226ca3f 100644 --- a/gen/buf/validate/conformance/harness/harness_pb2.pyi +++ b/gen/buf/validate/conformance/harness/harness_pb2.pyi @@ -18,7 +18,8 @@ from google.protobuf import descriptor_pb2 as _descriptor_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/conformance/harness/results_pb2.py b/gen/buf/validate/conformance/harness/results_pb2.py index 8147c744..1bd4714f 100644 --- a/gen/buf/validate/conformance/harness/results_pb2.py +++ b/gen/buf/validate/conformance/harness/results_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/conformance/harness/results.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/conformance/harness/results.proto' ) diff --git a/gen/buf/validate/conformance/harness/results_pb2.pyi b/gen/buf/validate/conformance/harness/results_pb2.pyi index d56a31d4..1880f033 100644 --- a/gen/buf/validate/conformance/harness/results_pb2.pyi +++ b/gen/buf/validate/conformance/harness/results_pb2.pyi @@ -18,7 +18,8 @@ from google.protobuf import descriptor_pb2 as _descriptor_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/gen/buf/validate/validate_pb2.py b/gen/buf/validate/validate_pb2.py index d861355a..d3397b61 100644 --- a/gen/buf/validate/validate_pb2.py +++ b/gen/buf/validate/validate_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: buf/validate/validate.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'buf/validate/validate.proto' ) diff --git a/gen/buf/validate/validate_pb2.pyi b/gen/buf/validate/validate_pb2.pyi index b1b17c18..46ec5ef6 100644 --- a/gen/buf/validate/validate_pb2.pyi +++ b/gen/buf/validate/validate_pb2.pyi @@ -20,7 +20,8 @@ from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf.internal import python_message as _python_message from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index ba8546fe..ceeb431f 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -216,7 +216,7 @@ def _is_hostname(val: str) -> bool: if (c < "a" or c > "z") and (c < "0" or c > "9") and c != "-": return False - all_digits = all_digits and c >= "0" and c <= "9" + all_digits = all_digits and "0" <= c <= "9" # the last part cannot be all numbers return not all_digits @@ -380,10 +380,7 @@ def __prefix_length(self) -> bool: start = self._index - while True: - if self._index >= len(self._string) or not self.__digit(): - break - + while self.__digit(): if self._index - start > 2: # max prefix-length is 32 bits, so anything more than 2 digits is invalid return False @@ -427,16 +424,12 @@ def __address_part(self) -> bool: return True self._index = start - return False def __dec_octet(self) -> bool: start = self._index - while True: - if self._index >= len(self._string) or not self.__digit(): - break - + while self.__digit(): if self._index - start > 3: # decimal octet can be three characters at most return False @@ -542,10 +535,7 @@ def get_bits(self) -> int: # Handle double colon, fill pieces with 0 if self._double_colon_seen: - while True: - if len(p16) >= 8: - break - + while len(p16) < 8: # Delete 0 entries at pos, insert a 0 p16.insert(self._double_colon_at, 0x00000000) @@ -572,11 +562,11 @@ def is_prefix_only(self) -> bool: bits = self.get_bits() mask: int if self._prefix_len >= 128: - mask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + mask = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF elif self._prefix_len < 0: - mask = 0x00000000000000000000000000000000 + mask = 0x00000000_00000000_00000000_00000000 else: - mask = ~(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF >> self._prefix_len) + mask = ~(0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF >> self._prefix_len) masked = bits & mask if bits != masked: @@ -604,10 +594,7 @@ def __prefix_length(self) -> bool: """Store value in prefix_len.""" start = self._index - while True: - if self._index >= len(self._string) or not self.__digit(): - break - + while self.__digit(): if self._index - start > 3: return False @@ -639,10 +626,7 @@ def __prefix_length(self) -> bool: def __address_part(self) -> bool: """Store dotted notation for right-most 32 bits in dotted_raw / dotted_addr if found.""" - while True: - if self._index >= len(self._string): - break - + while self._index < len(self._string): # dotted notation for right-most 32 bits, e.g. 0:0:0:0:0:ffff:192.1.56.10 if (self._double_colon_seen or len(self._pieces) == 6) and self.__dotted(): dotted = Ipv4(self._dotted_raw) @@ -703,7 +687,6 @@ def __zone_id(self) -> bool: self._index = start self._zone_id_found = False - return False def __dotted(self) -> bool: @@ -719,18 +702,14 @@ def __dotted(self) -> bool: start = self._index self._dotted_raw = "" - while True: - if self._index < len(self._string) and (self.__digit() or self.__take(".")): - continue - - break + while self.__digit() or self.__take("."): + pass if self._index - start >= 7: self._dotted_raw = self._string[start : self._index] return True self._index = start - return False def __h16(self) -> bool: @@ -749,9 +728,8 @@ def __h16(self) -> bool: start = self._index - while True: - if self._index >= len(self._string) or not self.__hex_dig(): - break + while self.__hex_dig(): + pass string = self._string[start : self._index] @@ -893,7 +871,6 @@ def __hier_part(self) -> bool: return True self._index = start - return self.__path_absolute() or self.__path_rootless() or self.__path_empty() def __relative_ref(self) -> bool: @@ -938,7 +915,6 @@ def __relative_part(self) -> bool: return True self._index = start - return self.__path_absolute() or self.__path_noscheme() or self.__path_empty() def __scheme(self) -> bool: @@ -1019,18 +995,18 @@ def __userinfo(self) -> bool: """ start = self._index - while True: - if self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take(":"): - continue + while self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take(":"): + pass - if self._index < len(self._string): - if self._string[self._index] == "@": - return True + if self._index < len(self._string): + if self._string[self._index] == "@": + return True - self._index = start - return False + self._index = start + return False - def __check_host_pct_encoded(self, string: str) -> bool: + @staticmethod + def __check_host_pct_encoded(string: str) -> bool: """Verify that string is correctly percent-encoded.""" try: # unquote defaults to 'UTF-8' encoding. @@ -1079,15 +1055,14 @@ def __port(self) -> bool: """ start = self._index - while True: - if self.__digit(): - continue + while self.__digit(): + pass - if self.__is_authority_end(): - return True + if self.__is_authority_end(): + return True - self._index = start - return False + self._index = start + return False def __ip_literal(self) -> bool: """Determine whether the current position is a IP-literal. @@ -1207,20 +1182,18 @@ def __reg_name(self) -> bool: """ start = self._index - while True: - if self.__unreserved() or self.__pct_encoded() or self.__sub_delims(): - continue - - if self.__is_authority_end(): - # End of authority - return True + while self.__unreserved() or self.__pct_encoded() or self.__sub_delims(): + pass - if self._string[self._index] == ":": - return True + if self.__is_authority_end(): + # End of authority + return True - self._index = start + if self._string[self._index] == ":": + return True - return False + self._index = start + return False def __is_path_end(self) -> bool: """Determine whether the current index has reached the end of path. @@ -1249,7 +1222,6 @@ def __path_abempty(self) -> bool: return True self._index = start - return False def __path_absolute(self) -> bool: @@ -1273,7 +1245,6 @@ def __path_absolute(self) -> bool: return True self._index = start - return False def __path_noscheme(self) -> bool: @@ -1295,7 +1266,6 @@ def __path_noscheme(self) -> bool: return True self._index = start - return True def __path_rootless(self) -> bool: @@ -1318,7 +1288,6 @@ def __path_rootless(self) -> bool: return True self._index = start - return True def __path_empty(self) -> bool: @@ -1363,7 +1332,6 @@ def __segment_nz(self) -> bool: return True self._index = start - return False def __segment_nz_nc(self) -> bool: @@ -1384,7 +1352,6 @@ def __segment_nz_nc(self) -> bool: return True self._index = start - return False def __pchar(self) -> bool: @@ -1411,16 +1378,14 @@ def __query(self) -> bool: """ start = self._index - while True: - if self.__pchar() or self.__take("/") or self.__take("?"): - continue - - if self._index == len(self._string) or self._string[self._index] == "#": - return True + while self.__pchar() or self.__take("/") or self.__take("?"): + pass - self._index = start + if self._index == len(self._string) or self._string[self._index] == "#": + return True - return False + self._index = start + return False def __fragment(self) -> bool: """Determine whether the current position is a fragment. @@ -1434,16 +1399,14 @@ def __fragment(self) -> bool: """ start = self._index - while True: - if self.__pchar() or self.__take("/") or self.__take("?"): - continue - - if self._index == len(self._string): - return True + while self.__pchar() or self.__take("/") or self.__take("?"): + pass - self._index = start + if self._index == len(self._string): + return True - return False + self._index = start + return False def __pct_encoded(self) -> bool: """Determine whether the current position is a pct-encoded. @@ -1554,9 +1517,8 @@ def __hex_dig(self) -> bool: c = self._string[self._index] - if ("0" <= c <= "9") or ("a" <= c <= "f") or ("A" <= c <= "F") or ("0" <= c <= "9"): + if ("0" <= c <= "9") or ("a" <= c <= "f") or ("A" <= c <= "F"): self._index += 1 - return True return False From fb0cfad89256d014ec730a2c5f990143beaa301b Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Tue, 15 Apr 2025 20:14:44 +0200 Subject: [PATCH 10/13] Fix isEmail ignoring trailing newlines (#285) --- protovalidate/internal/extra_func.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index ceeb431f..530bb470 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -137,7 +137,7 @@ def cel_is_email(string: celtypes.Value) -> celpy.Result: if not isinstance(string, celtypes.StringType): msg = "invalid argument, expected string" raise celpy.CELEvalError(msg) - m = _email_regex.match(string) is not None + m = _email_regex.fullmatch(string) is not None return celtypes.BoolType(m) From a435038aebc9f80dfdfee16db53337b8ee629f58 Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Tue, 15 Apr 2025 20:15:09 +0200 Subject: [PATCH 11/13] Fix isHostAndPort to reject leading zeros in port number (#286) --- protovalidate/internal/extra_func.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index 530bb470..adcfcbf8 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -225,14 +225,13 @@ def _is_hostname(val: str) -> bool: def _is_port(val: str) -> bool: if len(val) == 0: return False - + if len(val) > 1 and val[0] == "0": + return False for c in val: if c < "0" or c > "9": return False - try: return int(val) <= 65535 - except ValueError: # Error converting to number return False From ca617612f6fc00924d17124386b2c71e9b303c18 Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Tue, 22 Apr 2025 17:15:20 +0200 Subject: [PATCH 12/13] Fix IndexError in isUri (#287) --- protovalidate/internal/extra_func.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index adcfcbf8..eca6e1b5 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -931,7 +931,7 @@ def __scheme(self) -> bool: while self.__alpha() or self.__digit() or self.__take("+") or self.__take("-") or self.__take("."): pass - if self._string[self._index] == ":": + if self.__peek(":"): return True self._index = start @@ -997,9 +997,8 @@ def __userinfo(self) -> bool: while self.__unreserved() or self.__pct_encoded() or self.__sub_delims() or self.__take(":"): pass - if self._index < len(self._string): - if self._string[self._index] == "@": - return True + if self.__peek("@"): + return True self._index = start return False @@ -1023,14 +1022,11 @@ def __host(self) -> bool: host = IP-literal / IPv4address / reg-name. """ - if self._index >= len(self._string): - return False - start = self._index self._pct_encoded_found = False # Note: IPv4address is a subset of reg-name - if (self._string[self._index] == "[" and self.__ip_literal()) or self.__reg_name(): + if (self.__peek("[") and self.__ip_literal()) or self.__reg_name(): if self._pct_encoded_found: raw_host = self._string[start : self._index] # RFC 3986: @@ -1188,7 +1184,7 @@ def __reg_name(self) -> bool: # End of authority return True - if self._string[self._index] == ":": + if self.__peek(":"): return True self._index = start @@ -1380,7 +1376,7 @@ def __query(self) -> bool: while self.__pchar() or self.__take("/") or self.__take("?"): pass - if self._index == len(self._string) or self._string[self._index] == "#": + if self._index == len(self._string) or self.__peek("#"): return True self._index = start @@ -1537,6 +1533,9 @@ def __take(self, char: str) -> bool: return False + def __peek(self, char: str) -> bool: + return self._index < len(self._string) and self._string[self._index] == char + def make_extra_funcs(locale: str) -> dict[str, celpy.CELFunction]: # TODO(#257): Fix types and add tests for StringFormat. From e722a0663bce031ab0193bfd18776d0a16c9e20a Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Tue, 22 Apr 2025 12:09:05 -0400 Subject: [PATCH 13/13] Merge main --- gen/tests/example/v1/validations_pb2.py | 8 ++++---- gen/tests/example/v1/validations_pb2.pyi | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gen/tests/example/v1/validations_pb2.py b/gen/tests/example/v1/validations_pb2.py index f9b58951..88fbb62d 100644 --- a/gen/tests/example/v1/validations_pb2.py +++ b/gen/tests/example/v1/validations_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: tests/example/v1/validations.proto -# Protobuf Python Version: 5.29.3 +# Protobuf Python Version: 6.30.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -25,9 +25,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 29, - 3, + 6, + 30, + 1, '', 'tests/example/v1/validations.proto' ) diff --git a/gen/tests/example/v1/validations_pb2.pyi b/gen/tests/example/v1/validations_pb2.pyi index 576c477e..bccbc410 100644 --- a/gen/tests/example/v1/validations_pb2.pyi +++ b/gen/tests/example/v1/validations_pb2.pyi @@ -17,7 +17,8 @@ from google.protobuf import timestamp_pb2 as _timestamp_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor