From 02d88524d664b05876d3e0e3b088cdaa4e275564 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 29 Apr 2025 14:06:16 -0700 Subject: [PATCH] Fork timestamp/duration protobuf utility methods. Remove protobuf_java_util dependency from the runtime PiperOrigin-RevId: 752875600 --- .github/workflows/unwanted_deps.sh | 23 +- .../src/test/java/dev/cel/bundle/BUILD.bazel | 2 +- .../test/java/dev/cel/bundle/CelImplTest.java | 9 +- codelab/README.md | 4 +- codelab/src/test/codelab/BUILD.bazel | 4 +- codelab/src/test/codelab/Exercise5Test.java | 6 +- codelab/src/test/codelab/Exercise6Test.java | 4 +- .../src/test/codelab/solutions/BUILD.bazel | 4 +- .../test/codelab/solutions/Exercise5Test.java | 6 +- .../test/codelab/solutions/Exercise6Test.java | 4 +- common/internal/BUILD.bazel | 10 + .../src/main/java/dev/cel/common/BUILD.bazel | 2 +- .../dev/cel/common/CelProtoJsonAdapter.java | 7 +- .../java/dev/cel/common/internal/BUILD.bazel | 26 + .../cel/common/internal/ProtoTimeUtils.java | 568 ++++++++++++++++++ .../java/dev/cel/common/values/BUILD.bazel | 4 +- .../values/BaseProtoCelValueConverter.java | 71 +-- .../java/dev/cel/common/values/BUILD.bazel | 2 +- .../values/ProtoCelValueConverterTest.java | 7 +- .../values/ProtoMessageValueProviderTest.java | 7 +- .../test/java/dev/cel/extensions/BUILD.bazel | 2 +- .../extensions/CelOptionalLibraryTest.java | 11 +- .../src/main/java/dev/cel/runtime/BUILD.bazel | 4 +- .../dev/cel/runtime/CelStandardFunctions.java | 55 +- .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../dev/cel/runtime/CelLiteRuntimeTest.java | 15 +- .../ProtoMessageRuntimeEqualityTest.java | 7 +- .../dev/cel/runtime/TypeResolverTest.java | 7 +- .../src/main/java/dev/cel/testing/BUILD.bazel | 1 + .../dev/cel/testing/BaseInterpreterTest.java | 20 +- .../dev/cel/validator/validators/BUILD.bazel | 2 +- .../DurationLiteralValidatorTest.java | 4 +- .../TimestampLiteralValidatorTest.java | 4 +- 33 files changed, 736 insertions(+), 167 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java diff --git a/.github/workflows/unwanted_deps.sh b/.github/workflows/unwanted_deps.sh index b96951c41..522273ff8 100755 --- a/.github/workflows/unwanted_deps.sh +++ b/.github/workflows/unwanted_deps.sh @@ -13,16 +13,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Script ran as part of Github CEL-Java CI to verify that the runtime jar does not contain generated cel protos from @cel_spec. +# Script ran as part of Github CEL-Java CI to verify that the runtime jar does not contain unwanted dependencies. -runtime_deps="$(bazel query 'deps(//publish:cel_runtime)' --nohost_deps --noimplicit_deps --output graph | grep '@cel_spec')" +UNWANTED_DEPS=( + '@cel_spec' # Do not include generated CEL protos in the jar + 'protobuf_java_util' # Does not support protolite +) -if [[ ! -z $runtime_deps ]]; then - echo -e "Runtime contains unwanted @cel_spec dependency!\n" - echo "cel_spec dependency graph:" - echo -e "$runtime_deps" - exit 1 -fi +runtime_deps="$(bazel query 'deps(//publish:cel_runtime)' --nohost_deps --noimplicit_deps --output graph)" + +for unwanted_dep in "${UNWANTED_DEPS[@]}"; do + if echo "$runtime_deps" | grep "$unwanted_dep" > /dev/null; then + echo -e "Runtime contains unwanted dependency: $unwanted_dep!\n" + echo "cel_spec dependency graph (including '$unwanted_dep'):" + echo "$(echo "$runtime_deps" | grep "$unwanted_dep")" + exit 1 + fi +done exit 0 diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel index b98f26408..40f3257c2 100644 --- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel @@ -30,6 +30,7 @@ java_library( "//common:proto_ast", "//common:source_location", "//common/ast", + "//common/internal:proto_time_utils", "//common/resources/testdata/proto3:standalone_global_enum_java_proto", "//common/testing", "//common/types", @@ -55,7 +56,6 @@ java_library( "@com_google_googleapis//google/type:type_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:com_google_truth_extensions_truth_proto_extension", "@maven//:junit_junit", diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 0efe75cd5..2165390bb 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -51,7 +51,6 @@ import com.google.protobuf.Struct; import com.google.protobuf.TextFormat; import com.google.protobuf.Timestamp; -import com.google.protobuf.util.Timestamps; import com.google.rpc.context.AttributeContext; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; @@ -71,6 +70,7 @@ import dev.cel.common.CelVarDecl; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelList; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.testing.RepeatedTestProvider; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelProtoMessageTypes; @@ -409,6 +409,7 @@ public void program_setTypeFactoryOnAnyPackedMessage_messageConstructionSucceeds } @Test + @SuppressWarnings("unused") // testRunIndex name retained for test result readability public void program_concurrentMessageConstruction_succeeds( @TestParameter(valuesProvider = RepeatedTestProvider.class) int testRunIndex) throws Exception { @@ -814,7 +815,7 @@ public void program_duplicateTypeDescriptor() throws Exception { .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(Timestamps.fromSeconds(12)); + assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); } @Test @@ -827,7 +828,7 @@ public void program_hermeticDescriptors_wellKnownProtobuf() throws Exception { .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(Timestamps.fromSeconds(12)); + assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); } @Test @@ -956,7 +957,7 @@ public void program_typeProvider() throws Exception { .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(Timestamps.fromSeconds(12)); + assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); } @Test diff --git a/codelab/README.md b/codelab/README.md index 448135f03..6de1a2fb0 100644 --- a/codelab/README.md +++ b/codelab/README.md @@ -667,7 +667,7 @@ public void evaluate_jwtWithTimeVariable_producesJsonString() throws Exception { @SuppressWarnings("unchecked") Map evaluatedResult = (Map) - exercise5.eval(ast, ImmutableMap.of("time", Timestamps.fromSeconds(1698361778))); + exercise5.eval(ast, ImmutableMap.of("time", ProtoTimeUtils.fromSecondsToTimestamp(1698361778))); String jsonOutput = exercise5.toJson(evaluatedResult); assertThat(jsonOutput) @@ -776,7 +776,7 @@ public void evaluate_constructAttributeContext() { + "time: now" + "}"; // Values for `now` and `jwt` variables to be passed into the runtime - Timestamp now = Timestamps.now(); + Timestamp now = ProtoTimeUtils.now(); ImmutableMap jwt = ImmutableMap.of( "sub", "serviceAccount:delegate@acme.co", diff --git a/codelab/src/test/codelab/BUILD.bazel b/codelab/src/test/codelab/BUILD.bazel index 82ac34677..fb3f1e235 100644 --- a/codelab/src/test/codelab/BUILD.bazel +++ b/codelab/src/test/codelab/BUILD.bazel @@ -78,7 +78,7 @@ java_test( "//codelab", "//common:cel_ast", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", ], @@ -93,10 +93,10 @@ java_test( "//:java_truth", "//codelab", "//common:cel_ast", + "//common/internal:proto_time_utils", "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", ], diff --git a/codelab/src/test/codelab/Exercise5Test.java b/codelab/src/test/codelab/Exercise5Test.java index 7035ff742..128300166 100644 --- a/codelab/src/test/codelab/Exercise5Test.java +++ b/codelab/src/test/codelab/Exercise5Test.java @@ -17,7 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableMap; -import com.google.protobuf.util.Timestamps; +import com.google.protobuf.Timestamp; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; import java.util.Map; @@ -48,7 +48,9 @@ public void evaluate_jwtWithTimeVariable_producesJsonString() throws Exception { @SuppressWarnings("unchecked") Map evaluatedResult = (Map) - exercise5.eval(ast, ImmutableMap.of("time", Timestamps.fromSeconds(1698361778))); + exercise5.eval( + ast, + ImmutableMap.of("time", Timestamp.newBuilder().setSeconds(1698361778).build())); String jsonOutput = exercise5.toJson(evaluatedResult); assertThat(jsonOutput) diff --git a/codelab/src/test/codelab/Exercise6Test.java b/codelab/src/test/codelab/Exercise6Test.java index ed3c49bd4..d4add9b90 100644 --- a/codelab/src/test/codelab/Exercise6Test.java +++ b/codelab/src/test/codelab/Exercise6Test.java @@ -20,10 +20,10 @@ import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; import com.google.protobuf.Value; -import com.google.protobuf.util.Timestamps; import com.google.rpc.context.AttributeContext; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.internal.ProtoTimeUtils; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,7 +51,7 @@ public void evaluate_constructAttributeContext() { + "time: now" + "}"; // Values for `now` and `jwt` variables to be passed into the runtime - Timestamp now = Timestamps.now(); + Timestamp now = ProtoTimeUtils.now(); ImmutableMap jwt = ImmutableMap.of( "sub", "serviceAccount:delegate@acme.co", diff --git a/codelab/src/test/codelab/solutions/BUILD.bazel b/codelab/src/test/codelab/solutions/BUILD.bazel index 676559605..9eebbc3f4 100644 --- a/codelab/src/test/codelab/solutions/BUILD.bazel +++ b/codelab/src/test/codelab/solutions/BUILD.bazel @@ -73,7 +73,7 @@ java_test( "//codelab:solutions", "//common:cel_ast", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", ], @@ -87,10 +87,10 @@ java_test( "//:java_truth", "//codelab:solutions", "//common:cel_ast", + "//common/internal:proto_time_utils", "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", ], diff --git a/codelab/src/test/codelab/solutions/Exercise5Test.java b/codelab/src/test/codelab/solutions/Exercise5Test.java index e7a105a94..405a73f4d 100644 --- a/codelab/src/test/codelab/solutions/Exercise5Test.java +++ b/codelab/src/test/codelab/solutions/Exercise5Test.java @@ -17,7 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableMap; -import com.google.protobuf.util.Timestamps; +import com.google.protobuf.Timestamp; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; import java.util.Map; @@ -48,7 +48,9 @@ public void evaluate_jwtWithTimeVariable_producesJsonString() throws Exception { @SuppressWarnings("unchecked") Map evaluatedResult = (Map) - exercise5.eval(ast, ImmutableMap.of("time", Timestamps.fromSeconds(1698361778))); + exercise5.eval( + ast, + ImmutableMap.of("time", Timestamp.newBuilder().setSeconds(1698361778).build())); String jsonOutput = exercise5.toJson(evaluatedResult); assertThat(jsonOutput) diff --git a/codelab/src/test/codelab/solutions/Exercise6Test.java b/codelab/src/test/codelab/solutions/Exercise6Test.java index 0c31a05e8..fbb1848cc 100644 --- a/codelab/src/test/codelab/solutions/Exercise6Test.java +++ b/codelab/src/test/codelab/solutions/Exercise6Test.java @@ -20,10 +20,10 @@ import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; import com.google.protobuf.Value; -import com.google.protobuf.util.Timestamps; import com.google.rpc.context.AttributeContext; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.internal.ProtoTimeUtils; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,7 +51,7 @@ public void evaluate_constructAttributeContext() { + "time: now" + "}"; // Values for `now` and `jwt` variables to be passed into the runtime - Timestamp now = Timestamps.now(); + Timestamp now = ProtoTimeUtils.now(); ImmutableMap jwt = ImmutableMap.of( "sub", "serviceAccount:delegate@acme.co", diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index 576cf94b3..2e83bd7f0 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -127,3 +127,13 @@ java_library( name = "proto_java_qualified_names", exports = ["//common/src/main/java/dev/cel/common/internal:proto_java_qualified_names"], ) + +java_library( + name = "proto_time_utils", + exports = ["//common/src/main/java/dev/cel/common/internal:proto_time_utils"], +) + +cel_android_library( + name = "proto_time_util_android", + exports = ["//common/src/main/java/dev/cel/common/internal:proto_time_util_android"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 2ce9c303f..4701df27f 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -204,10 +204,10 @@ java_library( tags = [ ], deps = [ + "//common/internal:proto_time_utils", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java index 46e572b44..6002e00e3 100644 --- a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java +++ b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java @@ -27,8 +27,7 @@ import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; import com.google.protobuf.Value; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; +import dev.cel.common.internal.ProtoTimeUtils; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -112,11 +111,11 @@ public static Value adaptValueToJsonValue(Object value) { } if (value instanceof Timestamp) { // CEL follows the proto3 to JSON conversion which formats as an RFC 3339 encoded JSON string. - String ts = Timestamps.toString((Timestamp) value); + String ts = ProtoTimeUtils.toString((Timestamp) value); return json.setStringValue(ts).build(); } if (value instanceof Duration) { - String duration = Durations.toString((Duration) value); + String duration = ProtoTimeUtils.toString((Duration) value); return json.setStringValue(duration).build(); } if (value instanceof FieldMask) { diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index 88f6f06dc..cdbc8774b 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -392,3 +392,29 @@ java_library( "//common/annotations", ], ) + +java_library( + name = "proto_time_utils", + srcs = ["ProtoTimeUtils.java"], + tags = [ + ], + deps = [ + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +cel_android_library( + name = "proto_time_util_android", + srcs = ["ProtoTimeUtils.java"], + tags = [ + ], + deps = [ + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) diff --git a/common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java b/common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java new file mode 100644 index 000000000..cc705fccd --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java @@ -0,0 +1,568 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import static com.google.common.math.LongMath.checkedAdd; +import static com.google.common.math.LongMath.checkedMultiply; +import static com.google.common.math.LongMath.checkedSubtract; + +import com.google.common.base.Strings; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; +import dev.cel.common.annotations.Internal; +import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.Comparator; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Utility methods for handling {@code protobuf/duration.proto} and {@code + * protobuf/timestamp.proto}. + * + *

