From 9e0a13c5302b24243f0d9bdcc9af75f9e34843af Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Tue, 26 Nov 2024 06:20:00 -0500 Subject: [PATCH 1/2] Strict conformance fixes --- build.gradle.kts | 1 + gradle.properties | 2 +- gradle/libs.versions.toml | 1 + .../protovalidate/internal/celext/Format.java | 16 +++++++++++----- .../internal/evaluator/MapEvaluator.java | 6 +++++- 5 files changed, 19 insertions(+), 7 deletions(-) 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. From 32ddf0d05908d5f7f0bb69bc81782961bb9501ca Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Tue, 26 Nov 2024 11:28:28 -0500 Subject: [PATCH 2/2] Rewrite timestamp formatting using Java 8 Instant --- build.gradle.kts | 1 - gradle/libs.versions.toml | 1 - .../build/buf/protovalidate/internal/celext/Format.java | 7 +++++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5cf32e2b..4dc6e1ed 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -264,7 +264,6 @@ 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/libs.versions.toml b/gradle/libs.versions.toml index 1f5ab97a..4dd17333 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,6 @@ 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 ef5975db..82ea82a0 100644 --- a/src/main/java/build/buf/protovalidate/internal/celext/Format.java +++ b/src/main/java/build/buf/protovalidate/internal/celext/Format.java @@ -14,11 +14,13 @@ package build.buf.protovalidate.internal.celext; +import static java.time.format.DateTimeFormatter.ISO_INSTANT; + 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.time.Instant; import java.util.List; import org.projectnessie.cel.common.types.Err.ErrException; import org.projectnessie.cel.common.types.IntT; @@ -214,7 +216,8 @@ private static void formatList(StringBuilder builder, Val val) { */ private static void formatTimestamp(StringBuilder builder, Val val) { Timestamp timestamp = val.convertToNative(Timestamp.class); - builder.append(Timestamps.toString(timestamp)); + Instant instant = Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()); + builder.append(ISO_INSTANT.format(instant)); } /**