diff --git a/build.gradle.kts b/build.gradle.kts index 4dc6e1ed..5cf32e2b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -264,6 +264,7 @@ mavenPublishing { dependencies { annotationProcessor(libs.nullaway) api(libs.protobuf.java) + implementation(libs.protobuf.java.util) implementation(enforcedPlatform(libs.cel)) implementation(libs.cel.core) implementation(libs.guava) diff --git a/gradle.properties b/gradle.properties index 0e405393..9e884fbc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ protovalidate.version = v0.8.2 # Arguments to the protovalidate-conformance CLI -protovalidate.conformance.args = --strict_message --strict_error +protovalidate.conformance.args = --strict --strict_message --strict_error # Argument to the license-header CLI license-header.years = 2023-2024 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4dd17333..1f5ab97a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } maven-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish" } nullaway = { module = "com.uber.nullaway:nullaway", version = "0.12.1" } protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" } +protobuf-java-util = { module = "com.google.protobuf:protobuf-java-util", version.ref = "protobuf" } spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "6.25.0" } [plugins] diff --git a/src/main/java/build/buf/protovalidate/internal/celext/Format.java b/src/main/java/build/buf/protovalidate/internal/celext/Format.java index df662494..ef5975db 100644 --- a/src/main/java/build/buf/protovalidate/internal/celext/Format.java +++ b/src/main/java/build/buf/protovalidate/internal/celext/Format.java @@ -16,6 +16,7 @@ import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Timestamps; import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.util.List; @@ -139,7 +140,10 @@ private static void formatString(StringBuilder builder, Val val) { if (val.type().typeEnum() == TypeEnum.String) { builder.append(val.value()); } else if (val.type().typeEnum() == TypeEnum.Bytes) { - builder.append(val.value()); + builder.append(new String((byte[]) val.value(), StandardCharsets.UTF_8)); + } else if (val.type().typeEnum() == TypeEnum.Double) { + DecimalFormat format = new DecimalFormat(); + builder.append(format.format(val.value())); } else { formatStringSafe(builder, val, false); } @@ -159,7 +163,11 @@ private static void formatStringSafe(StringBuilder builder, Val val, boolean lis } else if (type == TypeEnum.Int || type == TypeEnum.Uint) { formatDecimal(builder, val); } else if (type == TypeEnum.Double) { - DecimalFormat format = new DecimalFormat("0.#"); + // When a double is nested in another type (e.g. a list) it will have a minimum of 6 decimal + // digits. This is to maintain consistency with the Go CEL runtime. + DecimalFormat format = new DecimalFormat(); + format.setMaximumFractionDigits(Integer.MAX_VALUE); + format.setMinimumFractionDigits(6); builder.append(format.format(val.value())); } else if (type == TypeEnum.String) { builder.append("\"").append(val.value().toString()).append("\""); @@ -205,10 +213,8 @@ private static void formatList(StringBuilder builder, Val val) { * @param val the value to format. */ private static void formatTimestamp(StringBuilder builder, Val val) { - builder.append("timestamp("); Timestamp timestamp = val.convertToNative(Timestamp.class); - builder.append(timestamp.toString()); - builder.append(")"); + builder.append(Timestamps.toString(timestamp)); } /** diff --git a/src/main/java/build/buf/protovalidate/internal/evaluator/MapEvaluator.java b/src/main/java/build/buf/protovalidate/internal/evaluator/MapEvaluator.java index a2894dfe..3110d390 100644 --- a/src/main/java/build/buf/protovalidate/internal/evaluator/MapEvaluator.java +++ b/src/main/java/build/buf/protovalidate/internal/evaluator/MapEvaluator.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** Performs validation on a map field's key-value pairs. */ class MapEvaluator implements Evaluator { @@ -84,7 +85,10 @@ public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionEx private List evalPairs(Value key, Value value, boolean failFast) throws ExecutionException { - List keyViolations = keyEvaluator.evaluate(key, failFast).getViolations(); + List keyViolations = + keyEvaluator.evaluate(key, failFast).getViolations().stream() + .map(violation -> violation.toBuilder().setForKey(true).build()) + .collect(Collectors.toList()); final List valueViolations; if (failFast && !keyViolations.isEmpty()) { // Don't evaluate value constraints if failFast is enabled and keys failed validation.