Forked from com.google.protobuf.util package. These exist because there's not an equivalent + * util JAR published in maven central that's compatible with protolite. See relevant github issue. + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +// Forked from protobuf-java-utils. Retaining units/date API for parity. +@SuppressWarnings({"GoodTime-ApiWithNumericTimeUnit", "JavaUtilDate"}) +public final class ProtoTimeUtils { + + // Timestamp for "0001-01-01T00:00:00Z" + private static final long TIMESTAMP_SECONDS_MIN = -62135596800L; + + // Timestamp for "9999-12-31T23:59:59Z" + private static final long TIMESTAMP_SECONDS_MAX = 253402300799L; + private static final long DURATION_SECONDS_MIN = -315576000000L; + private static final long DURATION_SECONDS_MAX = 315576000000L; + private static final int MILLIS_PER_SECOND = 1000; + + private static final int NANOS_PER_SECOND = 1000000000; + private static final int NANOS_PER_MILLISECOND = 1000000; + private static final int NANOS_PER_MICROSECOND = 1000; + + private static final long SECONDS_PER_MINUTE = 60L; + private static final long SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60; + + private static final ThreadLocal TIMESTAMP_FORMAT = + new ThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return createTimestampFormat(); + } + }; + + private enum TimestampComparator implements Comparator, Serializable { + INSTANCE; + + @Override + public int compare(Timestamp t1, Timestamp t2) { + checkValid(t1); + checkValid(t2); + int secDiff = Long.compare(t1.getSeconds(), t2.getSeconds()); + return (secDiff != 0) ? secDiff : Integer.compare(t1.getNanos(), t2.getNanos()); + } + } + + private enum DurationComparator implements Comparator, Serializable { + INSTANCE; + + @Override + public int compare(Duration d1, Duration d2) { + checkValid(d1); + checkValid(d2); + int secDiff = Long.compare(d1.getSeconds(), d2.getSeconds()); + return (secDiff != 0) ? secDiff : Integer.compare(d1.getNanos(), d2.getNanos()); + } + } + + /** + * A constant holding the {@link Timestamp} of epoch time, {@code 1970-01-01T00:00:00.000000000Z}. + */ + public static final Timestamp TIMESTAMP_EPOCH = + Timestamp.newBuilder().setSeconds(0).setNanos(0).build(); + + /** A constant holding the duration of zero. */ + public static final Duration DURATION_ZERO = + Duration.newBuilder().setSeconds(0L).setNanos(0).build(); + + private static SimpleDateFormat createTimestampFormat() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends + // backwards to year one) for timestamp formatting. + calendar.setGregorianChange(new Date(Long.MIN_VALUE)); + sdf.setCalendar(calendar); + return sdf; + } + + /** Convert a {@link Instant} object to proto-based {@link Timestamp}. */ + public static Timestamp toProtoTimestamp(Instant instant) { + return normalizedTimestamp(instant.getEpochSecond(), instant.getNano()); + } + + /** Convert a {@link java.time.Duration} object to proto-based {@link Duration}. */ + public static Duration toProtoDuration(java.time.Duration duration) { + return normalizedDuration(duration.getSeconds(), duration.getNano()); + } + + /** Convert a {@link Timestamp} object to java-based {@link Instant}. */ + public static Instant toJavaInstant(Timestamp timestamp) { + timestamp = normalizedTimestamp(timestamp.getSeconds(), timestamp.getNanos()); + return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()); + } + + /** Convert a {@link Duration} object to java-based {@link java.time.Duration}. */ + public static java.time.Duration toJavaDuration(Duration duration) { + duration = normalizedDuration(duration.getSeconds(), duration.getNanos()); + return java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNanos()); + } + + /** Convert a Timestamp to the number of seconds elapsed from the epoch. */ + public static long toSeconds(Timestamp timestamp) { + return checkValid(timestamp).getSeconds(); + } + + /** + * Convert a Duration to the number of seconds. The result will be rounded towards 0 to the + * nearest second. E.g., if the duration represents -1 nanosecond, it will be rounded to 0. + */ + public static long toSeconds(Duration duration) { + return checkValid(duration).getSeconds(); + } + + /** + * Convert a Duration to the number of hours. The result will be rounded towards 0 to the nearest + * hour. + */ + public static long toHours(Duration duration) { + return checkValid(duration).getSeconds() / SECONDS_PER_HOUR; + } + + /** + * Convert a Duration to the number of minutes. The result will be rounded towards 0 to the + * nearest minute. + */ + public static long toMinutes(Duration duration) { + return checkValid(duration).getSeconds() / SECONDS_PER_MINUTE; + } + + /** + * Convert a Duration to the number of milliseconds. The result will be rounded towards 0 to the + * nearest millisecond. E.g., if the duration represents -1 nanosecond, it will be rounded to 0. + */ + public static long toMillis(Duration duration) { + checkValid(duration); + return checkedAdd( + checkedMultiply(duration.getSeconds(), MILLIS_PER_SECOND), + duration.getNanos() / NANOS_PER_MILLISECOND); + } + + /** Create a Timestamp from the number of seconds elapsed from the epoch. */ + public static Timestamp fromSecondsToTimestamp(long seconds) { + return normalizedTimestamp(seconds, 0); + } + + /** Create a Timestamp from the number of seconds elapsed from the epoch. */ + public static Duration fromSecondsToDuration(long seconds) { + return normalizedDuration(seconds, 0); + } + + /** Create a Timestamp from the number of seconds elapsed from the epoch. */ + public static Duration fromMillisToDuration(long milliseconds) { + return normalizedDuration( + milliseconds / MILLIS_PER_SECOND, + (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Duration} is not valid. */ + @CanIgnoreReturnValue + private static Duration checkValid(Duration duration) { + long seconds = duration.getSeconds(); + int nanos = duration.getNanos(); + if (!isDurationValid(seconds, nanos)) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Duration is not valid. See proto definition for valid values. " + + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. " + + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " + + "Nanos must have the same sign as seconds", + seconds, nanos)); + } + return duration; + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Timestamp} is not valid. */ + @CanIgnoreReturnValue + private static Timestamp checkValid(Timestamp timestamp) { + long seconds = timestamp.getSeconds(); + int nanos = timestamp.getNanos(); + if (!isTimestampValid(seconds, nanos)) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Timestamp is not valid. See proto definition for valid values. " + + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]. " + + "Nanos (%s) must be in range [0, +999,999,999].", + seconds, nanos)); + } + return timestamp; + } + + /** + * Convert Timestamp to RFC 3339 date string format. The output will always be Z-normalized and + * uses 0, 3, 6 or 9 fractional digits as required to represent the exact value. Note that + * Timestamp can only represent time from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. + * See https://www.ietf.org/rfc/rfc3339.txt + * + *

