Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions protovalidate/internal/extra_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# limitations under the License.

import math
import re
import typing
from urllib import parse as urlparse

Expand All @@ -25,7 +24,7 @@
from protovalidate.internal.rules import MessageType, field_to_cel

# See https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
_email_regex = re.compile(
_email_regex = re2.compile(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regular expression from the WHATWG group is "JavaScript- and Perl-compatible". Are we sure that switching to re2 doesn't change behavior?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're running the protovalidate conformance tests and they still passed (relevant test cases). I'm not sure if we view those as exhaustive or more of a basic test of functionality.

In general, re2 is meant to be a drop-in replacement for re, and we aren't any of the documented PCRE-incompatible bits.

If there is a change in behavior, I'd still rather opt us towards using re2 since CEL expects that behavior. Happy to note it in release notes, though, thoughts?

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])?)*$"
)

Expand Down Expand Up @@ -107,17 +106,17 @@ def cel_is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result:
msg = "invalid argument, expected string or bytes"
raise celpy.CELEvalError(msg)
version = 0
strict = celtypes.BoolType(False)
strict = False
if len(args) == 1 and isinstance(args[0], celtypes.BoolType):
strict = args[0]
strict = bool(args[0])
elif len(args) == 1 and isinstance(args[0], celtypes.IntType):
version = args[0]
elif len(args) == 1 and (not isinstance(args[0], celtypes.BoolType) or not isinstance(args[0], celtypes.IntType)):
msg = "invalid argument, expected bool or int"
raise celpy.CELEvalError(msg)
elif len(args) == 2 and isinstance(args[0], celtypes.IntType) and isinstance(args[1], celtypes.BoolType):
version = args[0]
strict = args[1]
strict = bool(args[1])
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)
Expand Down
4 changes: 2 additions & 2 deletions protovalidate/internal/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,14 @@ def validate(self, ctx: RuleContext, msg: message.Message):
ctx.add(
Violation(
rule_id="message.oneof",
message=f"only one of {', '.join([field.name for field in self._fields])} can be set",
message=f"only one of {', '.join(field.name for field in self._fields)} can be set",
)
)
if self._required and num_set_fields == 0:
ctx.add(
Violation(
rule_id="message.oneof",
message=f"one of {', '.join([field.name for field in self._fields])} must be set",
message=f"one of {', '.join(field.name for field in self._fields)} must be set",
)
)

Expand Down
50 changes: 9 additions & 41 deletions protovalidate/internal/string_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,7 @@ def __format_exponential(self, arg: celtypes.Value, precision: int) -> str:
raise celpy.CELEvalError(msg)

def __format_int(self, arg: celtypes.Value) -> str:
if (
isinstance(arg, celtypes.IntType)
or isinstance(arg, celtypes.UintType)
or isinstance(arg, celtypes.DoubleType)
):
if isinstance(arg, (celtypes.IntType, celtypes.UintType, celtypes.DoubleType)):
result = self.__validate_number(arg)
if result is not None:
return result
Expand All @@ -138,9 +134,7 @@ def __format_int(self, arg: celtypes.Value) -> str:
raise celpy.CELEvalError(msg)

def __format_hex(self, arg: celtypes.Value) -> str:
if isinstance(arg, celtypes.IntType):
return f"{arg:x}"
if isinstance(arg, celtypes.UintType):
if isinstance(arg, (celtypes.IntType, celtypes.UintType)):
return f"{arg:x}"
if isinstance(arg, celtypes.BytesType):
return arg.hex()
Expand All @@ -153,9 +147,7 @@ def __format_hex(self, arg: celtypes.Value) -> str:
raise celpy.CELEvalError(msg)

