diff --git a/Makefile b/Makefile index 8c89c02c..8fa32085 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ export PATH := $(BIN):$(PATH) export GOBIN := $(abspath $(BIN)) # Set to use a different Python interpreter. For example, `PYTHON=python make test`. PYTHON ?= python3 -CONFORMANCE_ARGS ?= --strict --expected_failures=tests/conformance/nonconforming.yaml --timeout 10s +CONFORMANCE_ARGS ?= --strict --strict_message --expected_failures=tests/conformance/nonconforming.yaml --timeout 10s ADD_LICENSE_HEADER := $(BIN)/license-header \ --license-type apache \ --copyright-holder "Buf Technologies, Inc." \ diff --git a/protovalidate/internal/string_format.py b/protovalidate/internal/string_format.py index 6604029b..7043084f 100644 --- a/protovalidate/internal/string_format.py +++ b/protovalidate/internal/string_format.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from decimal import Decimal + import celpy # type: ignore from celpy import celtypes # type: ignore @@ -146,9 +148,21 @@ def format_string(self, arg: celtypes.Value) -> celpy.Result: if isinstance(arg, celtypes.StringType): return arg if isinstance(arg, celtypes.BytesType): - return celtypes.StringType(arg.hex()) + return celtypes.StringType(arg) if isinstance(arg, celtypes.ListType): return self.format_list(arg) + if isinstance(arg, celtypes.BoolType): + # True -> true + return celtypes.StringType(str(arg).lower()) + if isinstance(arg, celtypes.DoubleType): + return celtypes.StringType(f"{arg:.0f}") + if isinstance(arg, celtypes.DurationType): + return celtypes.StringType(self._format_duration(arg)) + if isinstance(arg, celtypes.TimestampType): + base = arg.isoformat() + if arg.getMilliseconds() != 0: + base = arg.isoformat(timespec="milliseconds") + return celtypes.StringType(base.removesuffix("+00:00") + "Z") return celtypes.StringType(arg) def format_value(self, arg: celtypes.Value) -> celpy.Result: @@ -156,6 +170,10 @@ def format_value(self, arg: celtypes.Value) -> celpy.Result: return celtypes.StringType(quote(arg)) if isinstance(arg, celtypes.UintType): return celtypes.StringType(arg) + if isinstance(arg, celtypes.DurationType): + return celtypes.StringType(f'duration("{self._format_duration(arg)}")') + if isinstance(arg, celtypes.DoubleType): + return celtypes.StringType(f"{arg:f}") return self.format_string(arg) def format_list(self, arg: celtypes.ListType) -> celpy.Result: @@ -167,6 +185,9 @@ def format_list(self, arg: celtypes.ListType) -> celpy.Result: result += "]" return celtypes.StringType(result) + def _format_duration(self, arg: celtypes.DurationType) -> celpy.Result: + return f"{arg.seconds + Decimal(arg.microseconds) / Decimal(1_000_000):f}s" + _default_format = StringFormat("en_US") format = _default_format.format # noqa: A001 diff --git a/tests/conformance/nonconforming.yaml b/tests/conformance/nonconforming.yaml index 4abf19b8..25744e50 100644 --- a/tests/conformance/nonconforming.yaml +++ b/tests/conformance/nonconforming.yaml @@ -1,4 +1,5 @@ # celpy doesn't support nano seconds +# ref: https://github.com/cloud-custodian/cel-python/issues/43 standard_constraints/well_known_types/duration: - gte_lte/invalid/above - lte/invalid