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 e373f442b..b76338cef 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -128,6 +128,7 @@ java_library( "@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//:org_jspecify_jspecify", ], ) diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java index 6e291c626..194e8260d 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -50,6 +50,8 @@ 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.CelErrorCode; import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; @@ -475,6 +477,15 @@ public Optional adaptValueToProto(Object value, String protoTypeName) { return json.setListValue(listValue).build(); } } + 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); + return json.setStringValue(ts).build(); + } + if (value instanceof Duration) { + String duration = Durations.toString((Duration) value); + return json.setStringValue(duration).build(); + } return null; } diff --git a/runtime/src/test/resources/jsonConversions.baseline b/runtime/src/test/resources/jsonConversions.baseline new file mode 100644 index 000000000..3c674b1c4 --- /dev/null +++ b/runtime/src/test/resources/jsonConversions.baseline @@ -0,0 +1,12 @@ +Source: google.protobuf.Struct { fields: {'timestamp': ts, 'duration': du } } +declare ts { + value google.protobuf.Timestamp +} +declare du { + value google.protobuf.Duration +} +=====> +bindings: {ts=seconds: 100 +, du=nanos: 200000000 +} +result: {timestamp=1970-01-01T00:01:40Z, duration=0.200s} diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 0addafaf9..17e6bc26c 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -1725,6 +1725,16 @@ public void jsonValueTypes() throws Exception { runTest(Activation.of("x", xStruct)); } + @Test + public void jsonConversions() throws Exception { + declareVariable("ts", CelTypes.TIMESTAMP); + declareVariable("du", CelTypes.DURATION); + source = "google.protobuf.Struct { fields: {'timestamp': ts, 'duration': du } }"; + runTest( + Activation.copyOf( + ImmutableMap.of("ts", Timestamps.fromSeconds(100), "du", Durations.fromMillis(200)))); + } + @Test public void typeComparisons() throws Exception { container = TestAllTypes.getDescriptor().getFile().getPackage();