def __format_oct(self, arg: celtypes.Value) -> str:
if isinstance(arg, celtypes.IntType):
return f"{arg:o}"
if isinstance(arg, celtypes.UintType):
if isinstance(arg, (celtypes.IntType, celtypes.UintType)):
return f"{arg:o}"
msg = (
"error during formatting: octal clause can only be used on integers, was given "
Expand All @@ -164,17 +156,12 @@ def __format_oct(self, arg: celtypes.Value) -> str:
raise celpy.CELEvalError(msg)

def __format_bin(self, arg: celtypes.Value) -> str:
if isinstance(arg, celtypes.IntType):
return f"{arg:b}"
if isinstance(arg, celtypes.UintType):
return f"{arg:b}"
if isinstance(arg, celtypes.BoolType):
if isinstance(arg, (celtypes.IntType, celtypes.UintType, celtypes.BoolType)):
return f"{arg:b}"
msg = (
"error during formatting: only integers and bools can be formatted as binary, was given "
f"{self.__type_str(type(arg))}"
)

raise celpy.CELEvalError(msg)

def __format_string(self, arg: celtypes.Value) -> str:
Expand All @@ -189,6 +176,7 @@ def __format_string(self, arg: celtypes.Value) -> str:
decoded = arg.decode("utf-8", errors="replace")
# Collapse any contiguous placeholders into one
return re.sub("\\ufffd+", "\ufffd", decoded)

if isinstance(arg, celtypes.DoubleType):
result = self.__validate_number(arg)
if result is not None:
Expand All @@ -206,7 +194,7 @@ def __format_string(self, arg: celtypes.Value) -> str:
if isinstance(arg, celtypes.MapType):
return self.__format_map(arg)
if isinstance(arg, celtypes.StringType):
return f"{arg}"
return arg
if isinstance(arg, celtypes.TimestampType):
base = arg.isoformat()
if arg.getMilliseconds() != 0:
Expand All @@ -215,31 +203,11 @@ def __format_string(self, arg: celtypes.Value) -> str:
return "unknown"

def __format_list(self, arg: celtypes.ListType) -> str:
result = "["
for i in range(len(arg)):
if i > 0:
result += ", "
result += self.__format_string(arg[i])
result += "]"
return result
return "[" + ", ".join(self.__format_string(val) for val in arg) + "]"

def __format_map(self, arg: celtypes.MapType) -> str:
m = {}
for cel_key, cel_val in arg.items():
key = self.__format_string(cel_key)
val = self.__format_string(cel_val)
m[key] = val

m = dict(sorted(m.items()))

result = "{"
for i, (key, val) in enumerate(m.items()):
if i > 0:
result += ", "
result += key + ": " + val

result += "}"
return result
m = {self.__format_string(cel_key): self.__format_string(cel_val) for cel_key, cel_val in arg.items()}
return "{" + ", ".join(key + ": " + val for key, val in sorted(m.items())) + "}"

def __format_duration(self, arg: celtypes.DurationType) -> str:
return f"{arg.seconds + Decimal(arg.microseconds) / Decimal(1_000_000):f}s"
Expand Down
5 changes: 1 addition & 4 deletions protovalidate/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,7 @@ def to_proto(self) -> validate_pb2.Violations:
"""
Provides the Protobuf form of the validation errors.
"""
result = validate_pb2.Violations()
for violation in self._violations:
result.violations.append(violation.proto)
return result
return validate_pb2.Violations(violations=[violation.proto for violation in self._violations])

@property
def violations(self) -> list[Violation]:
Expand Down
6 changes: 0 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ lint.select = [
"YTT",
]
lint.ignore = [
# Allow boolean positional values in function calls, like `dict.get(..., True)`.
"FBT003",
# Ignore complexity
"C901",
"PLR0911",
Expand All @@ -90,10 +88,6 @@ lint.ignore = [
# Ignore magic values - in this library, most are obvious in context.
"PLR2004",
]
lint.unfixable = [
# Don't autofix unused imports.
"F401",
]

[tool.ruff.lint.isort]
known-first-party = ["protovalidate", "buf"]
Expand Down