Example of generated format: "1972-01-01T10:00:20.021Z" + * + * @return The string representation of the given timestamp. + * @throws IllegalArgumentException if the given timestamp is not in the valid range. + */ + public static String toString(Timestamp timestamp) { + checkValid(timestamp); + + long seconds = timestamp.getSeconds(); + int nanos = timestamp.getNanos(); + + StringBuilder result = new StringBuilder(); + // Format the seconds part. + Date date = new Date(seconds * MILLIS_PER_SECOND); + result.append(TIMESTAMP_FORMAT.get().format(date)); + // Format the nanos part. + if (nanos != 0) { + result.append("."); + result.append(formatNanos(nanos)); + } + result.append("Z"); + return result.toString(); + } + + /** + * Convert Duration to string format. The string format will contains 3, 6, or 9 fractional digits + * depending on the precision required to represent the exact Duration value. For example: "1s", + * "1.010s", "1.000000100s", "-3.100s" The range that can be represented by Duration is from + * -315,576,000,000 to +315,576,000,000 inclusive (in seconds). + * + * @return The string representation of the given duration. + * @throws IllegalArgumentException if the given duration is not in the valid range. + */ + public static String toString(Duration duration) { + checkValid(duration); + + long seconds = duration.getSeconds(); + int nanos = duration.getNanos(); + + StringBuilder result = new StringBuilder(); + if (seconds < 0 || nanos < 0) { + result.append("-"); + seconds = -seconds; + nanos = -nanos; + } + result.append(seconds); + if (nanos != 0) { + result.append("."); + result.append(formatNanos(nanos)); + } + result.append("s"); + return result.toString(); + } + + /** + * Parse from RFC 3339 date string to Timestamp. This method accepts all outputs of {@link + * #toString(Timestamp)} and it also accepts any fractional digits (or none) and any offset as + * long as they fit into nano-seconds precision. + * + *

Example of accepted format: "1972-01-01T10:00:20.021-05:00" + * + * @return a Timestamp parsed from the string + * @throws ParseException if parsing fails + */ + public static Timestamp parse(String value) throws ParseException { + int dayOffset = value.indexOf('T'); + if (dayOffset == -1) { + throw new ParseException("Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0); + } + int timezoneOffsetPosition = value.indexOf('Z', dayOffset); + if (timezoneOffsetPosition == -1) { + timezoneOffsetPosition = value.indexOf('+', dayOffset); + } + if (timezoneOffsetPosition == -1) { + timezoneOffsetPosition = value.indexOf('-', dayOffset); + } + if (timezoneOffsetPosition == -1) { + throw new ParseException("Failed to parse timestamp: missing valid timezone offset.", 0); + } + // Parse seconds and nanos. + String timeValue = value.substring(0, timezoneOffsetPosition); + String secondValue = timeValue; + String nanoValue = ""; + int pointPosition = timeValue.indexOf('.'); + if (pointPosition != -1) { + secondValue = timeValue.substring(0, pointPosition); + nanoValue = timeValue.substring(pointPosition + 1); + } + Date date = TIMESTAMP_FORMAT.get().parse(secondValue); + long seconds = date.getTime() / MILLIS_PER_SECOND; + int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); + // Parse timezone offsets. + if (value.charAt(timezoneOffsetPosition) == 'Z') { + if (value.length() != timezoneOffsetPosition + 1) { + throw new ParseException( + "Failed to parse timestamp: invalid trailing data \"" + + value.substring(timezoneOffsetPosition) + + "\"", + 0); + } + } else { + String offsetValue = value.substring(timezoneOffsetPosition + 1); + long offset = parseTimezoneOffset(offsetValue); + if (value.charAt(timezoneOffsetPosition) == '+') { + seconds -= offset; + } else { + seconds += offset; + } + } + try { + return normalizedTimestamp(seconds, nanos); + } catch (IllegalArgumentException e) { + ParseException ex = + new ParseException( + "Failed to parse timestamp " + value + " Timestamp is out of range.", 0); + ex.initCause(e); + throw ex; + } + } + + /** Adds two durations */ + public static Duration add(Duration d1, Duration d2) { + java.time.Duration javaDuration1 = ProtoTimeUtils.toJavaDuration(checkValid(d1)); + java.time.Duration javaDuration2 = ProtoTimeUtils.toJavaDuration(checkValid(d2)); + + java.time.Duration sum = javaDuration1.plus(javaDuration2); + + return ProtoTimeUtils.toProtoDuration(sum); + } + + /** Adds two timestamps. */ + public static Timestamp add(Timestamp ts, Duration dur) { + Instant javaInstant = ProtoTimeUtils.toJavaInstant(checkValid(ts)); + java.time.Duration javaDuration = ProtoTimeUtils.toJavaDuration(checkValid(dur)); + + Instant newInstant = javaInstant.plus(javaDuration); + + return ProtoTimeUtils.toProtoTimestamp(newInstant); + } + + /** Subtract a duration from another. */ + public static Duration subtract(Duration d1, Duration d2) { + java.time.Duration javaDuration1 = ProtoTimeUtils.toJavaDuration(checkValid(d1)); + java.time.Duration javaDuration2 = ProtoTimeUtils.toJavaDuration(checkValid(d2)); + + java.time.Duration sum = javaDuration1.minus(javaDuration2); + + return ProtoTimeUtils.toProtoDuration(sum); + } + + /** Subtracts two timestamps */ + public static Timestamp subtract(Timestamp ts, Duration dur) { + Instant javaInstant = ProtoTimeUtils.toJavaInstant(checkValid(ts)); + java.time.Duration javaDuration = ProtoTimeUtils.toJavaDuration(checkValid(dur)); + + Instant newInstant = javaInstant.minus(javaDuration); + + return ProtoTimeUtils.toProtoTimestamp(newInstant); + } + + /** Calculate the difference between two timestamps. */ + public static Duration between(Timestamp from, Timestamp to) { + Instant javaFrom = ProtoTimeUtils.toJavaInstant(checkValid(from)); + Instant javaTo = ProtoTimeUtils.toJavaInstant(checkValid(to)); + + java.time.Duration between = java.time.Duration.between(javaFrom, javaTo); + + return ProtoTimeUtils.toProtoDuration(between); + } + + /** + * Compares two durations. The value returned is identical to what would be returned by: {@code + * Durations.comparator().compare(x, y)}. + * + * @return the value {@code 0} if {@code x == y}; a value less than {@code 0} if {@code x < y}; + * and a value greater than {@code 0} if {@code x > y} + */ + public static int compare(Duration x, Duration y) { + return DurationComparator.INSTANCE.compare(x, y); + } + + /** + * Compares two timestamps. The value returned is identical to what would be returned by: {@code + * Timestamps.comparator().compare(x, y)}. + * + * @return the value {@code 0} if {@code x == y}; a value less than {@code 0} if {@code x < y}; + * and a value greater than {@code 0} if {@code x > y} + */ + public static int compare(Timestamp x, Timestamp y) { + return TimestampComparator.INSTANCE.compare(x, y); + } + + /** + * Create a {@link Timestamp} using the best-available (in terms of precision) system clock. + * + *

Note: that while this API is convenient, it may harm the testability of your code, as + * you're unable to mock the current time. Instead, you may want to consider injecting a clock + * instance to read the current time. + */ + public static Timestamp now() { + Instant nowInstant = Instant.now(); + + return Timestamp.newBuilder() + .setSeconds(nowInstant.getEpochSecond()) + .setNanos(nowInstant.getNano()) + .build(); + } + + private static long parseTimezoneOffset(String value) throws ParseException { + int pos = value.indexOf(':'); + if (pos == -1) { + throw new ParseException("Invalid offset value: " + value, 0); + } + String hours = value.substring(0, pos); + String minutes = value.substring(pos + 1); + try { + return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60; + } catch (NumberFormatException e) { + ParseException ex = new ParseException("Invalid offset value: " + value, 0); + ex.initCause(e); + throw ex; + } + } + + private static int parseNanos(String value) throws ParseException { + int result = 0; + for (int i = 0; i < 9; ++i) { + result = result * 10; + if (i < value.length()) { + if (value.charAt(i) < '0' || value.charAt(i) > '9') { + throw new ParseException("Invalid nanoseconds.", 0); + } + result += value.charAt(i) - '0'; + } + } + return result; + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Duration}. The {@code + * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos} + * value must be in the range [-999,999,999, +999,999,999]. + * + *

Note: Durations less than one second are represented with a 0 {@code seconds} field + * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero + * value for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + private static boolean isDurationValid(long seconds, int nanos) { + if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { + return false; + } + if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { + return false; + } + if (seconds < 0 || nanos < 0) { + if (seconds > 0 || nanos > 0) { + return false; + } + } + return true; + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Timestamp}. The {@code + * seconds} value must be in the range [-62,135,596,800, +253,402,300,799] (i.e., between + * 0001-01-01T00:00:00Z and 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range + * [0, +999,999,999]. + * + *

Note: Negative second values with fractional seconds must still have non-negative + * nanos values that count forward in time. + */ + private static boolean isTimestampValid(long seconds, int nanos) { + if (!isTimestampSecondsValid(seconds)) { + return false; + } + + return nanos >= 0 && nanos < NANOS_PER_SECOND; + } + + /** + * Returns true if the given number of seconds is valid, if combined with a valid number of nanos. + * The {@code seconds} value must be in the range [-62,135,596,800, +253,402,300,799] (i.e., + * between 0001-01-01T00:00:00Z and 9999-12-31T23:59:59Z). + */ + @SuppressWarnings("GoodTime") // this is a legacy conversion API + private static boolean isTimestampSecondsValid(long seconds) { + return seconds >= TIMESTAMP_SECONDS_MIN && seconds <= TIMESTAMP_SECONDS_MAX; + } + + private static Timestamp normalizedTimestamp(long seconds, int nanos) { + if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { + seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); + nanos = nanos % NANOS_PER_SECOND; + } + if (nanos < 0) { + nanos = nanos + NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) + seconds = checkedSubtract(seconds, 1); + } + Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); + return checkValid(timestamp); + } + + private static Duration normalizedDuration(long seconds, int nanos) { + if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { + seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); + nanos %= NANOS_PER_SECOND; + } + if (seconds > 0 && nanos < 0) { + nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) + seconds--; // no overflow since seconds is positive (and we're decrementing) + } + if (seconds < 0 && nanos > 0) { + nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting) + seconds++; // no overflow since seconds is negative (and we're incrementing) + } + Duration duration = Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); + return checkValid(duration); + } + + private static String formatNanos(int nanos) { + // Determine whether to use 3, 6, or 9 digits for the nano part. + if (nanos % NANOS_PER_MILLISECOND == 0) { + return String.format(Locale.ENGLISH, "%1$03d", nanos / NANOS_PER_MILLISECOND); + } else if (nanos % NANOS_PER_MICROSECOND == 0) { + return String.format(Locale.ENGLISH, "%1$06d", nanos / NANOS_PER_MICROSECOND); + } else { + return String.format(Locale.ENGLISH, "%1$09d", nanos); + } + } + + private ProtoTimeUtils() {} +} diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index ce29083bb..904789836 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -150,11 +150,11 @@ java_library( ":cel_value", ":values", "//common/annotations", + "//common/internal:proto_time_utils", "//common/internal:well_known_proto", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -169,9 +169,9 @@ cel_android_library( ":cel_value_android", ":values_android", "//common/annotations", + "//common/internal:proto_time_util_android", "//common/internal:well_known_proto_android", "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], diff --git a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java index d709e2585..3310b3dad 100644 --- a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -16,14 +16,13 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.math.LongMath.checkedAdd; -import static com.google.common.math.LongMath.checkedSubtract; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.BoolValue; import com.google.protobuf.ByteString; import com.google.protobuf.DoubleValue; +import com.google.protobuf.Duration; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; import com.google.protobuf.Int64Value; @@ -35,12 +34,9 @@ import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import dev.cel.common.annotations.Internal; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.internal.WellKnownProto; -import java.time.Duration; -import java.time.Instant; import java.util.Optional; /** @@ -66,9 +62,9 @@ public Object fromCelValueToJavaObject(CelValue celValue) { Preconditions.checkNotNull(celValue); if (celValue instanceof TimestampValue) { - return TimeUtils.toProtoTimestamp(((TimestampValue) celValue).value()); + return ProtoTimeUtils.toProtoTimestamp(((TimestampValue) celValue).value()); } else if (celValue instanceof DurationValue) { - return TimeUtils.toProtoDuration(((DurationValue) celValue).value()); + return ProtoTimeUtils.toProtoDuration(((DurationValue) celValue).value()); } else if (celValue instanceof BytesValue) { return ByteString.copyFrom(((BytesValue) celValue).value().toByteArray()); } else if (celValue.equals(NullValue.NULL_VALUE)) { @@ -107,10 +103,9 @@ protected CelValue fromWellKnownProtoToCelValue( case JSON_LIST_VALUE: return adaptJsonListToCelValue((com.google.protobuf.ListValue) message); case DURATION: - return DurationValue.create( - TimeUtils.toJavaDuration((com.google.protobuf.Duration) message)); + return DurationValue.create(ProtoTimeUtils.toJavaDuration((Duration) message)); case TIMESTAMP: - return TimestampValue.create(TimeUtils.toJavaInstant((Timestamp) message)); + return TimestampValue.create(ProtoTimeUtils.toJavaInstant((Timestamp) message)); case BOOL_VALUE: return fromJavaPrimitiveToCelValue(((BoolValue) message).getValue()); case BYTES_VALUE: @@ -173,59 +168,5 @@ private MapValue adaptJsonStructToCelValue(Struct struct) { e -> adaptJsonValueToCelValue(e.getValue())))); } - /** Helper to convert between java.util.time and protobuf duration/timestamp. */ - private static class TimeUtils { - private static final int NANOS_PER_SECOND = 1000000000; - - private static Instant toJavaInstant(Timestamp timestamp) { - timestamp = normalizedTimestamp(timestamp.getSeconds(), timestamp.getNanos()); - return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()); - } - - private static Duration toJavaDuration(com.google.protobuf.Duration duration) { - duration = normalizedDuration(duration.getSeconds(), duration.getNanos()); - return java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNanos()); - } - - private static Timestamp toProtoTimestamp(Instant instant) { - return normalizedTimestamp(instant.getEpochSecond(), instant.getNano()); - } - - private static com.google.protobuf.Duration toProtoDuration(Duration duration) { - return normalizedDuration(duration.getSeconds(), duration.getNano()); - } - - private static Timestamp normalizedTimestamp(long seconds, int nanos) { - if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { - seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); - nanos = nanos % NANOS_PER_SECOND; - } - if (nanos < 0) { - nanos = nanos + NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) - seconds = checkedSubtract(seconds, 1); - } - Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); - return Timestamps.checkValid(timestamp); - } - - private static com.google.protobuf.Duration normalizedDuration(long seconds, int nanos) { - if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { - seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); - nanos %= NANOS_PER_SECOND; - } - if (seconds > 0 && nanos < 0) { - nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) - seconds--; // no overflow since seconds is positive (and we're decrementing) - } - if (seconds < 0 && nanos > 0) { - nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting) - seconds++; // no overflow since seconds is negative (and we're incrementing) - } - com.google.protobuf.Duration duration = - com.google.protobuf.Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); - return Durations.checkValid(duration); - } - } - protected BaseProtoCelValueConverter() {} } diff --git a/common/src/test/java/dev/cel/common/values/BUILD.bazel b/common/src/test/java/dev/cel/common/values/BUILD.bazel index 19cdf7204..23580157a 100644 --- a/common/src/test/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel @@ -20,6 +20,7 @@ java_library( "//common/internal:default_message_factory", "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", + "//common/internal:proto_time_utils", "//common/internal:well_known_proto", "//common/types", "//common/types:type_providers", @@ -37,7 +38,6 @@ java_library( "@maven//:com_google_guava_guava", "@maven//:com_google_guava_guava_testlib", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", "@maven_android//:com_google_protobuf_protobuf_javalite", diff --git a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java index 0d74c33e0..f5dd756e2 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -19,11 +19,10 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; +import dev.cel.common.internal.ProtoTimeUtils; import java.time.Instant; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,7 +42,7 @@ public void fromCelValueToJavaObject_returnsTimestampValue() { PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( TimestampValue.create(Instant.ofEpochSecond(50))); - assertThat(timestamp).isEqualTo(Timestamps.fromSeconds(50)); + assertThat(timestamp).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); } @Test @@ -53,7 +52,7 @@ public void fromCelValueToJavaObject_returnsDurationValue() { PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( DurationValue.create(java.time.Duration.ofSeconds(10))); - assertThat(duration).isEqualTo(Durations.fromSeconds(10)); + assertThat(duration).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(10)); } @Test diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java index d79f31bb3..88fc0ae16 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java @@ -20,8 +20,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelRuntimeException; @@ -30,6 +28,7 @@ import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; import dev.cel.common.internal.ProtoMessageFactory; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelValueProvider.CombinedCelValueProvider; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.expr.conformance.proto2.TestAllTypesExtensions; @@ -92,9 +91,9 @@ public void newValue_createProtoMessage_fieldsPopulated() { "single_string", "hello", "single_timestamp", - Timestamps.fromSeconds(50), + ProtoTimeUtils.fromSecondsToTimestamp(50), "single_duration", - Durations.fromSeconds(100))) + ProtoTimeUtils.fromSecondsToDuration(100))) .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index ca4afad54..fcba9b219 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -13,6 +13,7 @@ java_library( "//common:cel_ast", "//common:compiler_common", "//common:options", + "//common/internal:proto_time_utils", "//common/types", "//common/types:type_providers", "//compiler", @@ -30,7 +31,6 @@ java_library( "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", "@maven_android//:com_google_protobuf_protobuf_javalite", diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index 55156a7cc..a03a77b54 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -23,8 +23,6 @@ import com.google.protobuf.ByteString; import com.google.protobuf.DoubleValue; import com.google.protobuf.NullValue; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; @@ -36,6 +34,7 @@ import dev.cel.common.CelOptions; import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelValidationException; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -68,12 +67,16 @@ private enum ConstantTestCases { UINT("5u", "0u", SimpleType.UINT, UnsignedLong.valueOf(5)), BOOL("true", "false", SimpleType.BOOL, true), BYTES("b'abc'", "b''", SimpleType.BYTES, ByteString.copyFromUtf8("abc")), - DURATION("duration('180s')", "duration('0s')", SimpleType.DURATION, Durations.fromSeconds(180)), + DURATION( + "duration('180s')", + "duration('0s')", + SimpleType.DURATION, + ProtoTimeUtils.fromSecondsToDuration(180)), TIMESTAMP( "timestamp(1685552643)", "timestamp(0)", SimpleType.TIMESTAMP, - Timestamps.fromSeconds(1685552643)); + ProtoTimeUtils.fromSecondsToTimestamp(1685552643)); private final String sourceWithNonZeroValue; private final String sourceWithZeroValue; diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 1373d4258..ddc104e77 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -545,11 +545,11 @@ java_library( "//common:runtime_exception", "//common/annotations", "//common/internal:comparison_functions", + "//common/internal:proto_time_utils", "//common/internal:safe_string_formatter", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -568,10 +568,10 @@ cel_android_library( "//common:runtime_exception", "//common/annotations", "//common/internal:comparison_functions_android", + "//common/internal:proto_time_util_android", "//common/internal:safe_string_formatter", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 3aff6105d..a277d9b7e 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -26,13 +26,12 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; import dev.cel.common.internal.ComparisonFunctions; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.internal.SafeStringFormatter; import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Arithmetic; import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.BooleanOperator; @@ -353,11 +352,14 @@ public enum Arithmetic implements StandardOverload { ADD_DURATION_DURATION( (bindingHelper) -> CelFunctionBinding.from( - "add_duration_duration", Duration.class, Duration.class, Durations::add)), + "add_duration_duration", Duration.class, Duration.class, ProtoTimeUtils::add)), ADD_TIMESTAMP_DURATION( (bindingHelper) -> CelFunctionBinding.from( - "add_timestamp_duration", Timestamp.class, Duration.class, Timestamps::add)), + "add_timestamp_duration", + Timestamp.class, + Duration.class, + ProtoTimeUtils::add)), ADD_STRING( (bindingHelper) -> CelFunctionBinding.from( @@ -368,7 +370,7 @@ public enum Arithmetic implements StandardOverload { "add_duration_timestamp", Duration.class, Timestamp.class, - (Duration x, Timestamp y) -> Timestamps.add(y, x))), + (Duration x, Timestamp y) -> ProtoTimeUtils.add(y, x))), ADD_LIST( (bindingHelper) -> CelFunctionBinding.from( @@ -392,14 +394,14 @@ public enum Arithmetic implements StandardOverload { "subtract_timestamp_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> Timestamps.between(y, x))), + (Timestamp x, Timestamp y) -> ProtoTimeUtils.between(y, x))), SUBTRACT_TIMESTAMP_DURATION( (bindingHelper) -> CelFunctionBinding.from( "subtract_timestamp_duration", Timestamp.class, Duration.class, - Timestamps::subtract)), + ProtoTimeUtils::subtract)), SUBTRACT_UINT64( (bindingHelper) -> { if (bindingHelper.celOptions.enableUnsignedLongs()) { @@ -438,7 +440,7 @@ public enum Arithmetic implements StandardOverload { "subtract_duration_duration", Duration.class, Duration.class, - Durations::subtract)), + ProtoTimeUtils::subtract)), MULTIPLY_INT64( (bindingHelper) -> CelFunctionBinding.from( @@ -716,7 +718,7 @@ public enum Conversions implements StandardOverload { TIMESTAMP_TO_INT64( (bindingHelper) -> CelFunctionBinding.from( - "timestamp_to_int64", Timestamp.class, Timestamps::toSeconds)), + "timestamp_to_int64", Timestamp.class, ProtoTimeUtils::toSeconds)), UINT64_TO_INT64( (bindingHelper) -> { if (bindingHelper.celOptions.enableUnsignedLongs()) { @@ -892,10 +894,11 @@ public enum Conversions implements StandardOverload { TIMESTAMP_TO_STRING( (bindingHelper) -> CelFunctionBinding.from( - "timestamp_to_string", Timestamp.class, Timestamps::toString)), + "timestamp_to_string", Timestamp.class, ProtoTimeUtils::toString)), DURATION_TO_STRING( (bindingHelper) -> - CelFunctionBinding.from("duration_to_string", Duration.class, Durations::toString)), + CelFunctionBinding.from( + "duration_to_string", Duration.class, ProtoTimeUtils::toString)), UINT64_TO_STRING( (bindingHelper) -> { if (bindingHelper.celOptions.enableUnsignedLongs()) { @@ -938,7 +941,7 @@ public enum Conversions implements StandardOverload { String.class, (String ts) -> { try { - return Timestamps.parse(ts); + return ProtoTimeUtils.parse(ts); } catch (ParseException e) { throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); } @@ -949,7 +952,8 @@ public enum Conversions implements StandardOverload { "timestamp_to_timestamp", Timestamp.class, (Timestamp x) -> x)), INT64_TO_TIMESTAMP( (bindingHelper) -> - CelFunctionBinding.from("int64_to_timestamp", Long.class, Timestamps::fromSeconds)), + CelFunctionBinding.from( + "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp)), TO_DYN( (bindingHelper) -> CelFunctionBinding.from("to_dyn", Object.class, (Object arg) -> arg)); @@ -1194,22 +1198,23 @@ public enum DateTime implements StandardOverload { (long) (newLocalDateTime(ts, tz).getNano() / 1e+6))), DURATION_TO_HOURS( (bindingHelper) -> - CelFunctionBinding.from("duration_to_hours", Duration.class, Durations::toHours)), + CelFunctionBinding.from( + "duration_to_hours", Duration.class, ProtoTimeUtils::toHours)), DURATION_TO_MINUTES( (bindingHelper) -> CelFunctionBinding.from( - "duration_to_minutes", Duration.class, Durations::toMinutes)), + "duration_to_minutes", Duration.class, ProtoTimeUtils::toMinutes)), DURATION_TO_SECONDS( (bindingHelper) -> CelFunctionBinding.from( - "duration_to_seconds", Duration.class, Durations::toSeconds)), + "duration_to_seconds", Duration.class, ProtoTimeUtils::toSeconds)), DURATION_TO_MILLISECONDS( (bindingHelper) -> CelFunctionBinding.from( "duration_to_milliseconds", Duration.class, (Duration arg) -> - Durations.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis())); + ProtoTimeUtils.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis())); private final FunctionBindingCreator bindingCreator; @@ -1323,7 +1328,7 @@ public enum Comparison implements StandardOverload { "less_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> Durations.compare(x, y) < 0), + (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) < 0), false), LESS_STRING( (bindingHelper) -> @@ -1339,7 +1344,7 @@ public enum Comparison implements StandardOverload { "less_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> Timestamps.compare(x, y) < 0), + (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) < 0), false), LESS_EQUALS_BOOL( (bindingHelper) -> @@ -1372,7 +1377,7 @@ public enum Comparison implements StandardOverload { "less_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> Durations.compare(x, y) <= 0), + (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) <= 0), false), LESS_EQUALS_INT64( (bindingHelper) -> @@ -1393,7 +1398,7 @@ public enum Comparison implements StandardOverload { "less_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> Timestamps.compare(x, y) <= 0), + (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) <= 0), false), LESS_EQUALS_UINT64( (bindingHelper) -> { @@ -1489,7 +1494,7 @@ public enum Comparison implements StandardOverload { "greater_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> Durations.compare(x, y) > 0), + (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) > 0), false), GREATER_INT64( (bindingHelper) -> @@ -1510,7 +1515,7 @@ public enum Comparison implements StandardOverload { "greater_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> Timestamps.compare(x, y) > 0), + (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) > 0), false), GREATER_UINT64( (bindingHelper) -> { @@ -1609,7 +1614,7 @@ public enum Comparison implements StandardOverload { "greater_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> Durations.compare(x, y) >= 0), + (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) >= 0), false), GREATER_EQUALS_INT64( (bindingHelper) -> @@ -1630,7 +1635,7 @@ public enum Comparison implements StandardOverload { "greater_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> Timestamps.compare(x, y) >= 0), + (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) >= 0), false), GREATER_EQUALS_UINT64( (bindingHelper) -> { diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 47823ef66..7fabcfe8b 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -85,6 +85,7 @@ java_library( "//common/internal:default_message_factory", "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", + "//common/internal:proto_time_utils", "//common/internal:well_known_proto", "//common/types", "//common/types:cel_v1alpha1_types", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java index d152f6f20..76a67e9b7 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java @@ -36,13 +36,12 @@ import com.google.protobuf.Timestamp; import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import com.google.protobuf.util.Values; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructTypeReference; import dev.cel.common.values.ProtoMessageLiteValueProvider; @@ -286,11 +285,13 @@ public void fieldSelection_duration() throws Exception { String expression = "msg.single_duration"; CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); TestAllTypes msg = - TestAllTypes.newBuilder().setSingleDuration(Durations.fromMinutes(10)).build(); + TestAllTypes.newBuilder() + .setSingleDuration(ProtoTimeUtils.fromSecondsToDuration(600)) + .build(); Duration result = (Duration) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(Durations.fromMinutes(10)); + assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(600)); } @Test @@ -298,11 +299,13 @@ public void fieldSelection_timestamp() throws Exception { String expression = "msg.single_timestamp"; CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); TestAllTypes msg = - TestAllTypes.newBuilder().setSingleTimestamp(Timestamps.fromSeconds(50)).build(); + TestAllTypes.newBuilder() + .setSingleTimestamp(ProtoTimeUtils.fromSecondsToTimestamp(50)) + .build(); Timestamp result = (Timestamp) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(Timestamps.fromSeconds(50)); + assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java index 4120cc360..bb4b839e1 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java @@ -36,8 +36,6 @@ import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import com.google.rpc.context.AttributeContext; import com.google.rpc.context.AttributeContext.Auth; import com.google.rpc.context.AttributeContext.Peer; @@ -50,6 +48,7 @@ import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; +import dev.cel.common.internal.ProtoTimeUtils; import java.util.Arrays; import java.util.List; import org.jspecify.annotations.Nullable; @@ -332,8 +331,8 @@ public static List data() { {null, ImmutableList.of(), Result.alwaysFalse()}, {ImmutableMap.of(), null, Result.alwaysFalse()}, {ByteString.copyFromUtf8(""), null, Result.alwaysFalse()}, - {null, Timestamps.EPOCH, Result.alwaysFalse()}, - {Durations.ZERO, null, Result.alwaysFalse()}, + {null, ProtoTimeUtils.TIMESTAMP_EPOCH, Result.alwaysFalse()}, + {ProtoTimeUtils.DURATION_ZERO, null, Result.alwaysFalse()}, {NullValue.NULL_VALUE, NullValue.NULL_VALUE, Result.alwaysTrue()}, { Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(), diff --git a/runtime/src/test/java/dev/cel/runtime/TypeResolverTest.java b/runtime/src/test/java/dev/cel/runtime/TypeResolverTest.java index d6e22f000..c5a11b680 100644 --- a/runtime/src/test/java/dev/cel/runtime/TypeResolverTest.java +++ b/runtime/src/test/java/dev/cel/runtime/TypeResolverTest.java @@ -20,10 +20,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.NullValue; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; import dev.cel.common.types.OptionalType; @@ -70,8 +69,8 @@ private enum WellKnownObjectTestCase { UNSIGNED_LONG(UnsignedLong.valueOf(1L), TypeType.create(SimpleType.UINT)), STRING("test", TypeType.create(SimpleType.STRING)), NULL(NullValue.NULL_VALUE, TypeType.create(SimpleType.NULL_TYPE)), - DURATION(Durations.fromSeconds(1), TypeType.create(SimpleType.DURATION)), - TIMESTAMP(Timestamps.fromSeconds(1), TypeType.create(SimpleType.TIMESTAMP)), + DURATION(ProtoTimeUtils.fromSecondsToDuration(1), TypeType.create(SimpleType.DURATION)), + TIMESTAMP(ProtoTimeUtils.fromSecondsToTimestamp(1), TypeType.create(SimpleType.TIMESTAMP)), ARRAY_LIST(new ArrayList<>(), TypeType.create(ListType.create(SimpleType.DYN))), IMMUTABLE_LIST(ImmutableList.of(), TypeType.create(ListType.create(SimpleType.DYN))), HASH_MAP(new HashMap<>(), TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))), diff --git a/testing/src/main/java/dev/cel/testing/BUILD.bazel b/testing/src/main/java/dev/cel/testing/BUILD.bazel index 97298e8a4..4245fc90a 100644 --- a/testing/src/main/java/dev/cel/testing/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/BUILD.bazel @@ -102,6 +102,7 @@ java_library( "//common:proto_ast", "//common/internal:cel_descriptor_pools", "//common/internal:file_descriptor_converter", + "//common/internal:proto_time_utils", "//common/resources/testdata/proto3:standalone_global_enum_java_proto", "//common/types:cel_proto_types", "//common/types:type_providers", diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 119428d65..edb574bc8 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -54,14 +54,13 @@ import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; -import com.google.protobuf.util.Durations; import com.google.protobuf.util.JsonFormat; -import com.google.protobuf.util.Timestamps; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; import dev.cel.common.CelProtoAbstractSyntaxTree; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.FileDescriptorSetConverter; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelProtoTypes; import dev.cel.common.types.CelTypeProvider; import dev.cel.expr.conformance.proto3.TestAllTypes; @@ -662,7 +661,7 @@ public void packUnpackAny() { declareVariable( "message", CelProtoTypes.createMessage(TestAllTypes.getDescriptor().getFullName())); declareVariable("list", CelProtoTypes.createList(CelProtoTypes.DYN)); - Duration duration = Durations.fromSeconds(100); + Duration duration = ProtoTimeUtils.fromSecondsToDuration(100); Any any = Any.pack(duration); TestAllTypes message = TestAllTypes.newBuilder().setSingleAny(any).build(); @@ -982,7 +981,7 @@ public void timestampFunctions() { declareVariable("ts1", CelProtoTypes.TIMESTAMP); container = Type.getDescriptor().getFile().getPackage(); Timestamp ts1 = Timestamp.newBuilder().setSeconds(1).setNanos(11000000).build(); - Timestamp ts2 = Timestamps.fromSeconds(-1); + Timestamp ts2 = ProtoTimeUtils.fromSecondsToTimestamp(-1); source = "ts1.getFullYear(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", ts1)); @@ -1034,7 +1033,7 @@ public void timestampFunctions() { source = "ts1.getDate(\"9:30\")"; runTest(ImmutableMap.of("ts1", ts1)); - Timestamp tsSunday = Timestamps.fromSeconds(3 * 24 * 3600); + Timestamp tsSunday = ProtoTimeUtils.fromSecondsToTimestamp(3 * 24 * 3600); source = "ts1.getDayOfWeek(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", tsSunday)); source = "ts1.getDayOfWeek()"; @@ -1358,7 +1357,7 @@ public void timeConversions() { runTest(); source = "int(t1) == 100"; - runTest(ImmutableMap.of("t1", Timestamps.fromSeconds(100))); + runTest(ImmutableMap.of("t1", ProtoTimeUtils.fromSecondsToTimestamp(100))); source = "duration(\"1h2m3.4s\")"; runTest(); @@ -1845,7 +1844,12 @@ public void jsonConversions() { declareVariable("ts", CelProtoTypes.TIMESTAMP); declareVariable("du", CelProtoTypes.DURATION); source = "google.protobuf.Struct { fields: {'timestamp': ts, 'duration': du } }"; - runTest(ImmutableMap.of("ts", Timestamps.fromSeconds(100), "du", Durations.fromMillis(200))); + runTest( + ImmutableMap.of( + "ts", + ProtoTimeUtils.fromSecondsToTimestamp(100), + "du", + ProtoTimeUtils.fromMillisToDuration(200))); } @Test @@ -2173,7 +2177,7 @@ public void dynamicMessage_dynamicDescriptor() throws Exception { // Test any unpacking // With well-known type - Any anyDuration = Any.pack(Durations.fromSeconds(100)); + Any anyDuration = Any.pack(ProtoTimeUtils.fromSecondsToDuration(100)); declareVariable("dur", CelProtoTypes.TIMESTAMP); source = "TestAllTypes { single_any: dur }.single_any"; assertThat(runTest(ImmutableMap.of("dur", anyDuration))).isInstanceOf(Duration.class); diff --git a/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel b/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel index 47db17360..9e88e0d16 100644 --- a/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel @@ -14,6 +14,7 @@ java_library( "//common:compiler_common", "//common:options", "//common:proto_ast", + "//common/internal:proto_time_utils", "//common/types", "//extensions:optional_library", "//runtime", @@ -28,7 +29,6 @@ java_library( "@cel_spec//proto/cel/expr:checked_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", "@maven_android//:com_google_protobuf_protobuf_javalite", diff --git a/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java b/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java index 654126130..e89e5ce35 100644 --- a/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java +++ b/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableMap; import com.google.protobuf.Duration; -import com.google.protobuf.util.Durations; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; @@ -29,6 +28,7 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelIssue.Severity; import dev.cel.common.CelValidationResult; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.SimpleType; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; @@ -108,7 +108,7 @@ public void duration_withFunction_noOp() throws Exception { String.class, stringArg -> { try { - return Durations.parse(stringArg).toString(); + return ProtoTimeUtils.parse(stringArg).toString(); } catch (ParseException e) { throw new RuntimeException(e); } diff --git a/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java b/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java index 2f733fd93..65b7d593a 100644 --- a/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java +++ b/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableMap; import com.google.protobuf.Timestamp; -import com.google.protobuf.util.Timestamps; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; @@ -30,6 +29,7 @@ import dev.cel.common.CelIssue.Severity; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationResult; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.SimpleType; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; @@ -118,7 +118,7 @@ public void timestamp_withFunction_noOp() throws Exception { String.class, stringArg -> { try { - return Timestamps.parse(stringArg).getSeconds(); + return ProtoTimeUtils.parse(stringArg).getSeconds(); } catch (ParseException e) { throw new RuntimeException(e); }