diff --git a/.bazelrc b/.bazelrc index 4e4a0184c..750977061 100644 --- a/.bazelrc +++ b/.bazelrc @@ -5,3 +5,7 @@ build --java_language_version=11 # Hide Java 8 deprecation warnings. common --javacopt=-Xlint:-options + +# MacOS Fix https://github.com/protocolbuffers/protobuf/issues/16944 +build --host_cxxopt=-std=c++14 +build --cxxopt=-std=c++14 diff --git a/BUILD.bazel b/BUILD.bazel index 06942bc50..41a15f355 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -151,7 +151,7 @@ java_package_configuration( "-Xep:ProtoFieldPreconditionsCheckNotNull:ERROR", "-Xep:ProtocolBufferOrdinal:ERROR", "-Xep:ReferenceEquality:ERROR", - "-Xep:RemoveUnusedImports:ERROR", + # "-Xep:RemoveUnusedImports:ERROR", "-Xep:RequiredModifiers:ERROR", "-Xep:ShortCircuitBoolean:ERROR", "-Xep:SimpleDateFormatConstant:ERROR", diff --git a/WORKSPACE b/WORKSPACE index 15fdf8f20..1c1e03c59 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -242,11 +242,11 @@ http_archive( ) # cel-spec api/expr canonical protos -CEL_SPEC_VERSION = "0.20.0" +CEL_SPEC_VERSION = "0.22.1" http_archive( name = "cel_spec", - sha256 = "9f4acb83116f68af8a6b6acf700561a22a1bd8a9ad2f49bf642b7f9b8f285043", + sha256 = "1f1ad32bce5d31cf82e9c8f40685b1902de3ab07c78403601e7a43c3fb4de9a6", strip_prefix = "cel-spec-" + CEL_SPEC_VERSION, urls = [ "https://github.com/google/cel-spec/archive/" + diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel index 24ca0a484..ad34a3f16 100644 --- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel @@ -59,6 +59,7 @@ java_library( "@maven//:com_google_truth_extensions_truth_proto_extension", "@maven//:junit_junit", "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 0efe75cd5..a54f8e0ba 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -320,7 +320,6 @@ public void compile_customTypesWithAliasingCombinedProviders() throws Exception @Test public void compile_customTypesWithAliasingSelfContainedProvider() throws Exception { - // The custom type provider sets up an alias from "Condition" to "google.type.Expr". TypeProvider customTypeProvider = aliasingProvider( diff --git a/codelab/src/main/codelab/BUILD.bazel b/codelab/src/main/codelab/BUILD.bazel index 5f1e3ca56..af900769c 100644 --- a/codelab/src/main/codelab/BUILD.bazel +++ b/codelab/src/main/codelab/BUILD.bazel @@ -39,5 +39,6 @@ java_library( "@maven//:com_google_guava_guava", # unuseddeps: keep "@maven//:com_google_protobuf_protobuf_java", # unuseddeps: keep "@maven//:com_google_protobuf_protobuf_java_util", # unuseddeps: keep + "@maven_android//:com_google_protobuf_protobuf_javalite", # unuseddeps: keep ], ) diff --git a/codelab/src/main/codelab/solutions/BUILD.bazel b/codelab/src/main/codelab/solutions/BUILD.bazel index dd70c268f..5d3a37e30 100644 --- a/codelab/src/main/codelab/solutions/BUILD.bazel +++ b/codelab/src/main/codelab/solutions/BUILD.bazel @@ -40,5 +40,6 @@ java_library( "@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/internal/BUILD.bazel b/common/internal/BUILD.bazel index 63bff51d9..e1bb023c5 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -72,6 +72,11 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/internal:well_known_proto"], ) +cel_android_library( + name = "well_known_proto_android", + exports = ["//common/src/main/java/dev/cel/common/internal:well_known_proto_android"], +) + java_library( name = "proto_message_factory", exports = ["//common/src/main/java/dev/cel/common/internal:proto_message_factory"], @@ -87,6 +92,26 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/internal:cel_descriptor_pools"], ) +java_library( + name = "cel_lite_descriptor_pool", + exports = ["//common/src/main/java/dev/cel/common/internal:cel_lite_descriptor_pool"], +) + +cel_android_library( + name = "cel_lite_descriptor_pool_android", + exports = ["//common/src/main/java/dev/cel/common/internal:cel_lite_descriptor_pool_android"], +) + +java_library( + name = "default_lite_descriptor_pool", + exports = ["//common/src/main/java/dev/cel/common/internal:default_lite_descriptor_pool"], +) + +cel_android_library( + name = "default_lite_descriptor_pool_android", + exports = ["//common/src/main/java/dev/cel/common/internal:default_lite_descriptor_pool_android"], +) + java_library( name = "safe_string_formatter", # used_by_android @@ -95,6 +120,16 @@ java_library( cel_android_library( name = "internal_android", - visibility = ["//:android_allow_list"], exports = ["//common/src/main/java/dev/cel/common/internal:internal_android"], ) + +java_library( + name = "proto_java_qualified_names", + exports = ["//common/src/main/java/dev/cel/common/internal:proto_java_qualified_names"], +) + +java_library( + name = "reflection_util", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/internal:reflection_util"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 3c9006e72..2ce9c303f 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -208,6 +208,7 @@ java_library( "@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/ast/BUILD.bazel b/common/src/main/java/dev/cel/common/ast/BUILD.bazel index a99d0872c..bd6a8f0d8 100644 --- a/common/src/main/java/dev/cel/common/ast/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/ast/BUILD.bazel @@ -53,6 +53,7 @@ java_library( "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -114,7 +115,7 @@ java_library( ":ast", "//common/annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -138,7 +139,6 @@ cel_android_library( "//:auto_value", "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_jspecify_jspecify", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", 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 bca6ec303..0732009b2 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -47,6 +47,7 @@ java_library( "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_antlr_antlr4_runtime", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -61,7 +62,6 @@ cel_android_library( "//common/ast:ast_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_antlr_antlr4_runtime", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", @@ -140,6 +140,7 @@ java_library( ":proto_java_qualified_names", "//common/annotations", "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -152,7 +153,7 @@ java_library( ":reflection_util", "//common/annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -174,6 +175,7 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -191,6 +193,7 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -207,6 +210,7 @@ java_library( "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -249,6 +253,19 @@ java_library( ], ) +cel_android_library( + name = "well_known_proto_android", + srcs = ["WellKnownProto.java"], + tags = [ + ], + deps = [ + "//common/annotations", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + java_library( name = "default_message_factory", srcs = ["DefaultMessageFactory.java"], @@ -291,6 +308,62 @@ java_library( ], ) +java_library( + name = "cel_lite_descriptor_pool", + srcs = ["CelLiteDescriptorPool.java"], + tags = [ + ], + deps = [ + "//protobuf:cel_lite_descriptor", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +cel_android_library( + name = "cel_lite_descriptor_pool_android", + srcs = ["CelLiteDescriptorPool.java"], + tags = [ + ], + deps = [ + "//protobuf:cel_lite_descriptor", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +java_library( + name = "default_lite_descriptor_pool", + srcs = ["DefaultLiteDescriptorPool.java"], + tags = [ + ], + deps = [ + ":cel_lite_descriptor_pool", + "//common/annotations", + "//common/internal:well_known_proto", + "//protobuf:cel_lite_descriptor", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +cel_android_library( + name = "default_lite_descriptor_pool_android", + srcs = ["DefaultLiteDescriptorPool.java"], + tags = [ + ], + deps = [ + ":cel_lite_descriptor_pool_android", + "//common/annotations", + "//common/internal:well_known_proto_android", + "//protobuf:cel_lite_descriptor", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + java_library( name = "safe_string_formatter", srcs = ["SafeStringFormatter.java"], @@ -309,6 +382,7 @@ java_library( tags = [ ], deps = [ + "//common/annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", ], @@ -317,6 +391,7 @@ java_library( java_library( name = "reflection_util", srcs = ["ReflectionUtil.java"], + # used_by_android deps = [ "//common/annotations", ], diff --git a/common/src/main/java/dev/cel/common/internal/CelLiteDescriptorPool.java b/common/src/main/java/dev/cel/common/internal/CelLiteDescriptorPool.java new file mode 100644 index 000000000..8959b826f --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/CelLiteDescriptorPool.java @@ -0,0 +1,26 @@ +// 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 com.google.errorprone.annotations.Immutable; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.util.Optional; + +/** TODO: Replace with CelLiteDescriptor */ +@Immutable +public interface CelLiteDescriptorPool { + Optional findDescriptor(String protoTypeName); + MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName); +} diff --git a/common/src/main/java/dev/cel/common/internal/DefaultLiteDescriptorPool.java b/common/src/main/java/dev/cel/common/internal/DefaultLiteDescriptorPool.java new file mode 100644 index 000000000..c6da7f71e --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DefaultLiteDescriptorPool.java @@ -0,0 +1,314 @@ +// 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 com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.BoolValue; +import com.google.protobuf.BytesValue; +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; +import com.google.protobuf.ListValue; +import com.google.protobuf.MessageLite; +import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Timestamp; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; +import com.google.protobuf.Value; +import dev.cel.common.annotations.Internal; +import dev.cel.protobuf.CelLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.Type; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.CelFieldValueType; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.JavaType; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Supplier; + +/** Descriptor pool for {@link CelLiteDescriptor}s. */ +@Immutable +@Internal +public final class DefaultLiteDescriptorPool implements CelLiteDescriptorPool { + private final ImmutableMap protoFqnToMessageInfo; + + public static DefaultLiteDescriptorPool newInstance(ImmutableSet descriptors) { + return new DefaultLiteDescriptorPool(descriptors); + } + + @Override + public Optional findDescriptor(String protoTypeName) { + return Optional.ofNullable(protoFqnToMessageInfo.get(protoTypeName)); + } + + @Override + public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) { + return findDescriptor(protoTypeName).orElseThrow(() -> new NoSuchElementException("Could not find a descriptor for: " + protoTypeName)); + } + + private static MessageLiteDescriptor newMessageInfo(WellKnownProto wellKnownProto) { + ImmutableList.Builder fieldDescriptors = ImmutableList.builder(); + Supplier messageBuilder = null; + switch (wellKnownProto) { + case BOOL_VALUE: + messageBuilder = BoolValue::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.BOOLEAN, + Type.BOOL)); + break; + case BYTES_VALUE: + messageBuilder = BytesValue::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.BYTE_STRING, + Type.BYTES)); + break; + case DOUBLE_VALUE: + messageBuilder = DoubleValue::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.DOUBLE, + Type.DOUBLE)); + break; + case FLOAT_VALUE: + messageBuilder = FloatValue::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.FLOAT, + Type.FLOAT)); + break; + case INT32_VALUE: + messageBuilder = Int32Value::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.INT, + Type.INT32)); + break; + case INT64_VALUE: + messageBuilder = Int64Value::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.LONG, + Type.INT64)); + break; + case STRING_VALUE: + messageBuilder = StringValue::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.STRING, + Type.STRING)); + break; + case UINT32_VALUE: + messageBuilder = UInt32Value::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.INT, + Type.UINT32)); + break; + case UINT64_VALUE: + messageBuilder = UInt64Value::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "value", + JavaType.LONG, + Type.UINT64)); + break; + case JSON_STRUCT_VALUE: + messageBuilder = Struct::newBuilder; + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 1, + /* fieldName= */ "fields", + /* javaType= */ JavaType.MESSAGE, + /* celFieldValueType= */ CelFieldValueType.MAP, + /* protoFieldType= */ Type.MESSAGE, + /* hasHasser= */ false, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "google.protobuf.Struct.FieldsEntry")); + break; + case JSON_VALUE: + messageBuilder = Value::newBuilder; + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 1, + /* fieldName= */ "null_value", + /* javaType= */ JavaType.ENUM, + /* celFieldValueType= */ CelFieldValueType.SCALAR, + /* protoFieldType= */ Type.ENUM, + /* hasHasser= */ true, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "google.protobuf.NullValue") + ); + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 2, + /* fieldName= */ "number_value", + /* javaType= */ JavaType.DOUBLE, + /* celFieldValueType= */ CelFieldValueType.SCALAR, + /* protoFieldType= */ Type.DOUBLE, + /* hasHasser= */ true, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "")); + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 3, + /* fieldName= */ "string_value", + /* javaType= */ JavaType.STRING, + /* celFieldValueType= */ CelFieldValueType.SCALAR, + /* protoFieldType= */ Type.STRING, + /* hasHasser= */ true, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "")); + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 4, + /* fieldName= */ "bool_value", + /* javaType= */ JavaType.BOOLEAN, + /* celFieldValueType= */ CelFieldValueType.SCALAR, + /* protoFieldType= */ Type.BOOL, + /* hasHasser= */ true, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "")); + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 5, + /* fieldName= */ "struct_value", + /* javaType= */ JavaType.MESSAGE, + /* celFieldValueType= */ CelFieldValueType.SCALAR, + /* protoFieldType= */ Type.MESSAGE, + /* hasHasser= */ true, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "google.protobuf.Struct")); + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 6, + /* fieldName= */ "list_value", + /* javaType= */ JavaType.MESSAGE, + /* celFieldValueType= */ CelFieldValueType.SCALAR, + /* protoFieldType= */ Type.MESSAGE, + /* hasHasser= */ true, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "google.protobuf.ListValue")); + break; + case JSON_LIST_VALUE: + messageBuilder = ListValue::newBuilder; + fieldDescriptors.add( + new FieldLiteDescriptor( + /* fieldNumber= */ 1, + /* fieldName= */ "values", + /* javaTypeName= */ JavaType.MESSAGE, + /* celFieldValueType= */ CelFieldValueType.LIST, + /* protoFieldType= */ Type.MESSAGE, + /* hasHasser= */ false, + /* isPacked= */ false, + /* fieldProtoTypeName= */ "google.protobuf.Value") + ); + break; + case DURATION: + messageBuilder = Duration::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "seconds", + JavaType.LONG, + Type.INT64)); + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 2, + "nanos", + JavaType.INT, + Type.INT32)); + break; + case TIMESTAMP: + messageBuilder = Timestamp::newBuilder; + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 1, + "nanos", + JavaType.INT, + Type.INT32)); + fieldDescriptors.add( + newPrimitiveFieldDescriptor( + 2, + "seconds", + JavaType.LONG, + Type.INT64)); + break; + default: + break; + } + + return new MessageLiteDescriptor( + wellKnownProto.typeName(), + fieldDescriptors.build(), + messageBuilder + ); + } + + private static FieldLiteDescriptor newPrimitiveFieldDescriptor( + int fieldNumber, + String fieldName, + JavaType javaType, + Type protoFieldType) { + return new FieldLiteDescriptor( + /* fieldNumber= */ fieldNumber, + /* fieldName= */ fieldName, + /* javaType= */ javaType, + /* celFieldValueType= */ CelFieldValueType.SCALAR, + /* protoFieldType= */ protoFieldType, + /* hasHasser= */ false, + /* isPacked= */ false, + /* fieldProtoTypeName= */ ""); + } + + private DefaultLiteDescriptorPool(ImmutableSet descriptors) { + ImmutableMap.Builder protoFqnMapBuilder = ImmutableMap.builder(); + for (WellKnownProto wellKnownProto : WellKnownProto.values()) { + MessageLiteDescriptor wktMessageInfo = newMessageInfo(wellKnownProto); + protoFqnMapBuilder.put(wellKnownProto.typeName(), wktMessageInfo); + } + + for (CelLiteDescriptor descriptor : descriptors) { + protoFqnMapBuilder.putAll(descriptor.getProtoTypeNamesToDescriptors()); + } + + this.protoFqnToMessageInfo = protoFqnMapBuilder.buildOrThrow(); + } +} 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 3eed49257..c1a096930 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -139,7 +139,7 @@ public Object adaptProtoToValue(MessageOrBuilder proto) { // If the proto is not a well-known type, then the input Message is what's expected as the // output return value. WellKnownProto wellKnownProto = - WellKnownProto.getByTypeName(typeName(proto.getDescriptorForType())); + WellKnownProto.getByTypeName(typeName(proto.getDescriptorForType())).orElse(null); if (wellKnownProto == null) { return proto; } @@ -280,7 +280,7 @@ private BidiConverter fieldToValueConverter(FieldDescriptor fieldDescriptor) { * considered, such as a packing an {@code google.protobuf.StringValue} into a {@code Any} value. */ public Message adaptValueToProto(Object value, String protoTypeName) { - WellKnownProto wellKnownProto = WellKnownProto.getByTypeName(protoTypeName); + WellKnownProto wellKnownProto = WellKnownProto.getByTypeName(protoTypeName).orElse(null); if (wellKnownProto == null) { if (value instanceof Message) { return (Message) value; @@ -326,8 +326,7 @@ private static boolean isWrapperType(FieldDescriptor fieldDescriptor) { return false; } String fieldTypeName = fieldDescriptor.getMessageType().getFullName(); - WellKnownProto wellKnownProto = WellKnownProto.getByTypeName(fieldTypeName); - return wellKnownProto != null && wellKnownProto.isWrapperType(); + return WellKnownProto.isWrapperType(fieldTypeName); } private static int intCheckedCast(long value) { diff --git a/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java b/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java index 9c2ba049e..656c702fd 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java @@ -24,10 +24,16 @@ import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.GenericDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; +import dev.cel.common.annotations.Internal; import java.util.ArrayDeque; -/** Helper class for constructing a fully qualified Java class name from a protobuf descriptor. */ -final class ProtoJavaQualifiedNames { +/** + * Helper class for constructing a fully qualified Java class name from a protobuf descriptor. * * + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +public final class ProtoJavaQualifiedNames { // Controls how many times we should recursively inspect a nested message for building fully // qualified java class name before aborting. private static final int SAFE_RECURSE_LIMIT = 50; diff --git a/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java b/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java index e513a446b..8aa4c7f14 100644 --- a/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java +++ b/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java @@ -26,6 +26,14 @@ @Internal public final class ReflectionUtil { + public static Method getMethod(String className, String methodName, Class... params) { + try { + return getMethod(Class.forName(className), methodName, params); + } catch (ClassNotFoundException e) { + throw new LinkageError(String.format("Could not find class %s", className), e); + } + } + public static Method getMethod(Class clazz, String methodName, Class... params) { try { return clazz.getMethod(methodName, params); diff --git a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java index 476891181..385ec0586 100644 --- a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java +++ b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java @@ -36,8 +36,8 @@ import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; import dev.cel.common.annotations.Internal; +import java.util.Optional; import java.util.function.Function; -import org.jspecify.annotations.Nullable; /** * WellKnownProto types used throughout CEL. These types are specially handled to ensure that @@ -46,77 +46,83 @@ */ @Internal public enum WellKnownProto { - ANY_VALUE("google.protobuf.Any", Any.class.getName()), - DURATION("google.protobuf.Duration", Duration.class.getName()), - JSON_LIST_VALUE("google.protobuf.ListValue", ListValue.class.getName()), - JSON_STRUCT_VALUE("google.protobuf.Struct", Struct.class.getName()), - JSON_VALUE("google.protobuf.Value", Value.class.getName()), - TIMESTAMP("google.protobuf.Timestamp", Timestamp.class.getName()), + ANY_VALUE("google.protobuf.Any", Any.class), + DURATION("google.protobuf.Duration", Duration.class), + JSON_LIST_VALUE("google.protobuf.ListValue", ListValue.class), + JSON_STRUCT_VALUE("google.protobuf.Struct", Struct.class), + JSON_VALUE("google.protobuf.Value", Value.class), + TIMESTAMP("google.protobuf.Timestamp", Timestamp.class), // Wrapper types - FLOAT_VALUE("google.protobuf.FloatValue", FloatValue.class.getName(), /* isWrapperType= */ true), - INT32_VALUE("google.protobuf.Int32Value", Int32Value.class.getName(), /* isWrapperType= */ true), - INT64_VALUE("google.protobuf.Int64Value", Int64Value.class.getName(), /* isWrapperType= */ true), + FLOAT_VALUE("google.protobuf.FloatValue", FloatValue.class, /* isWrapperType= */ true), + INT32_VALUE("google.protobuf.Int32Value", Int32Value.class, /* isWrapperType= */ true), + INT64_VALUE("google.protobuf.Int64Value", Int64Value.class, /* isWrapperType= */ true), STRING_VALUE( - "google.protobuf.StringValue", StringValue.class.getName(), /* isWrapperType= */ true), - BOOL_VALUE("google.protobuf.BoolValue", BoolValue.class.getName(), /* isWrapperType= */ true), - BYTES_VALUE("google.protobuf.BytesValue", BytesValue.class.getName(), /* isWrapperType= */ true), + "google.protobuf.StringValue", StringValue.class, /* isWrapperType= */ true), + BOOL_VALUE("google.protobuf.BoolValue", BoolValue.class, /* isWrapperType= */ true), + BYTES_VALUE("google.protobuf.BytesValue", BytesValue.class, /* isWrapperType= */ true), DOUBLE_VALUE( - "google.protobuf.DoubleValue", DoubleValue.class.getName(), /* isWrapperType= */ true), + "google.protobuf.DoubleValue", DoubleValue.class, /* isWrapperType= */ true), UINT32_VALUE( - "google.protobuf.UInt32Value", UInt32Value.class.getName(), /* isWrapperType= */ true), + "google.protobuf.UInt32Value", UInt32Value.class, /* isWrapperType= */ true), UINT64_VALUE( - "google.protobuf.UInt64Value", UInt64Value.class.getName(), /* isWrapperType= */ true), + "google.protobuf.UInt64Value", UInt64Value.class, /* isWrapperType= */ true), // These aren't explicitly called out as wrapper types in the spec, but behave like one, because // they are still converted into an equivalent primitive type. - EMPTY("google.protobuf.Empty", Empty.class.getName(), /* isWrapperType= */ true), - FIELD_MASK("google.protobuf.FieldMask", FieldMask.class.getName(), /* isWrapperType= */ true), + EMPTY("google.protobuf.Empty", Empty.class, /* isWrapperType= */ true), + FIELD_MASK("google.protobuf.FieldMask", FieldMask.class, /* isWrapperType= */ true), ; - private static final ImmutableMap WELL_KNOWN_PROTO_MAP; + private static final ImmutableMap TYPE_NAME_TO_WELL_KNOWN_PROTO_MAP = + stream(WellKnownProto.values()) + .collect(toImmutableMap(WellKnownProto::typeName, Function.identity())); - static { - WELL_KNOWN_PROTO_MAP = - stream(WellKnownProto.values()) - .collect(toImmutableMap(WellKnownProto::typeName, Function.identity())); - } + private static final ImmutableMap, WellKnownProto> CLASS_TO_NAME_TO_WELL_KNOWN_PROTO_MAP = + stream(WellKnownProto.values()) + .collect(toImmutableMap(WellKnownProto::messageClass, Function.identity())); - private final String wellKnownProtoFullName; - private final String javaClassName; + private final String wellKnownProtoTypeName; + private final Class clazz; private final boolean isWrapperType; public String typeName() { - return wellKnownProtoFullName; + return wellKnownProtoTypeName; + } + + public Class messageClass() { + return clazz; } - public String javaClassName() { - return this.javaClassName; + + + public static Optional getByTypeName(String typeName) { + return Optional.ofNullable(TYPE_NAME_TO_WELL_KNOWN_PROTO_MAP.get(typeName)); } - public static @Nullable WellKnownProto getByTypeName(String typeName) { - return WELL_KNOWN_PROTO_MAP.get(typeName); + public static Optional getByClass(Class clazz) { + return Optional.ofNullable(CLASS_TO_NAME_TO_WELL_KNOWN_PROTO_MAP.get(clazz)); } + /** + * Returns true if the provided {@code typeName} is a well known type, and it's a wrapper. False otherwise. + */ public static boolean isWrapperType(String typeName) { - WellKnownProto wellKnownProto = getByTypeName(typeName); - if (wellKnownProto == null) { - return false; - } - - return wellKnownProto.isWrapperType(); + return getByTypeName(typeName) + .map(WellKnownProto::isWrapperType) + .orElse(false); } public boolean isWrapperType() { return isWrapperType; } - WellKnownProto(String wellKnownProtoFullName, String javaClassName) { - this(wellKnownProtoFullName, javaClassName, /* isWrapperType= */ false); + WellKnownProto(String wellKnownProtoTypeName, Class clazz) { + this(wellKnownProtoTypeName, clazz, /* isWrapperType= */ false); } - WellKnownProto(String wellKnownProtoFullName, String javaClassName, boolean isWrapperType) { - this.wellKnownProtoFullName = wellKnownProtoFullName; - this.javaClassName = javaClassName; + WellKnownProto(String wellKnownProtoFullName, Class clazz, boolean isWrapperType) { + this.wellKnownProtoTypeName = wellKnownProtoFullName; + this.clazz = clazz; this.isWrapperType = isWrapperType; } } 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 c9e233108..1f248188a 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") +load("//:cel_android_rules.bzl", "cel_android_library") package( default_applicable_licenses = [ @@ -52,11 +53,21 @@ java_library( ], ) +cel_android_library( + name = "cel_value_android", + srcs = ["CelValue.java"], + tags = [ + ], + deps = [ + "//common/annotations", + "//common/types:type_providers_android", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "cel_value_provider", - srcs = [ - "CelValueProvider.java", - ], + srcs = ["CelValueProvider.java"], tags = [ ], deps = [ @@ -66,6 +77,18 @@ java_library( ], ) +cel_android_library( + name = "cel_value_provider_android", + srcs = ["CelValueProvider.java"], + tags = [ + ], + deps = [ + ":cel_value_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "values", srcs = CEL_VALUES_SOURCES, @@ -87,14 +110,74 @@ java_library( ], ) +cel_android_library( + name = "values_android", + srcs = CEL_VALUES_SOURCES, + tags = [ + ], + deps = [ + ":cel_byte_string", + "//:auto_value", + "//common:error_codes", + "//common:options", + "//common:runtime_exception", + "//common/annotations", + "//common/types:type_providers_android", + "//common/types:types_android", + "//common/values:cel_value_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "cel_byte_string", srcs = ["CelByteString.java"], + # used_by_android tags = [ ], deps = [ "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "base_proto_cel_value_converter", + srcs = ["BaseProtoCelValueConverter.java"], + tags = [ + ], + deps = [ + ":cel_byte_string", + ":cel_value", + ":values", + "//common:options", + "//common/annotations", + "//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", + ], +) + +cel_android_library( + name = "base_proto_cel_value_converter_android", + srcs = ["BaseProtoCelValueConverter.java"], + tags = [ + ], + deps = [ + ":values_android", + "//common:options", + "//common/annotations", + "//common/internal:well_known_proto_android", + "//common/values:cel_byte_string", + "//common/values:cel_value_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", ], ) @@ -104,6 +187,7 @@ java_library( tags = [ ], deps = [ + ":base_proto_cel_value_converter", ":cel_value", ":values", "//:auto_value", @@ -115,12 +199,11 @@ java_library( "//common/types", "//common/types:cel_types", "//common/types:type_providers", - "//common/values:cel_byte_string", "@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", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -143,3 +226,83 @@ java_library( "@maven//:com_google_protobuf_protobuf_java", ], ) + +java_library( + name = "proto_message_lite_value", + srcs = [ + "ProtoLiteCelValueConverter.java", + "ProtoMessageLiteValue.java", + ], + tags = [ + ], + deps = [ + ":base_proto_cel_value_converter", + ":cel_value", + ":values", + "//:auto_value", + "//common:options", + "//common/annotations", + "//common/internal:cel_lite_descriptor_pool", + "//common/internal:reflection_util", + "//common/internal:well_known_proto", + "//common/types", + "//protobuf:cel_lite_descriptor", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +cel_android_library( + name = "proto_message_lite_value_android", + srcs = [ + "ProtoLiteCelValueConverter.java", + "ProtoMessageLiteValue.java", + ], + tags = [ + ], + deps = [ + "//:auto_value", + "//common:options", + "//common/annotations", + "//common/internal:cel_lite_descriptor_pool", + "//common/internal:reflection_util", + "//common/internal:well_known_proto_android", + "//common/types:types_android", + "//common/values:base_proto_cel_value_converter_android", + "//common/values:cel_value_android", + "//common/values:values_android", + "//protobuf:cel_lite_descriptor", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +java_library( + name = "proto_message_lite_value_provider", + srcs = ["ProtoMessageLiteValueProvider.java"], + tags = [ + ], + deps = [ + ":cel_value", + ":cel_value_provider", + ":proto_message_lite_value", + "//common:error_codes", + "//common:runtime_exception", + "//common/internal:cel_lite_descriptor_pool", + "//common/internal:default_instance_message_lite_factory", + "//common/internal:default_lite_descriptor_pool", + "//common/internal:proto_lite_adapter", + "//common/internal:reflection_util", + "//common/internal:well_known_proto", + "//protobuf:cel_lite_descriptor", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@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 new file mode 100644 index 000000000..1305b6991 --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -0,0 +1,235 @@ +// 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.values; + +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.FloatValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; +import com.google.protobuf.MessageLite; +import com.google.protobuf.MessageLiteOrBuilder; +import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Timestamp; +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.WellKnownProto; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; + +/** + * {@code BaseProtoCelValueConverter} contains the common logic for converting between native Java + * and protobuf objects to {@link CelValue}. This base class is inherited by {@code + * ProtoCelValueConverter} and {@code ProtoLiteCelValueConverter} to perform the conversion using + * full and lite variants of protobuf messages respectively. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +public abstract class BaseProtoCelValueConverter extends CelValueConverter { + + public abstract CelValue fromProtoMessageToCelValue(String protoTypeName, MessageLite msg); + + /** + * Adapts a {@link CelValue} to a native Java object. The CelValue is adapted into protobuf object + * when an equivalent exists. + */ + @Override + public Object fromCelValueToJavaObject(CelValue celValue) { + Preconditions.checkNotNull(celValue); + + if (celValue instanceof TimestampValue) { + return TimeUtils.toProtoTimestamp(((TimestampValue) celValue).value()); + } else if (celValue instanceof DurationValue) { + return TimeUtils.toProtoDuration(((DurationValue) celValue).value()); + } else if (celValue instanceof BytesValue) { + return ByteString.copyFrom(((BytesValue) celValue).value().toByteArray()); + } else if (celValue.equals(NullValue.NULL_VALUE)) { + return com.google.protobuf.NullValue.NULL_VALUE; + } + + return super.fromCelValueToJavaObject(celValue); + } + + /** + * Adapts a plain old Java Object to a {@link CelValue}. Protobuf semantics take precedence for + * conversion. + */ + @Override + public CelValue fromJavaObjectToCelValue(Object value) { + Preconditions.checkNotNull(value); + + Optional wellKnownProto = WellKnownProto.getByClass(value.getClass()); + if (wellKnownProto.isPresent()) { + return fromWellKnownProtoToCelValue((MessageLiteOrBuilder) value, wellKnownProto.get()); + } + + if (value instanceof ByteString) { + return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray())); + } else if (value instanceof com.google.protobuf.NullValue) { + return NullValue.NULL_VALUE; + } + + return super.fromJavaObjectToCelValue(value); + } + + protected final CelValue fromWellKnownProtoToCelValue( + MessageLiteOrBuilder message, WellKnownProto wellKnownProto) { + switch (wellKnownProto) { + case JSON_VALUE: + return adaptJsonValueToCelValue((Value) message); + case JSON_STRUCT_VALUE: + return adaptJsonStructToCelValue((Struct) message); + case JSON_LIST_VALUE: + return adaptJsonListToCelValue((com.google.protobuf.ListValue) message); + case DURATION: + return DurationValue.create( + TimeUtils.toJavaDuration((com.google.protobuf.Duration) message)); + case TIMESTAMP: + return TimestampValue.create(TimeUtils.toJavaInstant((Timestamp) message)); + case BOOL_VALUE: + return fromJavaPrimitiveToCelValue(((BoolValue) message).getValue()); + case BYTES_VALUE: + return fromJavaPrimitiveToCelValue( + ((com.google.protobuf.BytesValue) message).getValue().toByteArray()); + case DOUBLE_VALUE: + return fromJavaPrimitiveToCelValue(((DoubleValue) message).getValue()); + case FLOAT_VALUE: + return fromJavaPrimitiveToCelValue(((FloatValue) message).getValue()); + case INT32_VALUE: + return fromJavaPrimitiveToCelValue(((Int32Value) message).getValue()); + case INT64_VALUE: + return fromJavaPrimitiveToCelValue(((Int64Value) message).getValue()); + case STRING_VALUE: + return fromJavaPrimitiveToCelValue(((StringValue) message).getValue()); + case UINT32_VALUE: + return UintValue.create( + ((UInt32Value) message).getValue(), true); + case UINT64_VALUE: + return UintValue.create( + ((UInt64Value) message).getValue(), true); + default: + throw new UnsupportedOperationException( + "Unsupported message to CelValue conversion - " + message); + } + } + + private CelValue adaptJsonValueToCelValue(Value value) { + switch (value.getKindCase()) { + case BOOL_VALUE: + return fromJavaPrimitiveToCelValue(value.getBoolValue()); + case NUMBER_VALUE: + return fromJavaPrimitiveToCelValue(value.getNumberValue()); + case STRING_VALUE: + return fromJavaPrimitiveToCelValue(value.getStringValue()); + case LIST_VALUE: + return adaptJsonListToCelValue(value.getListValue()); + case STRUCT_VALUE: + return adaptJsonStructToCelValue(value.getStructValue()); + case NULL_VALUE: + case KIND_NOT_SET: // Fall-through is intended + return NullValue.NULL_VALUE; + } + throw new UnsupportedOperationException( + "Unsupported Json to CelValue conversion: " + value.getKindCase()); + } + + private ListValue adaptJsonListToCelValue(com.google.protobuf.ListValue listValue) { + return ImmutableListValue.create( + listValue.getValuesList().stream() + .map(this::adaptJsonValueToCelValue) + .collect(toImmutableList())); + } + + private MapValue adaptJsonStructToCelValue(Struct struct) { + return ImmutableMapValue.create( + struct.getFieldsMap().entrySet().stream() + .collect( + toImmutableMap( + e -> fromJavaObjectToCelValue(e.getKey()), + 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/main/java/dev/cel/common/values/CelByteString.java b/common/src/main/java/dev/cel/common/values/CelByteString.java index 196af790b..d8a50949e 100644 --- a/common/src/main/java/dev/cel/common/values/CelByteString.java +++ b/common/src/main/java/dev/cel/common/values/CelByteString.java @@ -14,7 +14,6 @@ package dev.cel.common.values; -import com.google.common.base.Preconditions; import com.google.errorprone.annotations.Immutable; import java.util.Arrays; @@ -30,7 +29,9 @@ public final class CelByteString { private volatile int hash = 0; public static CelByteString of(byte[] buffer) { - Preconditions.checkNotNull(buffer); + if (buffer == null) { + throw new NullPointerException("buffer cannot be null"); + } if (buffer.length == 0) { return EMPTY; } diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index 83275e3c1..5686778ee 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; -import dev.cel.common.CelOptions; import dev.cel.common.annotations.Internal; import java.util.Map; import java.util.Map.Entry; @@ -34,8 +33,6 @@ @Internal abstract class CelValueConverter { - protected final CelOptions celOptions; - /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object fromCelValueToJavaObject(CelValue celValue) { Preconditions.checkNotNull(celValue); @@ -112,7 +109,7 @@ protected CelValue fromJavaPrimitiveToCelValue(Object value) { } else if (value instanceof Float) { return DoubleValue.create(Double.valueOf((Float) value)); } else if (value instanceof UnsignedLong) { - return UintValue.create(((UnsignedLong) value).longValue(), celOptions.enableUnsignedLongs()); + return UintValue.create(((UnsignedLong) value).longValue(), true); } // Fall back to an Opaque value, as a custom class was supplied in the runtime. The legacy @@ -145,8 +142,5 @@ private MapValue toMapValue(Map map) { return ImmutableMapValue.create(mapBuilder.buildOrThrow()); } - protected CelValueConverter(CelOptions celOptions) { - Preconditions.checkNotNull(celOptions); - this.celOptions = celOptions; - } + protected CelValueConverter() {} } diff --git a/common/src/main/java/dev/cel/common/values/CelValueProvider.java b/common/src/main/java/dev/cel/common/values/CelValueProvider.java index 0e896e7ac..995064e51 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CelValueProvider.java @@ -41,10 +41,9 @@ public interface CelValueProvider { final class CombinedCelValueProvider implements CelValueProvider { private final ImmutableList celValueProviders; - public CombinedCelValueProvider(CelValueProvider first, CelValueProvider second) { - Preconditions.checkNotNull(first); - Preconditions.checkNotNull(second); - celValueProviders = ImmutableList.of(first, second); + public static CombinedCelValueProvider newInstance( + CelValueProvider first, CelValueProvider second) { + return new CombinedCelValueProvider(first, second); } @Override @@ -58,5 +57,11 @@ public Optional newValue(String structType, Map fields return Optional.empty(); } + + private CombinedCelValueProvider(CelValueProvider first, CelValueProvider second) { + Preconditions.checkNotNull(first); + Preconditions.checkNotNull(second); + celValueProviders = ImmutableList.of(first, second); + } } } diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 16d1a8956..0cbb9973c 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -14,50 +14,30 @@ package dev.cel.common.values; -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.Any; -import com.google.protobuf.BoolValue; -import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.DoubleValue; import com.google.protobuf.DynamicMessage; -import com.google.protobuf.FloatValue; -import com.google.protobuf.Int32Value; -import com.google.protobuf.Int64Value; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MapEntry; import com.google.protobuf.Message; +import com.google.protobuf.MessageLite; import com.google.protobuf.MessageOrBuilder; -import com.google.protobuf.StringValue; -import com.google.protobuf.Struct; -import com.google.protobuf.Timestamp; -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.CelOptions; import dev.cel.common.annotations.Internal; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DynamicProto; import dev.cel.common.internal.WellKnownProto; import dev.cel.common.types.CelTypes; -import java.time.Duration; -import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; /** - * {@code CelValueConverter} handles bidirectional conversion between native Java and protobuf - * objects to {@link CelValue}. + * {@code ProtoCelValueConverter} handles bidirectional conversion between native Java and protobuf + * objects to {@link CelValue}. This converter leverages descriptors, thus requires the full version + * of protobuf implementation. * *

Protobuf semantics take precedence for conversion. For example, CEL's TimestampValue will be * converted into Protobuf's Timestamp instead of java.time.Instant. @@ -66,35 +46,14 @@ */ @Immutable @Internal -public final class ProtoCelValueConverter extends CelValueConverter { +public final class ProtoCelValueConverter extends BaseProtoCelValueConverter { private final CelDescriptorPool celDescriptorPool; private final DynamicProto dynamicProto; /** Constructs a new instance of ProtoCelValueConverter. */ public static ProtoCelValueConverter newInstance( - CelOptions celOptions, CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) { - return new ProtoCelValueConverter(celOptions, celDescriptorPool, dynamicProto); - } - - /** - * Adapts a {@link CelValue} to a native Java object. The CelValue is adapted into protobuf object - * when an equivalent exists. - */ - @Override - public Object fromCelValueToJavaObject(CelValue celValue) { - Preconditions.checkNotNull(celValue); - - if (celValue instanceof TimestampValue) { - return TimeUtils.toProtoTimestamp(((TimestampValue) celValue).value()); - } else if (celValue instanceof DurationValue) { - return TimeUtils.toProtoDuration(((DurationValue) celValue).value()); - } else if (celValue instanceof BytesValue) { - return ByteString.copyFrom(((BytesValue) celValue).value().toByteArray()); - } else if (NullValue.NULL_VALUE.equals(celValue)) { - return com.google.protobuf.NullValue.NULL_VALUE; - } - - return super.fromCelValueToJavaObject(celValue); + CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) { + return new ProtoCelValueConverter(celDescriptorPool, dynamicProto); } /** Adapts a Protobuf message into a {@link CelValue}. */ @@ -107,7 +66,7 @@ public CelValue fromProtoMessageToCelValue(MessageOrBuilder message) { } WellKnownProto wellKnownProto = - WellKnownProto.getByTypeName(message.getDescriptorForType().getFullName()); + WellKnownProto.getByTypeName(message.getDescriptorForType().getFullName()).orElse(null); if (wellKnownProto == null) { return ProtoMessageValue.create((Message) message, celDescriptorPool, this); } @@ -122,44 +81,15 @@ public CelValue fromProtoMessageToCelValue(MessageOrBuilder message) { "Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e); } return fromProtoMessageToCelValue(unpackedMessage); - case JSON_VALUE: - return adaptJsonValueToCelValue((Value) message); - case JSON_STRUCT_VALUE: - return adaptJsonStructToCelValue((Struct) message); - case JSON_LIST_VALUE: - return adaptJsonListToCelValue((com.google.protobuf.ListValue) message); - case DURATION: - return DurationValue.create( - TimeUtils.toJavaDuration((com.google.protobuf.Duration) message)); - case TIMESTAMP: - return TimestampValue.create(TimeUtils.toJavaInstant((Timestamp) message)); - case BOOL_VALUE: - return fromJavaPrimitiveToCelValue(((BoolValue) message).getValue()); - case BYTES_VALUE: - return fromJavaPrimitiveToCelValue( - ((com.google.protobuf.BytesValue) message).getValue().toByteArray()); - case DOUBLE_VALUE: - return fromJavaPrimitiveToCelValue(((DoubleValue) message).getValue()); - case FLOAT_VALUE: - return fromJavaPrimitiveToCelValue(((FloatValue) message).getValue()); - case INT32_VALUE: - return fromJavaPrimitiveToCelValue(((Int32Value) message).getValue()); - case INT64_VALUE: - return fromJavaPrimitiveToCelValue(((Int64Value) message).getValue()); - case STRING_VALUE: - return fromJavaPrimitiveToCelValue(((StringValue) message).getValue()); - case UINT32_VALUE: - return UintValue.create( - ((UInt32Value) message).getValue(), celOptions.enableUnsignedLongs()); - case UINT64_VALUE: - return UintValue.create( - ((UInt64Value) message).getValue(), celOptions.enableUnsignedLongs()); default: - throw new UnsupportedOperationException( - "Unsupported message to CelValue conversion - " + message); + return super.fromWellKnownProtoToCelValue(message, wellKnownProto); } } + @Override + public CelValue fromProtoMessageToCelValue(String unusedProtoTypeName, MessageLite msg) { + return fromProtoMessageToCelValue((MessageOrBuilder) msg); + } /** * Adapts a plain old Java Object to a {@link CelValue}. Protobuf semantics take precedence for * conversion. @@ -173,10 +103,6 @@ public CelValue fromJavaObjectToCelValue(Object value) { } else if (value instanceof Message.Builder) { Message.Builder msgBuilder = (Message.Builder) value; return fromProtoMessageToCelValue(msgBuilder.build()); - } else if (value instanceof ByteString) { - return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray())); - } else if (value instanceof com.google.protobuf.NullValue) { - return NullValue.NULL_VALUE; } else if (value instanceof EnumValueDescriptor) { // (b/178627883) Strongly typed enum is not supported yet return IntValue.create(((EnumValueDescriptor) value).getNumber()); @@ -227,9 +153,9 @@ public CelValue fromProtoMessageFieldToCelValue( } break; case UINT32: - return UintValue.create((int) result, celOptions.enableUnsignedLongs()); + return UintValue.create((int) result, true); case UINT64: - return UintValue.create((long) result, celOptions.enableUnsignedLongs()); + return UintValue.create((long) result, true); default: break; } @@ -237,99 +163,8 @@ public CelValue fromProtoMessageFieldToCelValue( return fromJavaObjectToCelValue(result); } - private CelValue adaptJsonValueToCelValue(Value value) { - switch (value.getKindCase()) { - case BOOL_VALUE: - return fromJavaPrimitiveToCelValue(value.getBoolValue()); - case NUMBER_VALUE: - return fromJavaPrimitiveToCelValue(value.getNumberValue()); - case STRING_VALUE: - return fromJavaPrimitiveToCelValue(value.getStringValue()); - case LIST_VALUE: - return adaptJsonListToCelValue(value.getListValue()); - case STRUCT_VALUE: - return adaptJsonStructToCelValue(value.getStructValue()); - case NULL_VALUE: - case KIND_NOT_SET: // Fall-through is intended - return NullValue.NULL_VALUE; - } - throw new UnsupportedOperationException( - "Unsupported Json to CelValue conversion: " + value.getKindCase()); - } - - private ListValue adaptJsonListToCelValue(com.google.protobuf.ListValue listValue) { - return ImmutableListValue.create( - listValue.getValuesList().stream() - .map(this::adaptJsonValueToCelValue) - .collect(toImmutableList())); - } - - private MapValue adaptJsonStructToCelValue(Struct struct) { - return ImmutableMapValue.create( - struct.getFieldsMap().entrySet().stream() - .collect( - toImmutableMap( - e -> fromJavaObjectToCelValue(e.getKey()), - 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); - } - } - private ProtoCelValueConverter( - CelOptions celOptions, CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) { - super(celOptions); + CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) { Preconditions.checkNotNull(celDescriptorPool); Preconditions.checkNotNull(dynamicProto); this.celDescriptorPool = celDescriptorPool; diff --git a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java new file mode 100644 index 000000000..7eb1c3d2b --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java @@ -0,0 +1,320 @@ +// 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.values; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Defaults; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.UnsignedLong; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.MessageLite; +import com.google.protobuf.NullValue; +import com.google.protobuf.WireFormat; +import dev.cel.common.annotations.Internal; +import dev.cel.common.internal.CelLiteDescriptorPool; +import dev.cel.common.internal.WellKnownProto; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.CelFieldValueType; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * {@code ProtoLiteCelValueConverter} handles bidirectional conversion between native Java and + * protobuf objects to {@link CelValue}. This converter is specifically designed for use with + * lite-variants of protobuf messages. + * + *

Protobuf semantics take precedence for conversion. For example, CEL's TimestampValue will be + * converted into Protobuf's Timestamp instead of java.time.Instant. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +public final class ProtoLiteCelValueConverter extends BaseProtoCelValueConverter { + private final CelLiteDescriptorPool descriptorPool; + + public static ProtoLiteCelValueConverter newInstance( + CelLiteDescriptorPool celLiteDescriptorPool) { + return new ProtoLiteCelValueConverter(celLiteDescriptorPool); + } + + private static Object readPrimitiveField(CodedInputStream inputStream, FieldLiteDescriptor.Type fieldType) + throws IOException { + switch (fieldType) { + case SINT32: + return inputStream.readSInt32(); + case SINT64: + return inputStream.readSInt64(); + case INT32: + case ENUM: + return inputStream.readInt32(); + case INT64: + return inputStream.readInt64(); + case UINT32: + return inputStream.readUInt32(); + case UINT64: + return inputStream.readUInt64(); + case BOOL: + return inputStream.readBool(); + case STRING: + return inputStream.readStringRequireUtf8(); + default: + throw new IllegalStateException("Unexpected field type: " + fieldType); + } + } + + private static Object readFixed32BitField(CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException { + switch (fieldDescriptor.getProtoFieldType()) { + case FLOAT: + return inputStream.readFloat(); + case FIXED32: + case SFIXED32: + return inputStream.readRawLittleEndian32(); + default: + throw new IllegalStateException("Unexpected field type: " + fieldDescriptor.getProtoFieldType()); + } + } + + private static Object readFixed64BitField(CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException { + switch (fieldDescriptor.getProtoFieldType()) { + case DOUBLE: + return inputStream.readDouble(); + case FIXED64: + case SFIXED64: + return inputStream.readRawLittleEndian64(); + default: + throw new IllegalStateException("Unexpected field type: " + fieldDescriptor.getProtoFieldType()); + } + } + + private Object readLengthDelimitedField(CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException { + FieldLiteDescriptor.Type fieldType = fieldDescriptor.getProtoFieldType(); + + switch (fieldType) { + case BYTES: + return inputStream.readBytes(); + case MESSAGE: + MessageLite.Builder builder = getDefaultMessageBuilder(fieldDescriptor.getFieldProtoTypeName()); + + inputStream.readMessage(builder, ExtensionRegistryLite.getEmptyRegistry()); + return builder.build(); + case STRING: + return inputStream.readStringRequireUtf8(); + default: + throw new IllegalStateException("Unexpected field type: " + fieldType); + } + } + + private MessageLite.Builder getDefaultMessageBuilder(String protoTypeName) { + return descriptorPool.findDescriptor(protoTypeName) + .map(MessageLiteDescriptor::newMessageBuilder) + .orElseThrow(() -> new NoSuchElementException("Could not find a descriptor for: " + protoTypeName)); + } + + private Object getDefaultValue(FieldLiteDescriptor fieldDescriptor) { + FieldLiteDescriptor.CelFieldValueType celFieldValueType = fieldDescriptor.getCelFieldValueType(); + switch (celFieldValueType) { + case LIST: + return Collections.unmodifiableList(new ArrayList<>()); + case MAP: + return Collections.unmodifiableMap(new HashMap<>()); + case SCALAR: + FieldLiteDescriptor.JavaType type = fieldDescriptor.getJavaType(); + switch (type) { + case INT: + return fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.UINT32) ? UnsignedLong.ZERO : Defaults.defaultValue(long.class); + case LONG: + return fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.UINT64) ? UnsignedLong.ZERO : Defaults.defaultValue(long.class); + case ENUM: + return Defaults.defaultValue(long.class); + case FLOAT: + return Defaults.defaultValue(float.class); + case DOUBLE: + return Defaults.defaultValue(double.class); + case BOOLEAN: + return Defaults.defaultValue(boolean.class); + case STRING: + return ""; + case BYTE_STRING: + return ByteString.EMPTY; + case MESSAGE: + if (WellKnownProto.isWrapperType(fieldDescriptor.getFieldProtoTypeName())) { + return NullValue.NULL_VALUE; + } else { + return getDefaultMessageBuilder(fieldDescriptor.getFieldProtoTypeName()).build(); + } + default: + throw new IllegalStateException("Unexpected java type: " + type); + } + default: + throw new IllegalStateException("Unexpected cel field value type: " + celFieldValueType); + } + } + + private List readPackedRepeatedFields(CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) + throws IOException { + int length = inputStream.readInt32(); + int limit = inputStream.pushLimit(length); + List repeatedFieldValues = new ArrayList<>(); + while (inputStream.getBytesUntilLimit() > 0) { + Object value = readPrimitiveField(inputStream, fieldDescriptor.getProtoFieldType()); + repeatedFieldValues.add(value); + } + inputStream.popLimit(limit); + return Collections.unmodifiableList(repeatedFieldValues); + } + + private ImmutableMap readSingleMapEntry(CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException { + ImmutableMap singleMapEntry = readAllFields(inputStream.readByteArray(), fieldDescriptor.getFieldProtoTypeName()); + Object key = checkNotNull(singleMapEntry.get("key")); + Object value = checkNotNull(singleMapEntry.get("value")); + return ImmutableMap.of(key, value); + } + + private ImmutableMap readAllFields(byte[] bytes, String protoTypeName) + throws IOException { + MessageLiteDescriptor messageDescriptor = descriptorPool.getDescriptorOrThrow(protoTypeName); + CodedInputStream inputStream = CodedInputStream.newInstance(bytes); + + ImmutableMap.Builder fieldValues = ImmutableMap.builder(); + Map> nonPackedRepeatedFields = new HashMap<>(); + Map> mapFieldValues = new HashMap<>(); + for (int iterCount = 0; iterCount < bytes.length; iterCount++) { + int tag = inputStream.readTag(); + if (tag == 0) { + break; + } + + int tagWireType = WireFormat.getTagWireType(tag); + int fieldNumber = WireFormat.getTagFieldNumber(tag); + FieldLiteDescriptor fieldDescriptor = messageDescriptor.getByFieldNumberOrThrow(fieldNumber); + + Object payload; + switch (tagWireType) { + case WireFormat.WIRETYPE_VARINT: + payload = readPrimitiveField(inputStream, fieldDescriptor.getProtoFieldType()); + break; + case WireFormat.WIRETYPE_FIXED32: + payload = readFixed32BitField(inputStream, fieldDescriptor); + break; + case WireFormat.WIRETYPE_FIXED64: + payload = readFixed64BitField(inputStream, fieldDescriptor); + break; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + CelFieldValueType celFieldValueType = fieldDescriptor.getCelFieldValueType(); + switch (celFieldValueType) { + case LIST: + if (fieldDescriptor.getIsPacked()) { + payload = readPackedRepeatedFields(inputStream, fieldDescriptor); + } else { + List repeatedValues = nonPackedRepeatedFields.computeIfAbsent(fieldNumber, (unused) -> new ArrayList<>()); + Object elementValue = fieldDescriptor.getProtoFieldType().equals( + FieldLiteDescriptor.Type.MESSAGE) ? + readLengthDelimitedField(inputStream, fieldDescriptor) : + readPrimitiveField(inputStream, fieldDescriptor.getProtoFieldType()); + repeatedValues.add(elementValue); + payload = repeatedValues; + } + break; + case MAP: + Map fieldMap = mapFieldValues.computeIfAbsent(fieldNumber, (unused) -> new HashMap<>()); + fieldMap.putAll(readSingleMapEntry(inputStream, fieldDescriptor)); + payload = fieldMap; + break; + default: + payload = readLengthDelimitedField(inputStream, fieldDescriptor); + break; + } + break; + case WireFormat.WIRETYPE_START_GROUP: + case WireFormat.WIRETYPE_END_GROUP: + throw new UnsupportedOperationException("Groups are not supported"); + default: + throw new IllegalArgumentException("Unexpected wire type: " + tagWireType); + } + + switch (fieldDescriptor.getProtoFieldType()) { + case UINT32: + payload = UnsignedLong.valueOf((int) payload); + break; + case UINT64: + payload = UnsignedLong.valueOf((long) payload); + break; + default: + break; + } + + fieldValues.put(fieldDescriptor.getFieldName(), payload); + } + + // Protobuf encoding follows a "last one wins" semantics. This means for duplicated fields, + // we accept the last value encountered. + return fieldValues.buildKeepingLast(); + } + + ImmutableMap readAllFields(MessageLite msg, String protoTypeName) + throws IOException { + return readAllFields(msg.toByteArray(), protoTypeName); + } + + Object getDefaultValue(String protoTypeName, String fieldName) { + MessageLiteDescriptor messageDescriptor = descriptorPool.getDescriptorOrThrow(protoTypeName); + FieldLiteDescriptor fieldDescriptor = messageDescriptor.getByFieldNameOrThrow(fieldName); + + return getDefaultValue(fieldDescriptor); + } + + @Override + public CelValue fromProtoMessageToCelValue(String protoTypeName, MessageLite msg) { + checkNotNull(msg); + checkNotNull(protoTypeName); + + MessageLiteDescriptor messageInfo = + descriptorPool + .findDescriptor(protoTypeName) + .orElseThrow( + () -> + new NoSuchElementException( + "Could not find message info for : " + protoTypeName)); + WellKnownProto wellKnownProto = + WellKnownProto.getByTypeName(messageInfo.getProtoTypeName()).orElse(null); + + if (wellKnownProto == null) { + return ProtoMessageLiteValue.create( + msg, messageInfo.getProtoTypeName(), descriptorPool, this); + } + + switch (wellKnownProto) { + case ANY_VALUE: + throw new UnsupportedOperationException("Any messages are not supported yet"); + default: + return super.fromWellKnownProtoToCelValue(msg, wellKnownProto); + } + } + + private ProtoLiteCelValueConverter(CelLiteDescriptorPool celLiteDescriptorPool) { + this.descriptorPool = celLiteDescriptorPool; + } +} diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java new file mode 100644 index 000000000..b503b49aa --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java @@ -0,0 +1,98 @@ +// 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.values; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.MessageLite; +import dev.cel.common.internal.CelLiteDescriptorPool; +import dev.cel.common.types.StructTypeReference; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.io.IOException; +import java.util.Optional; + +/** ProtoMessageLiteValue is a struct value with protobuf support. */ +@AutoValue +@Immutable +public abstract class ProtoMessageLiteValue extends StructValue { + + @Override + public abstract MessageLite value(); + + @Override + public abstract StructTypeReference celType(); + + abstract CelLiteDescriptorPool descriptorPool(); + + abstract ProtoLiteCelValueConverter protoLiteCelValueConverter(); + + @Memoized + ImmutableMap fieldValues() { + ImmutableMap allFieldValues; + try { + allFieldValues = protoLiteCelValueConverter().readAllFields(value(), celType().name()); + } catch (IOException e) { + throw new RuntimeException(e); + } + return allFieldValues; + } + + @Override + public boolean isZeroValue() { + return value().getDefaultInstanceForType().equals(value()); + } + + @Override + public CelValue select(StringValue field) { + String fieldName = field.value(); + Object fieldValue = fieldValues().get(fieldName); + if (fieldValue == null) { + fieldValue = protoLiteCelValueConverter().getDefaultValue(celType().name(), fieldName); + } + + return protoLiteCelValueConverter().fromJavaObjectToCelValue(fieldValue); + } + + @Override + public Optional find(StringValue field) { + MessageLiteDescriptor messageInfo = descriptorPool().getDescriptorOrThrow(celType().name()); + FieldLiteDescriptor fieldInfo = messageInfo.getFieldDescriptorsMap().get(field.value()); + + CelValue selectedValue = select(field); + if (fieldInfo.getHasHasser()) { + if (!fieldValues().containsKey(field.value())) { + return Optional.empty(); + } + } else if (selectedValue.isZeroValue()){ + return Optional.empty(); + } + + return Optional.of(selectedValue); + } + + public static ProtoMessageLiteValue create( + MessageLite value, + String protoTypeName, + CelLiteDescriptorPool descriptorPool, + ProtoLiteCelValueConverter protoLiteCelValueConverter) { + Preconditions.checkNotNull(value); + return new AutoValue_ProtoMessageLiteValue( + value, StructTypeReference.create(protoTypeName), descriptorPool, protoLiteCelValueConverter); + } +} diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java new file mode 100644 index 000000000..ea74982d6 --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java @@ -0,0 +1,77 @@ +// 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.values; + +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.MessageLite; +import dev.cel.common.internal.CelLiteDescriptorPool; +import dev.cel.common.internal.DefaultLiteDescriptorPool; +import dev.cel.protobuf.CelLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * {@code ProtoMessageValueProvider} constructs new instances of protobuf lite-message given its + * fully qualified name and its fields to populate. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +public class ProtoMessageLiteValueProvider implements CelValueProvider { + private final CelLiteDescriptorPool descriptorPool; + private final ProtoLiteCelValueConverter protoLiteCelValueConverter; + + public ProtoLiteCelValueConverter getProtoLiteCelValueConverter() { + return protoLiteCelValueConverter; + } + + @Override + public Optional newValue(String structType, Map fields) { + MessageLiteDescriptor descriptor = descriptorPool.findDescriptor(structType).orElse(null); + if (descriptor == null) { + return Optional.empty(); + } + + if (!fields.isEmpty()) { + throw new UnsupportedOperationException("Message creation with prepopulated fields is not supported yet."); + } + + MessageLite message = descriptor.newMessageBuilder().build(); + return Optional.of(protoLiteCelValueConverter.fromProtoMessageToCelValue(structType, message)); + } + + + public static ProtoMessageLiteValueProvider newInstance( + CelLiteDescriptor... descriptors) { + return newInstance(ImmutableSet.copyOf(descriptors)); + } + + public static ProtoMessageLiteValueProvider newInstance(Set descriptors) { + DefaultLiteDescriptorPool descriptorPool = DefaultLiteDescriptorPool.newInstance(ImmutableSet.copyOf(descriptors)); + ProtoLiteCelValueConverter protoLiteCelValueConverter = + ProtoLiteCelValueConverter.newInstance(descriptorPool); + return new ProtoMessageLiteValueProvider(protoLiteCelValueConverter, descriptorPool); + } + + private ProtoMessageLiteValueProvider( + ProtoLiteCelValueConverter protoLiteCelValueConverter, + CelLiteDescriptorPool descriptorPool) { + this.protoLiteCelValueConverter = protoLiteCelValueConverter; + this.descriptorPool = descriptorPool; + } +} diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java index 430328596..b5ae03eb8 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java @@ -19,7 +19,6 @@ import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Message; 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.DynamicProto; @@ -89,15 +88,15 @@ private FieldDescriptor findField(Descriptor descriptor, String fieldName) { } public static ProtoMessageValueProvider newInstance( - DynamicProto dynamicProto, CelOptions celOptions) { - return new ProtoMessageValueProvider(dynamicProto, celOptions); + DynamicProto dynamicProto) { + return new ProtoMessageValueProvider(dynamicProto); } - private ProtoMessageValueProvider(DynamicProto dynamicProto, CelOptions celOptions) { + private ProtoMessageValueProvider(DynamicProto dynamicProto) { this.protoMessageFactory = dynamicProto.getProtoMessageFactory(); this.protoCelValueConverter = ProtoCelValueConverter.newInstance( - celOptions, protoMessageFactory.getDescriptorPool(), dynamicProto); - this.protoAdapter = new ProtoAdapter(dynamicProto, celOptions.enableUnsignedLongs()); + protoMessageFactory.getDescriptorPool(), dynamicProto); + this.protoAdapter = new ProtoAdapter(dynamicProto, true); } } diff --git a/common/src/test/java/dev/cel/common/BUILD.bazel b/common/src/test/java/dev/cel/common/BUILD.bazel index de0d1d079..33f916d23 100644 --- a/common/src/test/java/dev/cel/common/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/BUILD.bazel @@ -39,6 +39,7 @@ java_library( "@maven//:com_google_truth_extensions_truth_proto_extension", "@maven//:junit_junit", "@maven//:org_antlr_antlr4_runtime", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/common/src/test/java/dev/cel/common/ast/BUILD.bazel b/common/src/test/java/dev/cel/common/ast/BUILD.bazel index 3e2d87cee..bf241d447 100644 --- a/common/src/test/java/dev/cel/common/ast/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/ast/BUILD.bazel @@ -38,6 +38,7 @@ java_library( "@maven//:com_google_protobuf_protobuf_java", "@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/internal/BUILD.bazel b/common/src/test/java/dev/cel/common/internal/BUILD.bazel index efbe5e7d2..7da820097 100644 --- a/common/src/test/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/internal/BUILD.bazel @@ -40,6 +40,7 @@ java_library( "@maven//:com_google_protobuf_protobuf_java", "@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/internal/DefaultLiteDescriptorPoolTest.java b/common/src/test/java/dev/cel/common/internal/DefaultLiteDescriptorPoolTest.java new file mode 100644 index 000000000..496ce5713 --- /dev/null +++ b/common/src/test/java/dev/cel/common/internal/DefaultLiteDescriptorPoolTest.java @@ -0,0 +1,26 @@ +// 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 com.google.testing.junit.testparameterinjector.TestParameterInjector; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class DefaultLiteDescriptorPoolTest { + + @Test + public void smokeTest() {} +} diff --git a/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java b/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java index 7fc6eabfd..b1e2193e4 100644 --- a/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java +++ b/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java @@ -51,9 +51,4 @@ public void isWrapperType_withTypeName_true(String typeName) { public void isWrapperType_withTypeName_false(String typeName) { assertThat(WellKnownProto.isWrapperType(typeName)).isFalse(); } - - @Test - public void getJavaClassName() { - assertThat(WellKnownProto.ANY_VALUE.javaClassName()).isEqualTo("com.google.protobuf.Any"); - } } 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 a4e8e51c2..5e7e8137b 100644 --- a/common/src/test/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel @@ -33,6 +33,7 @@ java_library( "@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/CelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java index c373f04fc..46a956762 100644 --- a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java @@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import dev.cel.common.CelOptions; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,7 +26,7 @@ @RunWith(JUnit4.class) public class CelValueConverterTest { private static final CelValueConverter CEL_VALUE_CONVERTER = - new CelValueConverter(CelOptions.DEFAULT) {}; + new CelValueConverter() {}; @Test public void fromJavaPrimitiveToCelValue_returnsOpaqueValue() { 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 2c1e92e1d..5b599db3b 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -21,7 +21,6 @@ import com.google.protobuf.Timestamp; import com.google.protobuf.util.Durations; import com.google.protobuf.util.Timestamps; -import dev.cel.common.CelOptions; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; @@ -35,7 +34,6 @@ public class ProtoCelValueConverterTest { private static final ProtoCelValueConverter PROTO_CEL_VALUE_CONVERTER = ProtoCelValueConverter.newInstance( - CelOptions.DEFAULT, DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); 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 2ce416053..14ae59bdb 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java @@ -24,7 +24,6 @@ import com.google.protobuf.util.Timestamps; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelDescriptorUtil; -import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; @@ -56,7 +55,7 @@ public class ProtoMessageValueProviderTest { @Test public void newValue_createEmptyProtoMessage() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -70,7 +69,7 @@ public void newValue_createEmptyProtoMessage() { @Test public void newValue_createProtoMessage_fieldsPopulated() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -122,7 +121,7 @@ public void newValue_createProtoMessage_fieldsPopulated() { @Test public void newValue_createProtoMessage_unsignedLongFieldsPopulated() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.current().build()); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -143,7 +142,7 @@ public void newValue_createProtoMessage_unsignedLongFieldsPopulated() { @Test public void newValue_createProtoMessage_wrappersPopulated() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -189,7 +188,7 @@ public void newValue_createProtoMessage_wrappersPopulated() { @Test public void newValue_createProtoMessage_extensionFieldsPopulated() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -210,7 +209,7 @@ public void newValue_createProtoMessage_extensionFieldsPopulated() { @Test public void newValue_invalidMessageName_throws() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); CelRuntimeException e = assertThrows( @@ -225,7 +224,7 @@ public void newValue_invalidMessageName_throws() { @Test public void newValue_invalidField_throws() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); IllegalArgumentException e = assertThrows( @@ -245,9 +244,9 @@ public void newValue_invalidField_throws() { public void newValue_onCombinedProvider() { CelValueProvider celValueProvider = (structType, fields) -> Optional.empty(); ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); + ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO); CelValueProvider combinedProvider = - new CombinedCelValueProvider(celValueProvider, protoMessageValueProvider); + CombinedCelValueProvider.newInstance(celValueProvider, protoMessageValueProvider); ProtoMessageValue protoMessageValue = (ProtoMessageValue) diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java index ab3c52d18..685d4342d 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java @@ -35,7 +35,6 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelDescriptorUtil; -import dev.cel.common.CelOptions; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; @@ -55,7 +54,6 @@ public final class ProtoMessageValueTest { private static final ProtoCelValueConverter PROTO_CEL_VALUE_CONVERTER = ProtoCelValueConverter.newInstance( - CelOptions.current().build(), DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); @@ -140,7 +138,6 @@ public void findField_extensionField_success() { ImmutableList.of(TestAllTypesExtensions.getDescriptor()))); ProtoCelValueConverter protoCelValueConverter = ProtoCelValueConverter.newInstance( - CelOptions.DEFAULT, DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.create(descriptorPool))); TestAllTypes proto2Message = diff --git a/common/values/BUILD.bazel b/common/values/BUILD.bazel index 962d376cb..5784d0852 100644 --- a/common/values/BUILD.bazel +++ b/common/values/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") +load("//:cel_android_rules.bzl", "cel_android_library") package( default_applicable_licenses = ["//:license"], @@ -11,16 +12,44 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/values:cel_value"], ) +cel_android_library( + name = "cel_value_android", + visibility = ["//:android_allow_list"], + exports = ["//common/src/main/java/dev/cel/common/values:cel_value_android"], +) + java_library( name = "cel_value_provider", exports = ["//common/src/main/java/dev/cel/common/values:cel_value_provider"], ) +cel_android_library( + name = "cel_value_provider_android", + visibility = ["//:android_allow_list"], + exports = ["//common/src/main/java/dev/cel/common/values:cel_value_provider_android"], +) + java_library( name = "values", exports = ["//common/src/main/java/dev/cel/common/values"], ) +cel_android_library( + name = "values_android", + exports = ["//common/src/main/java/dev/cel/common/values:values_android"], +) + +java_library( + name = "base_proto_cel_value_converter", + exports = ["//common/src/main/java/dev/cel/common/values:base_proto_cel_value_converter"], +) + +cel_android_library( + name = "base_proto_cel_value_converter_android", + visibility = ["//:android_allow_list"], + exports = ["//common/src/main/java/dev/cel/common/values:base_proto_cel_value_converter_android"], +) + java_library( name = "proto_message_value_provider", exports = ["//common/src/main/java/dev/cel/common/values:proto_message_value_provider"], @@ -28,6 +57,7 @@ java_library( java_library( name = "cel_byte_string", + # used_by_android exports = ["//common/src/main/java/dev/cel/common/values:cel_byte_string"], ) @@ -35,3 +65,19 @@ java_library( name = "proto_message_value", exports = ["//common/src/main/java/dev/cel/common/values:proto_message_value"], ) + +java_library( + name = "proto_message_lite_value", + exports = ["//common/src/main/java/dev/cel/common/values:proto_message_lite_value"], +) + +cel_android_library( + name = "proto_message_lite_value_android", + visibility = ["//:android_allow_list"], + exports = ["//common/src/main/java/dev/cel/common/values:proto_message_lite_value_android"], +) + +java_library( + name = "proto_message_lite_value_provider", + exports = ["//common/src/main/java/dev/cel/common/values:proto_message_lite_value_provider"], +) diff --git a/compiler/src/test/java/dev/cel/compiler/tools/BUILD.bazel b/compiler/src/test/java/dev/cel/compiler/tools/BUILD.bazel index 1e0476a0b..877d0be8f 100644 --- a/compiler/src/test/java/dev/cel/compiler/tools/BUILD.bazel +++ b/compiler/src/test/java/dev/cel/compiler/tools/BUILD.bazel @@ -67,6 +67,7 @@ java_library( "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven//:junit_junit", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java index 11c151d4a..3b8202406 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java @@ -47,6 +47,7 @@ import java.util.Map; import org.junit.runners.model.Statement; +/** Conformance test suite for CEL-Java. */ // Qualifying proto2/proto3 TestAllTypes makes it less clear. @SuppressWarnings("UnnecessarilyFullyQualified") public final class ConformanceTest extends Statement { diff --git a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel index 82b9fea95..598369c66 100644 --- a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel @@ -110,7 +110,7 @@ java_library( "//runtime:function_binding", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -132,6 +132,7 @@ java_library( "//runtime:function_binding", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index 23848d4e3..ca4afad54 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -33,6 +33,7 @@ java_library( "@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/java_lite_proto_cel_library.bzl b/java_lite_proto_cel_library.bzl new file mode 100644 index 000000000..49a31e4dd --- /dev/null +++ b/java_lite_proto_cel_library.bzl @@ -0,0 +1,107 @@ +# 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. + +"""Starlark rule for generating descriptors that is compatible with Protolite Messages.""" + +load("@com_google_protobuf//bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("@rules_java//java:defs.bzl", "java_library") +load("@rules_proto//proto:defs.bzl", "proto_descriptor_set") +load("//publish:cel_version.bzl", "CEL_VERSION") + +def java_lite_proto_cel_library( + name, + java_descriptor_class_prefix, + deps, + debug = False): + """Generates a CelLiteDescriptor + + Args: + name: name of this target. + java_descriptor_class_prefix: Prefix name for the generated descriptor java class (ex: 'TestAllTypes' generates 'TestAllTypesCelLiteDescriptor.java'). + deps: Name of the proto_library target. Only a single proto_library is supported at this time. + debug: (optional) If true, prints additional information during codegen for debugging purposes. + """ + if not name: + fail("You must provide a name.") + + if not java_descriptor_class_prefix: + fail("You must provide a descriptor_class_prefix.") + + if not deps: + fail("You must provide a proto_library dependency.") + + if len(deps) > 1: + fail("You must provide only one proto_library dependency.") + + _generate_cel_lite_descriptor_class( + name, + java_descriptor_class_prefix + "CelLiteDescriptor", + deps[0], + debug, + ) + + java_lite_proto_library_dep_name = name + "_java_lite_proto_dep" + java_lite_proto_library( + name = java_lite_proto_library_dep_name, + deps = deps, + ) + + descriptor_codegen_deps = [ + java_lite_proto_library_dep_name, + "//protobuf:cel_lite_descriptor", + ] + + java_library( + name = name, + srcs = [":" + name + "_cel_lite_descriptor"], + deps = deps + descriptor_codegen_deps, + ) + +def _generate_cel_lite_descriptor_class( + name, + descriptor_class_name, + proto_src, + debug): + outfile = "%s.java" % descriptor_class_name + + transitive_descriptor_set_name = "%s_transitive_descriptor_set" % name + proto_descriptor_set( + name = transitive_descriptor_set_name, + deps = [proto_src], + ) + + direct_descriptor_set_name = proto_src + + debug_flag = "--debug" if debug else "" + + cmd = ( + "$(location //protobuf:cel_lite_descriptor_generator) " + + "--descriptor $(location %s) " % direct_descriptor_set_name + + "--transitive_descriptor_set $(location %s) " % transitive_descriptor_set_name + + "--descriptor_class_name %s " % descriptor_class_name + + "--out $(location %s) " % outfile + + "--version %s " % CEL_VERSION + + debug_flag + ) + + native.genrule( + name = name + "_cel_lite_descriptor", + srcs = [ + transitive_descriptor_set_name, + direct_descriptor_set_name, + ], + cmd = cmd, + outs = [outfile], + tools = ["//protobuf:cel_lite_descriptor_generator"], + ) diff --git a/parser/src/main/java/dev/cel/parser/BUILD.bazel b/parser/src/main/java/dev/cel/parser/BUILD.bazel index f01690423..6535fb4bb 100644 --- a/parser/src/main/java/dev/cel/parser/BUILD.bazel +++ b/parser/src/main/java/dev/cel/parser/BUILD.bazel @@ -139,7 +139,7 @@ java_library( "//common/ast", "//common/ast:cel_expr_visitor", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_re2j_re2j", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/parser/src/test/java/dev/cel/parser/BUILD.bazel b/parser/src/test/java/dev/cel/parser/BUILD.bazel index a93dc780e..f56c080ce 100644 --- a/parser/src/test/java/dev/cel/parser/BUILD.bazel +++ b/parser/src/test/java/dev/cel/parser/BUILD.bazel @@ -37,6 +37,7 @@ java_library( "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/protobuf/BUILD.bazel b/protobuf/BUILD.bazel new file mode 100644 index 000000000..2ed6c74f4 --- /dev/null +++ b/protobuf/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_java//java:defs.bzl", "java_library") +load("//:cel_android_rules.bzl", "cel_android_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//:internal"], # TODO: Expose when ready +) + +java_library( + name = "cel_lite_descriptor", + # used_by_android + exports = ["//protobuf/src/main/java/dev/cel/protobuf:cel_lite_descriptor"], +) + +java_library( + name = "proto_descriptor_collector", + testonly = 1, + exports = ["//protobuf/src/main/java/dev/cel/protobuf:proto_descriptor_collector"], +) + +alias( + name = "cel_lite_descriptor_generator", + actual = "//protobuf/src/main/java/dev/cel/protobuf:cel_lite_descriptor_generator", + visibility = ["//:internal"], +) diff --git a/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel b/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel new file mode 100644 index 000000000..e7059674d --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel @@ -0,0 +1,100 @@ +load("@rules_android//rules:rules.bzl", "android_library") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//protobuf:__pkg__"], +) + +filegroup( + name = "cel_lite_descriptor_template_file", + srcs = ["templates/cel_lite_descriptor_template.txt"], + visibility = ["//visibility:private"], +) + +java_binary( + name = "cel_lite_descriptor_generator", + srcs = ["CelLiteDescriptorGenerator.java"], + main_class = "dev.cel.protobuf.CelLiteDescriptorGenerator", + runtime_deps = [ + # Prevent Classloader from picking protolite. We need full version to access descriptors to codegen CelLiteDescriptor. + "@maven//:com_google_protobuf_protobuf_java", + ], + deps = [ + ":debug_printer", + ":java_file_generator", + ":proto_descriptor_collector", + "//common:cel_descriptors", + "//common/internal:proto_java_qualified_names", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:info_picocli_picocli", + ], +) + +java_library( + name = "proto_descriptor_collector", + srcs = ["ProtoDescriptorCollector.java"], + deps = [ + ":cel_lite_descriptor", + ":debug_printer", + ":lite_descriptor_codegen_metadata", + "//common:cel_descriptors", + "//common/internal:proto_java_qualified_names", + "//common/internal:well_known_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "java_file_generator", + srcs = ["JavaFileGenerator.java"], + resources = [ + ":cel_lite_descriptor_template_file", + ], + deps = [ + ":cel_lite_descriptor", + ":lite_descriptor_codegen_metadata", + "//:auto_value", + "@maven//:com_google_guava_guava", + "@maven//:org_freemarker_freemarker", + ], +) + +java_library( + name = "debug_printer", + srcs = ["DebugPrinter.java"], + deps = [ + "@maven//:info_picocli_picocli", + ], +) + +java_library( + name = "cel_lite_descriptor", + srcs = ["CelLiteDescriptor.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +java_library( + name = "lite_descriptor_codegen_metadata", + srcs = ["LiteDescriptorCodegenMetadata.java"], + tags = [ + ], + deps = [ + ":cel_lite_descriptor", + "//:auto_value", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) diff --git a/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java b/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java new file mode 100644 index 000000000..3db2cddf9 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java @@ -0,0 +1,366 @@ +// 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.protobuf; + +import static java.lang.Math.ceil; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.MessageLite; +import dev.cel.common.annotations.Internal; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Base class for code generated CEL lite descriptors to extend from. + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +@Immutable +@SuppressWarnings("ReturnMissingNullable") // Avoid taking a dependency on jspecify.nullable. +public abstract class CelLiteDescriptor { + @SuppressWarnings("Immutable") // Copied to unmodifiable map + private final Map protoFqnToDescriptors; + private final String version; + + public Map getProtoTypeNamesToDescriptors() { + return protoFqnToDescriptors; + } + + /** + * Retrieves the CEL-Java version this descriptor was generated with + */ + public String getVersion() { + return version; + } + + + /** + * Contains a collection of classes which describe protobuf messagelite types. + * + *

CEL Library Internals. Do Not Use. + */ + @Internal + @Immutable + public static final class MessageLiteDescriptor { + private final String fullyQualifiedProtoTypeName; + + @SuppressWarnings("Immutable") // Copied to an unmodifiable list + private final List fieldLiteDescriptors; + + @SuppressWarnings("Immutable") // Copied to an unmodifiable map + private final Map fieldNameToFieldDescriptors; + + @SuppressWarnings("Immutable") // Copied to an unmodifiable map + private final Map fieldNumberToFieldDescriptors; + + @SuppressWarnings("Immutable") // Does not alter the descriptor content + private final Supplier messageBuilderSupplier; + private final String fullyQualifiedJavaClassName; + + public String getProtoTypeName() { + return fullyQualifiedProtoTypeName; + } + + public List getFieldDescriptors() { + return fieldLiteDescriptors; + } + + public FieldLiteDescriptor getByFieldNameOrThrow(String fieldName) { + return Objects.requireNonNull(fieldNameToFieldDescriptors.get(fieldName)); + } + + public FieldLiteDescriptor getByFieldNumberOrThrow(int fieldNumber) { + return Objects.requireNonNull(fieldNumberToFieldDescriptors.get(fieldNumber)); + } + + public Map getFieldDescriptorsMap() { + return fieldNameToFieldDescriptors; + } + + public String getFullyQualifiedJavaClassName() { + return fullyQualifiedJavaClassName; + } + + public MessageLite.Builder newMessageBuilder() { + return messageBuilderSupplier.get(); + } + + MessageLiteDescriptor( + String fullyQualifiedProtoTypeName, + List fieldLiteDescriptors, + String fullyQualifiedJavaClassName) { + this(fullyQualifiedProtoTypeName, fieldLiteDescriptors, () -> null, fullyQualifiedJavaClassName); + } + + /** + * CEL Library Internals. Do not use. + * + *

Public visibility due to codegen. + */ + @Internal + public MessageLiteDescriptor( + String fullyQualifiedProtoTypeName, + List fieldLiteDescriptors) { + this(fullyQualifiedProtoTypeName, fieldLiteDescriptors, () -> null, null); + } + + /** + * CEL Library Internals. Do not use. + * + *

Public visibility due to codegen. + */ + @Internal + public MessageLiteDescriptor( + String fullyQualifiedProtoTypeName, + List fieldLiteDescriptors, + Supplier messageBuilderSupplier) { + this(fullyQualifiedProtoTypeName, fieldLiteDescriptors, messageBuilderSupplier, null); + } + + /** + * CEL Library Internals. Do not use. + * + *

Public visibility due to codegen. + */ + @Internal + private MessageLiteDescriptor( + String fullyQualifiedProtoTypeName, + List fieldLiteDescriptors, + Supplier messageBuilderSupplier, + String fullyQualifiedJavaClassName) { + this.fullyQualifiedProtoTypeName = checkNotNull(fullyQualifiedProtoTypeName); + // This is a cheap operation. View over the existing map with mutators disabled. + this.fieldLiteDescriptors = Collections.unmodifiableList(checkNotNull(fieldLiteDescriptors)); + Map fieldNameMap = new HashMap<>(getMapInitialCapacity( + fieldLiteDescriptors.size())); + Map fieldNumberMap = new HashMap<>(getMapInitialCapacity( + fieldLiteDescriptors.size())); + for (FieldLiteDescriptor fd : fieldLiteDescriptors) { + fieldNameMap.put(fd.fieldName, fd); + fieldNumberMap.put(fd.fieldNumber, fd); + } + this.fieldNameToFieldDescriptors = Collections.unmodifiableMap(fieldNameMap); + this.fieldNumberToFieldDescriptors = Collections.unmodifiableMap(fieldNumberMap); + this.messageBuilderSupplier = messageBuilderSupplier; + this.fullyQualifiedJavaClassName = fullyQualifiedJavaClassName; + } + } + + /** + * Describes a field of a protobuf messagelite type. + * + *

CEL Library Internals. Do Not Use. + */ + @Internal + @Immutable + public static final class FieldLiteDescriptor { + private final int fieldNumber; + private final String fieldName; + private final JavaType javaType; + private final String fieldProtoTypeName; + private final Type protoFieldType; + private final CelFieldValueType celFieldValueType; + private final boolean hasHasser; + private final boolean isPacked; + + /** + * Enumeration of the CEL field value type. This is analogous to the following from field + * descriptors: + * + *

    + *
  • LIST: Repeated Field + *
  • MAP: Map Field + *
  • SCALAR: Neither of above (scalars, messages) + *
+ */ + public enum CelFieldValueType { + SCALAR, + LIST, + MAP + } + + /** + * Enumeration of the java type. + * + *

This is exactly the same as com.google.protobuf.Descriptors#JavaType + */ + public enum JavaType { + INT, + LONG, + FLOAT, + DOUBLE, + BOOLEAN, + STRING, + BYTE_STRING, + ENUM, + MESSAGE + } + + /** + * Enumeration of the protobuf type. + * + *

This is exactly the same as com.google.protobuf.Descriptors#Type + */ + public enum Type { + DOUBLE, + FLOAT, + INT64, + UINT64, + INT32, + FIXED64, + FIXED32, + BOOL, + STRING, + GROUP, + MESSAGE, + BYTES, + UINT32, + ENUM, + SFIXED32, + SFIXED64, + SINT32, + SINT64 + } + + public int getFieldNumber() { + return fieldNumber; + } + + public String getFieldName() { + return fieldName; + } + + /** + * Gets the field's java type. + * + *

This is exactly the same as com.google.protobuf.Descriptors#JavaType + */ + public JavaType getJavaType() { + return javaType; + } + + public CelFieldValueType getCelFieldValueType() { + return celFieldValueType; + } + + /** + * Gets the field's protobuf type. + * + *

This is exactly the same as com.google.protobuf.Descriptors#Type + */ + public Type getProtoFieldType() { + return protoFieldType; + } + + /** + * Checks whether the field contains a hasser method (i.e: wrappers). + */ + public boolean getHasHasser() { + return hasHasser && celFieldValueType.equals(CelFieldValueType.SCALAR); + } + + /** + * Checks whether the repeated field is packed. + */ + public boolean getIsPacked() { + return isPacked; + } + + /** + * Gets the fully qualified protobuf type name for the field, including its package name (ex: + * cel.expr.conformance.proto3.TestAllTypes.SingleStringWrapper). Returns an empty string for + * primitives. + */ + public String getFieldProtoTypeName() { + return fieldProtoTypeName; + } + + /** + * Must be public, used for codegen only. Do not use. + * + * @param fieldNumber Field index + * @param fieldName Name of the field + * @param javaType Canonical Java type name (ex: Long, Double, Float, Message... see + * com.google.protobuf.Descriptors#JavaType) + * @param celFieldValueType Describes whether the field is a scalar, list or a map with respect + * to CEL. + * @param protoFieldType Protobuf Field Type (ex: INT32, SINT32, GROUP, MESSAGE... see + * com.google.protobuf.Descriptors#Type) + * @param hasHasser True if the message has a presence test method (ex: wrappers). + * @param fieldProtoTypeName Fully qualified protobuf type name for the field. Empty if the + * field is a primitive. + */ + @Internal + public FieldLiteDescriptor( + int fieldNumber, + String fieldName, + JavaType javaType, + CelFieldValueType celFieldValueType, // LIST, MAP, SCALAR + Type protoFieldType, // INT32, SINT32, GROUP, MESSAGE... (See Descriptors#Type) + boolean hasHasser, + boolean isPacked, + String fieldProtoTypeName) { + this.fieldNumber = fieldNumber; + this.fieldName = checkNotNull(fieldName); + this.javaType = javaType; + this.celFieldValueType = celFieldValueType; + this.protoFieldType = protoFieldType; + this.hasHasser = hasHasser; + this.isPacked = isPacked; + this.fieldProtoTypeName = checkNotNull(fieldProtoTypeName); + } + } + + protected CelLiteDescriptor( + String version, + List messageInfoList) { + Map protoFqnMap = + new HashMap<>(getMapInitialCapacity(messageInfoList.size())); + for (MessageLiteDescriptor msgInfo : messageInfoList) { + protoFqnMap.put(msgInfo.getProtoTypeName(), msgInfo); + } + + this.version = version; + this.protoFqnToDescriptors = Collections.unmodifiableMap(protoFqnMap); + } + + /** + * Returns a capacity that is sufficient to keep the map from being resized as long as it grows no + * larger than expectedSize and the load factor is ≥ its default (0.75). + */ + private static int getMapInitialCapacity(int expectedSize) { + if (expectedSize < 3) { + return expectedSize + 1; + } + + // See https://github.com/openjdk/jdk/commit/3e393047e12147a81e2899784b943923fc34da8e. 0.75 is + // used as a load factor. + return (int) ceil(expectedSize / 0.75); + } + + @CanIgnoreReturnValue + private static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } +} diff --git a/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptorGenerator.java b/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptorGenerator.java new file mode 100644 index 000000000..da154c7b4 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptorGenerator.java @@ -0,0 +1,159 @@ +// 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.protobuf; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.io.Files; +import com.google.protobuf.DescriptorProtos.FileDescriptorProto; +import com.google.protobuf.DescriptorProtos.FileDescriptorSet; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.ExtensionRegistry; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.internal.ProtoJavaQualifiedNames; +import dev.cel.protobuf.JavaFileGenerator.JavaFileGeneratorOption; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.Callable; +import picocli.CommandLine; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.Option; + +final class CelLiteDescriptorGenerator implements Callable { + + @Option( + names = {"--out"}, + description = "Outpath for the CelLiteDescriptor") + private String outPath = ""; + + @Option( + names = {"--descriptor"}, + description = + "Path to the descriptor (from proto_library) that the CelLiteDescriptor is to be" + + " generated from") + private String targetDescriptorPath = ""; + + @Option( + names = {"--transitive_descriptor_set"}, + description = "Path to the transitive set of descriptors") + private String transitiveDescriptorSetPath = ""; + + @Option( + names = {"--descriptor_class_name"}, + description = "Class name for the CelLiteDescriptor") + private String descriptorClassName = ""; + + @Option( + names = {"--version"}, + description = "CEL-Java version") + private String version = ""; + + @Option( + names = {"--debug"}, + description = "Prints debug output") + private boolean debug = false; + + private DebugPrinter debugPrinter; + + @Override + public Integer call() throws Exception { + String targetDescriptorProtoPath = extractProtoPath(targetDescriptorPath); + debugPrinter.print("Target descriptor proto path: " + targetDescriptorProtoPath); + + FileDescriptor targetFileDescriptor = null; + ImmutableSet transitiveFileDescriptors = + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet( + load(transitiveDescriptorSetPath)); + for (FileDescriptor fd : transitiveFileDescriptors) { + if (fd.getFullName().equals(targetDescriptorProtoPath)) { + debugPrinter.print("Transitive Descriptor Path: " + fd.getFullName()); + targetFileDescriptor = fd; + break; + } + } + + if (targetFileDescriptor == null) { + throw new IllegalArgumentException( + String.format( + "Target descriptor %s not found from transitive set of descriptors!", + targetDescriptorProtoPath)); + } + + codegenCelLiteDescriptor(targetFileDescriptor); + + return 0; + } + + private void codegenCelLiteDescriptor(FileDescriptor targetFileDescriptor) throws Exception { + String javaPackageName = ProtoJavaQualifiedNames.getJavaPackageName(targetFileDescriptor); + ProtoDescriptorCollector descriptorCollector = + ProtoDescriptorCollector.newInstance(debugPrinter); + + debugPrinter.print( + String.format("Descriptor Java class name: %s.%s", javaPackageName, descriptorClassName)); + + JavaFileGenerator.createFile( + outPath, + JavaFileGeneratorOption.newBuilder() + .setVersion(version) + .setDescriptorClassName(descriptorClassName) + .setPackageName(javaPackageName) + .setDescriptorMetadataList(descriptorCollector.collectCodegenMetadata(targetFileDescriptor)) + .build()); + } + + private String extractProtoPath(String descriptorPath) { + FileDescriptorSet fds = load(descriptorPath); + FileDescriptorProto fileDescriptorProto = Iterables.getOnlyElement(fds.getFileList()); + return fileDescriptorProto.getName(); + } + + private FileDescriptorSet load(String descriptorSetPath) { + try { + byte[] descriptorBytes = Files.toByteArray(new File(descriptorSetPath)); + // TODO: Implement ProtoExtensions + return FileDescriptorSet.parseFrom(descriptorBytes, ExtensionRegistry.getEmptyRegistry()); + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to load FileDescriptorSet from path: " + descriptorSetPath, e); + } + } + + private void printAllFlags(CommandLine cmd) { + debugPrinter.print("Flag values:"); + debugPrinter.print("-------------------------------------------------------------"); + for (OptionSpec option : cmd.getCommandSpec().options()) { + debugPrinter.print(option.longestName() + ": " + option.getValue()); + } + debugPrinter.print("-------------------------------------------------------------"); + } + + private void initializeDebugPrinter() { + this.debugPrinter = DebugPrinter.newInstance(debug); + } + + public static void main(String[] args) { + CelLiteDescriptorGenerator celLiteDescriptorGenerator = new CelLiteDescriptorGenerator(); + CommandLine cmd = new CommandLine(celLiteDescriptorGenerator); + cmd.parseArgs(args); + celLiteDescriptorGenerator.initializeDebugPrinter(); + celLiteDescriptorGenerator.printAllFlags(cmd); + + int exitCode = cmd.execute(args); + System.exit(exitCode); + } + + CelLiteDescriptorGenerator() {} +} diff --git a/protobuf/src/main/java/dev/cel/protobuf/DebugPrinter.java b/protobuf/src/main/java/dev/cel/protobuf/DebugPrinter.java new file mode 100644 index 000000000..34a09ce98 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/DebugPrinter.java @@ -0,0 +1,36 @@ +// 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.protobuf; + +import picocli.CommandLine.Help.Ansi; + +final class DebugPrinter { + + private final boolean debug; + + static DebugPrinter newInstance(boolean debug) { + return new DebugPrinter(debug); + } + + void print(String message) { + if (debug) { + System.out.println(Ansi.ON.string("@|cyan [CelLiteDescriptorGenerator] |@" + message)); + } + } + + private DebugPrinter(boolean debug) { + this.debug = debug; + } +} diff --git a/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java b/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java new file mode 100644 index 000000000..9d407fcf7 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java @@ -0,0 +1,95 @@ +// 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.protobuf; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; +// CEL-Internal-5 +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapperBuilder; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.Version; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +final class JavaFileGenerator { + + private static final String HELPER_CLASS_TEMPLATE_FILE = "cel_lite_descriptor_template.txt"; + + public static void createFile(String filePath, JavaFileGeneratorOption option) + throws IOException, TemplateException { + Version version = Configuration.VERSION_2_3_32; + Configuration cfg = new Configuration(version); + cfg.setClassForTemplateLoading(JavaFileGenerator.class, "templates/"); + cfg.setDefaultEncoding("UTF-8"); + cfg.setBooleanFormat("c"); + cfg.setAPIBuiltinEnabled(true); + DefaultObjectWrapperBuilder wrapperBuilder = new DefaultObjectWrapperBuilder(version); + wrapperBuilder.setExposeFields(true); + cfg.setObjectWrapper(wrapperBuilder.build()); + + Template template = cfg.getTemplate(HELPER_CLASS_TEMPLATE_FILE); + Writer out = new StringWriter(); + + template.process(option.getTemplateMap(), out); + + Files.asCharSink(new File(filePath), UTF_8).write(out.toString()); + } + + @AutoValue + abstract static class JavaFileGeneratorOption { + abstract String packageName(); + + abstract String descriptorClassName(); + + abstract String version(); + + abstract ImmutableList descriptorMetadataList(); + + ImmutableMap getTemplateMap() { + return ImmutableMap.of( + "package_name", packageName(), + "descriptor_class_name", descriptorClassName(), + "version", version(), + "descriptor_metadata_list", descriptorMetadataList()); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setPackageName(String packageName); + + abstract Builder setDescriptorClassName(String className); + + abstract Builder setVersion(String version); + + abstract Builder setDescriptorMetadataList(ImmutableList messageInfo); + + abstract JavaFileGeneratorOption build(); + } + + static Builder newBuilder() { + return new AutoValue_JavaFileGenerator_JavaFileGeneratorOption.Builder(); + } + } + + private JavaFileGenerator() {} +} diff --git a/protobuf/src/main/java/dev/cel/protobuf/LiteDescriptorCodegenMetadata.java b/protobuf/src/main/java/dev/cel/protobuf/LiteDescriptorCodegenMetadata.java new file mode 100644 index 000000000..c11d594a0 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/LiteDescriptorCodegenMetadata.java @@ -0,0 +1,112 @@ +package dev.cel.protobuf; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import dev.cel.common.annotations.Internal; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.CelFieldValueType; +import org.jspecify.annotations.Nullable; +/** + * LiteDescriptorCodegenMetadata holds metadata collected from a full protobuf descriptor pertinent for generating a {@link CelLiteDescriptor}. + * + *

The class properties here are almost identical to CelLiteDescriptor, except it contains extraneous information such as the fully qualified class names to + * support codegen, which do not need to be present on a CelLiteDescriptor instance. + * + *

Note: Properties must be of simple primitive types. + * + *

Note: JavaBeans prefix (e.g: getFoo) is required for compatibility with freemarker. + * + *

CEL Library Internals. Do Not Use. + */ +@AutoValue +@Internal +public abstract class LiteDescriptorCodegenMetadata { + + public abstract String getProtoTypeName(); + + public abstract ImmutableList getFieldDescriptors(); + + public abstract @Nullable String getJavaClassName(); // A java class name is not populated for maps, even though it behaves like a message. + + @AutoValue.Builder + abstract static class Builder { + + abstract Builder setProtoTypeName(String protoTypeName); + abstract Builder setJavaClassName(String javaClassName); + + abstract ImmutableList.Builder fieldDescriptorsBuilder(); + + @CanIgnoreReturnValue + Builder addFieldDescriptor(FieldLiteDescriptorMetadata fieldDescriptor) { + this.fieldDescriptorsBuilder().add(fieldDescriptor); + return this; + } + + abstract LiteDescriptorCodegenMetadata build(); + } + + static Builder newBuilder() { + return new AutoValue_LiteDescriptorCodegenMetadata.Builder(); + } + + @AutoValue + public abstract static class FieldLiteDescriptorMetadata { + + public abstract int getFieldNumber(); + + public abstract String getFieldName(); + + // Fully-qualified name to the Java Type enumeration (ex: dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.INT) + public String getJavaTypeEnumName() { + return getFullyQualifiedEnumName(getJavaType()); + } + + // Fully-qualified name to the CelFieldValueType enumeration (ex: dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.SCALAR) + public String getCelFieldValueTypeEnumName() { + return getFullyQualifiedEnumName(getCelFieldValueType()); + } + + // Fully-qualified name to the Proto Type enumeration (ex: dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.INT) + public String getProtoFieldTypeEnumName() { + return getFullyQualifiedEnumName(getProtoFieldType()); + } + + public abstract boolean getHasPresence(); + + public abstract boolean getIsPacked(); + + public abstract String getFieldProtoTypeName(); + + abstract FieldLiteDescriptor.JavaType getJavaType(); + + abstract FieldLiteDescriptor.Type getProtoFieldType(); + + abstract FieldLiteDescriptor.CelFieldValueType getCelFieldValueType(); + + private static String getFullyQualifiedEnumName(Object enumValue) { + String enumClassName = enumValue.getClass().getName(); + return (enumClassName + "." + enumValue).replaceAll("\\$", "."); + } + + @AutoValue.Builder + abstract static class Builder { + + abstract Builder setFieldNumber(int fieldNumber); + abstract Builder setFieldName(String fieldName); + abstract Builder setJavaType(FieldLiteDescriptor.JavaType javaTypeEnum); + abstract Builder setCelFieldValueType(FieldLiteDescriptor.CelFieldValueType celFieldValueTypeEnum); + abstract Builder setProtoFieldType(FieldLiteDescriptor.Type protoFieldTypeEnum); + abstract Builder setHasPresence(boolean hasHasser); + abstract Builder setIsPacked(boolean isPacked); + abstract Builder setFieldProtoTypeName(String fieldProtoTypeName); + + abstract FieldLiteDescriptorMetadata build(); + } + + static FieldLiteDescriptorMetadata.Builder newBuilder() { + return new AutoValue_LiteDescriptorCodegenMetadata_FieldLiteDescriptorMetadata.Builder() + .setFieldProtoTypeName(""); + } + } +} diff --git a/protobuf/src/main/java/dev/cel/protobuf/ProtoDescriptorCollector.java b/protobuf/src/main/java/dev/cel/protobuf/ProtoDescriptorCollector.java new file mode 100644 index 000000000..b795924be --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/ProtoDescriptorCollector.java @@ -0,0 +1,186 @@ +// 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.protobuf; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Descriptors; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor.Type; +import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; +import com.google.protobuf.Descriptors.FileDescriptor; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelDescriptors; +import dev.cel.common.internal.ProtoJavaQualifiedNames; +import dev.cel.common.internal.WellKnownProto; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.CelFieldValueType; +import dev.cel.protobuf.LiteDescriptorCodegenMetadata.FieldLiteDescriptorMetadata; +import java.util.ArrayDeque; +import java.util.stream.Collectors; + +/** + * ProtoDescriptorCollector inspects a {@link FileDescriptor} to collect message information into + * {@link LiteDescriptorCodegenMetadata}. This is later utilized to create an instance of {@code MessageLiteDescriptor}. + */ +final class ProtoDescriptorCollector { + + private final DebugPrinter debugPrinter; + + ImmutableList collectCodegenMetadata(FileDescriptor targetFileDescriptor) { + ImmutableList.Builder descriptorListBuilder = ImmutableList.builder(); + CelDescriptors celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + ImmutableList.of(targetFileDescriptor), /* resolveTypeDependencies= */ false); + ArrayDeque descriptorQueue = + celDescriptors.messageTypeDescriptors().stream() + // Don't collect WKTs. They are included in the default descriptor pool. + .filter(d -> !WellKnownProto.getByTypeName(d.getFullName()).isPresent()) + .collect(Collectors.toCollection(ArrayDeque::new)); + + while (!descriptorQueue.isEmpty()) { + Descriptor descriptor = descriptorQueue.pop(); + LiteDescriptorCodegenMetadata.Builder descriptorCodegenBuilder = LiteDescriptorCodegenMetadata.newBuilder(); + for (Descriptors.FieldDescriptor fieldDescriptor : descriptor.getFields()) { + FieldLiteDescriptorMetadata.Builder fieldDescriptorCodegenBuilder = FieldLiteDescriptorMetadata.newBuilder() + .setFieldNumber(fieldDescriptor.getNumber()) + .setFieldName(fieldDescriptor.getName()) + .setIsPacked(fieldDescriptor.isPacked()) + .setJavaType(adaptJavaType(fieldDescriptor.getJavaType())) + .setProtoFieldType(adaptProtoType(fieldDescriptor.getType())) + .setHasPresence(fieldDescriptor.hasPresence()); + + switch (fieldDescriptor.getJavaType()) { + case ENUM: + fieldDescriptorCodegenBuilder.setFieldProtoTypeName(fieldDescriptor.getEnumType().getFullName()); + break; + case MESSAGE: + fieldDescriptorCodegenBuilder.setFieldProtoTypeName(fieldDescriptor.getMessageType().getFullName()); + break; + default: + break; + } + + if (fieldDescriptor.isMapField()) { + fieldDescriptorCodegenBuilder.setCelFieldValueType(CelFieldValueType.MAP); + // Maps are treated as messages in proto. + descriptorQueue.push(fieldDescriptor.getMessageType()); + } else if (fieldDescriptor.isRepeated()) { + fieldDescriptorCodegenBuilder.setCelFieldValueType(CelFieldValueType.LIST); + } else { + fieldDescriptorCodegenBuilder.setCelFieldValueType(CelFieldValueType.SCALAR); + } + + descriptorCodegenBuilder.addFieldDescriptor(fieldDescriptorCodegenBuilder.build()); + + debugPrinter.print( + String.format( + "Collecting message %s, for field %s, type: %s", + descriptor.getFullName(), fieldDescriptor.getFullName(), fieldDescriptor.getType())); + } + + descriptorCodegenBuilder.setProtoTypeName(descriptor.getFullName()); + if (!descriptor.getOptions().getMapEntry()) { + String sanitizedJavaClassName = ProtoJavaQualifiedNames.getFullyQualifiedJavaClassName(descriptor).replaceAll("\\$", "."); + descriptorCodegenBuilder.setJavaClassName(sanitizedJavaClassName); + } + + descriptorListBuilder.add(descriptorCodegenBuilder.build()); + } + + return descriptorListBuilder.build(); + } + + @VisibleForTesting + static ProtoDescriptorCollector newInstance() { + return new ProtoDescriptorCollector(DebugPrinter.newInstance(false)); + } + + static ProtoDescriptorCollector newInstance(DebugPrinter debugPrinter) { + return new ProtoDescriptorCollector(debugPrinter); + } + + private static FieldLiteDescriptor.Type adaptProtoType(Type type) { + switch (type) { + case DOUBLE: + return FieldLiteDescriptor.Type.DOUBLE; + case FLOAT: + return FieldLiteDescriptor.Type.FLOAT; + case INT64: + return FieldLiteDescriptor.Type.INT64; + case UINT64: + return FieldLiteDescriptor.Type.UINT64; + case INT32: + return FieldLiteDescriptor.Type.INT32; + case FIXED64: + return FieldLiteDescriptor.Type.FIXED64; + case FIXED32: + return FieldLiteDescriptor.Type.FIXED32; + case BOOL: + return FieldLiteDescriptor.Type.BOOL; + case STRING: + return FieldLiteDescriptor.Type.STRING; + case GROUP: + return FieldLiteDescriptor.Type.GROUP; + case MESSAGE: + return FieldLiteDescriptor.Type.MESSAGE; + case BYTES: + return FieldLiteDescriptor.Type.BYTES; + case UINT32: + return FieldLiteDescriptor.Type.UINT32; + case ENUM: + return FieldLiteDescriptor.Type.ENUM; + case SFIXED32: + return FieldLiteDescriptor.Type.SFIXED32; + case SFIXED64: + return FieldLiteDescriptor.Type.SFIXED64; + case SINT32: + return FieldLiteDescriptor.Type.SINT32; + case SINT64: + return FieldLiteDescriptor.Type.SINT64; + default: + throw new IllegalArgumentException("Unknown Type: " + type); + } + } + + private static FieldLiteDescriptor.JavaType adaptJavaType(JavaType javaType) { + switch (javaType) { + case INT: + return FieldLiteDescriptor.JavaType.INT; + case LONG: + return FieldLiteDescriptor.JavaType.LONG; + case FLOAT: + return FieldLiteDescriptor.JavaType.FLOAT; + case DOUBLE: + return FieldLiteDescriptor.JavaType.DOUBLE; + case BOOLEAN: + return FieldLiteDescriptor.JavaType.BOOLEAN; + case STRING: + return FieldLiteDescriptor.JavaType.STRING; + case BYTE_STRING: + return FieldLiteDescriptor.JavaType.BYTE_STRING; + case ENUM: + return FieldLiteDescriptor.JavaType.ENUM; + case MESSAGE: + return FieldLiteDescriptor.JavaType.MESSAGE; + default: + throw new IllegalArgumentException("Unknown JavaType: " + javaType); + } + } + + private ProtoDescriptorCollector(DebugPrinter debugPrinter) { + this.debugPrinter = debugPrinter; + } +} diff --git a/protobuf/src/main/java/dev/cel/protobuf/templates/cel_lite_descriptor_template.txt b/protobuf/src/main/java/dev/cel/protobuf/templates/cel_lite_descriptor_template.txt new file mode 100644 index 000000000..491116027 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/templates/cel_lite_descriptor_template.txt @@ -0,0 +1,73 @@ +// 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. + +/** + * Generated by CEL-Java library. DO NOT EDIT! + * Version: ${version} + */ + +package ${package_name}; + +import dev.cel.protobuf.CelLiteDescriptor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class ${descriptor_class_name} extends CelLiteDescriptor { + + private static final ${descriptor_class_name} DESCRIPTOR = new ${descriptor_class_name}(); + + public static ${descriptor_class_name} getDescriptor() { + return DESCRIPTOR; + } + + private static List newDescriptors() { + List descriptors = new ArrayList<>(${descriptor_metadata_list?size}); + List fieldDescriptors; + <#list descriptor_metadata_list as descriptor_metadata> + + fieldDescriptors = new ArrayList<>(${descriptor_metadata.fieldDescriptors?size}); + <#list descriptor_metadata.fieldDescriptors as field_descriptor> + fieldDescriptors.add(new FieldLiteDescriptor( + ${field_descriptor.fieldNumber}, + "${field_descriptor.fieldName}", + ${field_descriptor.javaTypeEnumName}, + ${field_descriptor.celFieldValueTypeEnumName}, + ${field_descriptor.protoFieldTypeEnumName}, + ${field_descriptor.hasPresence}, + ${field_descriptor.isPacked}, + "${field_descriptor.fieldProtoTypeName}" + )); + + + descriptors.add( + new MessageLiteDescriptor( + "${descriptor_metadata.protoTypeName}", + fieldDescriptors + <#if descriptor_metadata.javaClassName??> + ,${descriptor_metadata.javaClassName}::newBuilder + + ) + ); + + + return Collections.unmodifiableList(descriptors); + } + + private ${descriptor_class_name}() { + super("${version}", newDescriptors()); + } +} \ No newline at end of file diff --git a/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel b/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel new file mode 100644 index 000000000..247973851 --- /dev/null +++ b/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel @@ -0,0 +1,42 @@ +load("@com_google_protobuf//bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") +load("@rules_java//java:defs.bzl", "java_library", "java_test") +load("//:java_lite_proto_cel_library.bzl", "java_lite_proto_cel_library") +load("//:testing.bzl", "junit4_test_suites") + +package( + default_applicable_licenses = ["//:license"], + default_testonly = True, +) + +java_test( + name = "cel_lite_descriptor_test", + srcs = ["CelLiteDescriptorTest.java"], + test_class = "dev.cel.protobuf.CelLiteDescriptorTest", + deps = [ + "//:java_truth", + "//protobuf:cel_lite_descriptor", + "//testing:test_all_types_cel_java_proto_lite", + "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto_lite", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +java_test( + name = "proto_descriptor_collector_test", + srcs = ["ProtoDescriptorCollectorTest.java"], + test_class = "dev.cel.protobuf.ProtoDescriptorCollectorTest", + runtime_deps = ["@maven//:com_google_protobuf_protobuf_java"], + deps = [ + "//protobuf:cel_lite_descriptor", + "//protobuf:proto_descriptor_collector", + "@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_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) diff --git a/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java b/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java new file mode 100644 index 000000000..e97c945fd --- /dev/null +++ b/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java @@ -0,0 +1,194 @@ +// 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.protobuf; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.CelFieldValueType; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.JavaType; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class CelLiteDescriptorTest { + + private static final TestAllTypesCelLiteDescriptor TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR = + TestAllTypesCelLiteDescriptor.getDescriptor(); + + @Test + public void getProtoTypeNamesToDescriptors_containsAllMessages() { + Map protoNamesToDescriptors = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR.getProtoTypeNamesToDescriptors(); + + assertThat(protoNamesToDescriptors).containsKey("cel.expr.conformance.proto3.TestAllTypes"); + assertThat(protoNamesToDescriptors) + .containsKey("cel.expr.conformance.proto3.TestAllTypes.NestedMessage"); + assertThat(protoNamesToDescriptors) + .containsKey("cel.expr.conformance.proto3.NestedTestAllTypes"); + } + + @Test + public void testAllTypesMessageLiteDescriptor_fullyQualifiedNames() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + + assertThat(testAllTypesDescriptor.getProtoTypeName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes"); + } + + @Test + public void testAllTypesMessageLiteDescriptor_fieldInfoMap_containsAllEntries() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + + assertThat(testAllTypesDescriptor.getFieldDescriptors()).hasSize(243); + } + + @Test + public void fieldDescriptor_scalarField() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = testAllTypesDescriptor.getByFieldNameOrThrow("single_string"); + + assertThat(fieldLiteDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.SCALAR); + assertThat(fieldLiteDescriptor.getJavaType()).isEqualTo(JavaType.STRING); + assertThat(fieldLiteDescriptor.getProtoFieldType()).isEqualTo(FieldLiteDescriptor.Type.STRING); + } + + @Test + public void fieldDescriptor_primitiveField_fullyQualifiedNames() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = testAllTypesDescriptor.getByFieldNameOrThrow("single_string"); + + assertThat(fieldLiteDescriptor.getFieldProtoTypeName()).isEmpty(); + } + + @Test + public void fieldDescriptor_hasHasser_falseOnPrimitive() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = testAllTypesDescriptor.getByFieldNameOrThrow("single_string"); + + assertThat(fieldLiteDescriptor.getHasHasser()).isFalse(); + } + + @Test + public void fieldDescriptor_hasHasser_trueOnWrapper() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = + testAllTypesDescriptor.getByFieldNameOrThrow("single_string_wrapper"); + + assertThat(fieldLiteDescriptor.getHasHasser()).isTrue(); + } + + @Test + public void fieldDescriptor_mapField() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = + testAllTypesDescriptor.getByFieldNameOrThrow("map_bool_string"); + + assertThat(fieldLiteDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.MAP); + assertThat(fieldLiteDescriptor.getJavaType()).isEqualTo(JavaType.MESSAGE); + assertThat(fieldLiteDescriptor.getProtoFieldType()).isEqualTo(FieldLiteDescriptor.Type.MESSAGE); + } + + @Test + public void fieldDescriptor_hasHasser_falseOnMap() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = + testAllTypesDescriptor.getByFieldNameOrThrow("map_bool_string"); + + assertThat(fieldLiteDescriptor.getHasHasser()).isFalse(); + } + + @Test + public void fieldDescriptor_repeatedField() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = + testAllTypesDescriptor.getByFieldNameOrThrow("repeated_int64"); + + assertThat(fieldLiteDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.LIST); + assertThat(fieldLiteDescriptor.getJavaType()).isEqualTo(JavaType.LONG); + assertThat(fieldLiteDescriptor.getProtoFieldType()).isEqualTo(FieldLiteDescriptor.Type.INT64); + } + + @Test + public void fieldDescriptor_hasHasser_falseOnRepeatedField() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = + testAllTypesDescriptor.getByFieldNameOrThrow("repeated_int64"); + + assertThat(fieldLiteDescriptor.getHasHasser()).isFalse(); + } + + @Test + public void fieldDescriptor_nestedMessage() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = + testAllTypesDescriptor.getByFieldNameOrThrow("standalone_message"); + + assertThat(fieldLiteDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.SCALAR); + assertThat(fieldLiteDescriptor.getJavaType()).isEqualTo(JavaType.MESSAGE); + assertThat(fieldLiteDescriptor.getProtoFieldType()).isEqualTo(FieldLiteDescriptor.Type.MESSAGE); + } + + @Test + public void fieldDescriptor_nestedMessage_fullyQualifiedNames() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldLiteDescriptor fieldLiteDescriptor = + testAllTypesDescriptor.getByFieldNameOrThrow("standalone_message"); + + assertThat(fieldLiteDescriptor.getFieldProtoTypeName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes.NestedMessage"); + } +} diff --git a/protobuf/src/test/java/dev/cel/protobuf/ProtoDescriptorCollectorTest.java b/protobuf/src/test/java/dev/cel/protobuf/ProtoDescriptorCollectorTest.java new file mode 100644 index 000000000..21895558a --- /dev/null +++ b/protobuf/src/test/java/dev/cel/protobuf/ProtoDescriptorCollectorTest.java @@ -0,0 +1,22 @@ +package dev.cel.protobuf; + +import com.google.common.collect.ImmutableList; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class ProtoDescriptorCollectorTest { + + @Test + public void smokeTest() { + ProtoDescriptorCollector collector = ProtoDescriptorCollector.newInstance(); + + ImmutableList descriptors = collector.collectCodegenMetadata( + TestAllTypes.getDescriptor().getFile()); + + System.out.println(descriptors); + } +} diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 1ca1e59c9..d0e24898c 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -140,6 +140,11 @@ java_library( exports = ["//runtime/src/main/java/dev/cel/runtime:unknown_attributes"], ) +cel_android_library( + name = "unknown_attributes_android", + exports = ["//runtime/src/main/java/dev/cel/runtime:unknown_attributes_android"], +) + java_library( name = "unknown_options", exports = ["//runtime/src/main/java/dev/cel/runtime:unknown_options"], diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index e4c4dbbc5..29048b684 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -156,8 +156,8 @@ java_library( ":runtime_helpers", "//common/annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -169,9 +169,9 @@ cel_android_library( ":interpretable_android", ":runtime_helpers_android", "//common/annotations", - "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_jspecify_jspecify", "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -201,6 +201,7 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -213,7 +214,6 @@ cel_android_library( "//common/types:types_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], @@ -347,7 +347,7 @@ java_library( "//common/internal:comparison_functions", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -364,8 +364,8 @@ cel_android_library( "//common:runtime_exception", "//common/internal:comparison_functions_android", "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_protobuf_protobuf_java", "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -401,7 +401,6 @@ cel_android_library( "//common:runtime_exception", "//common/internal:converter", "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_re2j_re2j", "@maven//:org_threeten_threeten_extra", "@maven_android//:com_google_guava_guava", @@ -426,6 +425,7 @@ java_library( "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_re2j_re2j", "@maven//:org_threeten_threeten_extra", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -550,6 +550,7 @@ java_library( "@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", ], ) @@ -570,7 +571,6 @@ cel_android_library( "//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_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", @@ -632,7 +632,7 @@ java_library( "//common/annotations", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_protobuf_protobuf_java", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -643,6 +643,7 @@ java_library( ], deps = [ ":activation", + ":cel_value_runtime_type_provider", ":descriptor_message_provider", ":descriptor_type_resolver", ":dispatcher", @@ -658,7 +659,6 @@ java_library( ":proto_message_runtime_equality", ":runtime_equality", ":runtime_type_provider", - ":runtime_type_provider_legacy", ":standard_functions", ":unknown_attributes", "//:auto_value", @@ -667,12 +667,18 @@ java_library( "//common:options", "//common/annotations", "//common/internal:cel_descriptor_pools", + "//common/internal:default_lite_descriptor_pool", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", + "//common/internal:proto_lite_adapter", "//common/internal:proto_message_factory", "//common/types:cel_types", "//common/values:cel_value_provider", + "//common/values:proto_message_lite_value", + "//common/values:proto_message_lite_value_provider", + "//common/values:proto_message_value", "//common/values:proto_message_value_provider", + "//protobuf:cel_lite_descriptor", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -693,6 +699,7 @@ java_library( "//:auto_value", "//common:cel_ast", "//common:options", + "//common/values:cel_value_provider", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -706,6 +713,7 @@ java_library( ], deps = [ ":activation", + ":cel_value_runtime_type_provider", ":dispatcher", ":evaluation_exception", ":function_binding", @@ -720,10 +728,13 @@ java_library( "//:auto_value", "//common:cel_ast", "//common:options", + "//common/internal:default_lite_descriptor_pool", + "//common/values:cel_value_provider", + "//common/values:proto_message_lite_value", + "//common/values:proto_message_lite_value_provider", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", ], ) @@ -734,6 +745,7 @@ cel_android_library( ], deps = [ ":activation_android", + ":cel_value_runtime_type_provider_android", ":dispatcher_android", ":evaluation_exception", ":function_binding_android", @@ -748,10 +760,13 @@ cel_android_library( "//:auto_value", "//common:cel_ast_android", "//common:options", + "//common/internal:default_lite_descriptor_pool_android", + "//common/values:cel_value_provider_android", + "//common/values:proto_message_lite_value_android", + "//common/values:proto_message_lite_value_provider", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", "@maven_android//:com_google_guava_guava", ], ) @@ -828,7 +843,6 @@ java_library( cel_android_library( name = "unknown_attributes_android", srcs = UNKNOWN_ATTRIBUTE_SOURCES, - visibility = ["//visibility:private"], deps = [ "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", @@ -838,23 +852,41 @@ cel_android_library( ) java_library( - name = "runtime_type_provider_legacy", - srcs = ["RuntimeTypeProviderLegacyImpl.java"], + name = "cel_value_runtime_type_provider", + srcs = ["CelValueRuntimeTypeProvider.java"], deps = [ ":runtime_type_provider", ":unknown_attributes", "//common:error_codes", - "//common:options", "//common:runtime_exception", "//common/annotations", - "//common/internal:cel_descriptor_pools", - "//common/internal:dynamic_proto", "//common/values", + "//common/values:base_proto_cel_value_converter", "//common/values:cel_value", "//common/values:cel_value_provider", - "//common/values:proto_message_value", + "//common/values:proto_message_lite_value_provider", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +cel_android_library( + name = "cel_value_runtime_type_provider_android", + srcs = ["CelValueRuntimeTypeProvider.java"], + deps = [ + ":runtime_type_provider_android", + ":unknown_attributes_android", + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "//common/values:base_proto_cel_value_converter_android", + "//common/values:cel_value_android", + "//common/values:cel_value_provider_android", + "//common/values:proto_message_lite_value_provider", + "//common/values:values_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -921,6 +953,7 @@ cel_android_library( "//:auto_value", "//common:cel_ast_android", "//common:options", + "//common/values:cel_value_provider_android", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", diff --git a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java index 86058aaff..494781b56 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java @@ -17,6 +17,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import dev.cel.common.CelOptions; +import dev.cel.common.values.CelValueProvider; /** Interface for building an instance of {@link CelLiteRuntime} */ public interface CelLiteRuntimeBuilder { @@ -40,6 +41,9 @@ public interface CelLiteRuntimeBuilder { @CanIgnoreReturnValue CelLiteRuntimeBuilder addFunctionBindings(Iterable bindings); + @CanIgnoreReturnValue + CelLiteRuntimeBuilder setValueProvider(CelValueProvider celValueProvider); + @CheckReturnValue CelLiteRuntime build(); } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java index 28c0ae0e6..1b60ed378 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java @@ -23,6 +23,7 @@ import com.google.protobuf.Message; import dev.cel.common.CelOptions; import dev.cel.common.values.CelValueProvider; +import dev.cel.protobuf.CelLiteDescriptor; import java.util.function.Function; /** Interface for building an instance of CelRuntime */ @@ -78,6 +79,12 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder addMessageTypes(Iterable descriptors); + @CanIgnoreReturnValue + CelRuntimeBuilder addCelLiteDescriptors(CelLiteDescriptor... descriptors); + + @CanIgnoreReturnValue + CelRuntimeBuilder addCelLiteDescriptors(Iterable descriptors); + /** * Add {@link FileDescriptor}s to the use for type-checking, and for object creation at * interpretation time. diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 141f9dc83..cadd69be9 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -36,13 +36,19 @@ import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.CombinedDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; +import dev.cel.common.internal.DefaultLiteDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; // CEL-Internal-3 import dev.cel.common.internal.ProtoMessageFactory; import dev.cel.common.types.CelTypes; import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.CelValueProvider.CombinedCelValueProvider; +import dev.cel.common.values.ProtoCelValueConverter; +import dev.cel.common.values.ProtoLiteCelValueConverter; +import dev.cel.common.values.ProtoMessageLiteValueProvider; import dev.cel.common.values.ProtoMessageValueProvider; +import dev.cel.protobuf.CelLiteDescriptor; import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Arithmetic; import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Comparison; import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Conversions; @@ -130,6 +136,7 @@ public static final class Builder implements CelRuntimeBuilder { @VisibleForTesting final ImmutableSet.Builder fileTypes; @VisibleForTesting final HashMap customFunctionBindings; + private final ImmutableSet.Builder celLiteDescriptorBuilder; @VisibleForTesting final ImmutableSet.Builder celRuntimeLibraries; @@ -170,6 +177,17 @@ public CelRuntimeBuilder addMessageTypes(Iterable descriptors) { return addFileTypes(CelDescriptorUtil.getFileDescriptorsForDescriptors(descriptors)); } + @Override + public CelRuntimeBuilder addCelLiteDescriptors(CelLiteDescriptor... descriptors) { + return addCelLiteDescriptors(Arrays.asList(descriptors)); + } + + @Override + public CelRuntimeBuilder addCelLiteDescriptors(Iterable descriptors) { + this.celLiteDescriptorBuilder.addAll(descriptors); + return this; + } + @Override public CelRuntimeBuilder addFileTypes(FileDescriptor... fileDescriptors) { return addFileTypes(Arrays.asList(fileDescriptors)); @@ -291,16 +309,40 @@ public CelRuntimeLegacyImpl build() { RuntimeTypeProvider runtimeTypeProvider; if (options.enableCelValue()) { - CelValueProvider messageValueProvider = - ProtoMessageValueProvider.newInstance(dynamicProto, options); - if (celValueProvider != null) { - messageValueProvider = - new CelValueProvider.CombinedCelValueProvider(celValueProvider, messageValueProvider); + ImmutableSet liteDescriptors = celLiteDescriptorBuilder.build(); + if (liteDescriptors.isEmpty()) { + CelValueProvider messageValueProvider = + ProtoMessageValueProvider.newInstance(dynamicProto); + if (celValueProvider != null) { + messageValueProvider = + CombinedCelValueProvider.newInstance(celValueProvider, messageValueProvider); + } + + ProtoCelValueConverter protoCelValueConverter = + ProtoCelValueConverter.newInstance(celDescriptorPool, dynamicProto); + + runtimeTypeProvider = + new CelValueRuntimeTypeProvider(messageValueProvider, protoCelValueConverter); + } else { + DefaultLiteDescriptorPool celLiteDescriptorPool = + DefaultLiteDescriptorPool.newInstance(liteDescriptors); + + // TODO: instantiate these dependencies within ProtoMessageLiteValueProvider. + // For now, they need to be outside to instantiate the RuntimeTypeProviderLegacyImpl + // adapter. + ProtoLiteCelValueConverter protoLiteCelValueConverter = + ProtoLiteCelValueConverter.newInstance(celLiteDescriptorPool); + CelValueProvider messageValueProvider = + ProtoMessageLiteValueProvider.newInstance(liteDescriptors); + if (celValueProvider != null) { + messageValueProvider = + CombinedCelValueProvider.newInstance(celValueProvider, messageValueProvider); + } + + runtimeTypeProvider = + new CelValueRuntimeTypeProvider(messageValueProvider, protoLiteCelValueConverter); } - runtimeTypeProvider = - new RuntimeTypeProviderLegacyImpl( - options, messageValueProvider, celDescriptorPool, dynamicProto); } else { runtimeTypeProvider = new DescriptorMessageProvider(runtimeTypeFactory, options); } @@ -407,6 +449,7 @@ private Builder() { this.fileTypes = ImmutableSet.builder(); this.customFunctionBindings = new HashMap<>(); this.celRuntimeLibraries = ImmutableSet.builder(); + this.celLiteDescriptorBuilder = ImmutableSet.builder(); this.extensionRegistry = ExtensionRegistry.getEmptyRegistry(); this.customTypeFactory = null; } diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java new file mode 100644 index 000000000..017bebc4c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java @@ -0,0 +1,142 @@ +// Copyright 2023 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.runtime; + +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.MessageLite; +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; +import dev.cel.common.values.BaseProtoCelValueConverter; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.ProtoMessageLiteValueProvider; +import dev.cel.common.values.SelectableValue; +import dev.cel.common.values.StringValue; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +/** Bridge between the old RuntimeTypeProvider and CelValueProvider APIs. */ +@Internal +@Immutable +final class CelValueRuntimeTypeProvider implements RuntimeTypeProvider { + + private final CelValueProvider valueProvider; + private final BaseProtoCelValueConverter protoCelValueConverter; + + @SuppressWarnings("Immutable") // Lazily populated cache. Does not change any observable behavior. + // TODO: Move to interpreter + private final HashMap celMessageLiteCache; + + CelValueRuntimeTypeProvider( + ProtoMessageLiteValueProvider protoMessageLiteValueProvider) { + this.valueProvider = protoMessageLiteValueProvider; + this.protoCelValueConverter = protoMessageLiteValueProvider.getProtoLiteCelValueConverter(); + this.celMessageLiteCache = new HashMap<>(); + } + + + CelValueRuntimeTypeProvider( + CelValueProvider valueProvider, BaseProtoCelValueConverter protoCelValueConverter) { + this.valueProvider = valueProvider; + this.protoCelValueConverter = protoCelValueConverter; + this.celMessageLiteCache = new HashMap<>(); + } + + @Override + public Object createMessage(String messageName, Map values) { + return unwrapCelValue( + valueProvider + .newValue(messageName, values) + .orElseThrow( + () -> + new NoSuchElementException( + "Could not generate a new value for message name: " + messageName))); + } + + @Override + @SuppressWarnings("unchecked") + public Object selectField(String typeName, Object message, String fieldName) { + // TODO + SelectableValue selectableValue = getSelectableValueOrThrow(typeName, + message, fieldName); + + return unwrapCelValue(selectableValue.select(StringValue.create(fieldName))); + } + + @Override + @SuppressWarnings("unchecked") + public Object hasField(String typeName, Object message, String fieldName) { + // TODO + SelectableValue selectableValue = getSelectableValueOrThrow(typeName, + message, fieldName); + + return selectableValue.find(StringValue.create(fieldName)).isPresent(); + } + + private SelectableValue getSelectableValueOrThrow(String typeName, Object obj, String fieldName) { + CelValue convertedCelValue = null; + if ((obj instanceof MessageLite)) { + convertedCelValue = celMessageLiteCache.computeIfAbsent((MessageLite) obj, (msg) -> protoCelValueConverter.fromProtoMessageToCelValue(typeName, + msg)); + } else if ((obj instanceof Map)) { + convertedCelValue = protoCelValueConverter.fromJavaObjectToCelValue(obj); + } else { + throwInvalidFieldSelection(fieldName); + } + + if (!(convertedCelValue instanceof SelectableValue)) { + throwInvalidFieldSelection(fieldName); + } + + return (SelectableValue) convertedCelValue; + } + + @Override + public Object adapt(String typeName, Object message) { + if (message instanceof CelUnknownSet) { + return message; // CelUnknownSet is handled specially for iterative evaluation. No need to + // adapt to CelValue. + } + + CelValue convertedCelValue; + if (message instanceof MessageLite) { + convertedCelValue = celMessageLiteCache.computeIfAbsent((MessageLite) message, (msg) -> protoCelValueConverter.fromProtoMessageToCelValue(typeName, (MessageLite) msg)); + } else { + convertedCelValue = protoCelValueConverter.fromJavaObjectToCelValue(message); + } + + return unwrapCelValue(convertedCelValue); + } + /** + * DefaultInterpreter cannot handle CelValue and instead expects plain Java objects. + * + *

This will become unnecessary once we introduce a rewrite of a Cel runtime. + */ + private Object unwrapCelValue(CelValue object) { + return protoCelValueConverter.fromCelValueToJavaObject(object); + } + + private static void throwInvalidFieldSelection(String fieldName) { + throw new CelRuntimeException( + new IllegalArgumentException( + String.format( + "Error resolving field '%s'. Field selections must be performed on messages or" + + " maps.", + fieldName)), + CelErrorCode.ATTRIBUTE_NOT_FOUND); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index 14ae160fa..5cbe38ab6 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -264,10 +264,11 @@ private IntermediateResult evalIdent(ExecutionFrame frame, CelExpr expr) private IntermediateResult resolveIdent(ExecutionFrame frame, CelExpr expr, String name) throws CelEvaluationException { + // TODO: Check if this is safe // Check whether the type exists in the type check map as a 'type'. - Optional checkedType = ast.getType(expr.id()); - if (checkedType.isPresent() && checkedType.get().kind() == CelKind.TYPE) { - TypeType typeValue = typeResolver.adaptType(checkedType.get()); + CelType checkedType = getCheckedTypeOrThrow(expr); + if (checkedType.kind() == CelKind.TYPE) { + TypeType typeValue = typeResolver.adaptType(checkedType); return IntermediateResult.create(typeValue); } @@ -279,7 +280,7 @@ private IntermediateResult resolveIdent(ExecutionFrame frame, CelExpr expr, Stri } // Value resolved from Binding, it could be Message, PartialMessage or unbound(null) - value = InterpreterUtil.strict(typeProvider.adapt(value)); + value = InterpreterUtil.strict(typeProvider.adapt(checkedType.name(), value)); IntermediateResult result = IntermediateResult.create(rawResult.attribute(), value); if (isLazyExpression) { @@ -327,10 +328,11 @@ private IntermediateResult evalFieldSelect( return IntermediateResult.create(attribute, operand); } + CelType operandCheckedType = getCheckedTypeOrThrow(operandExpr); if (isTestOnly) { - return IntermediateResult.create(attribute, typeProvider.hasField(operand, field)); + return IntermediateResult.create(attribute, typeProvider.hasField(operandCheckedType.name(), operand, field)); } - Object fieldValue = typeProvider.selectField(operand, field); + Object fieldValue = typeProvider.selectField(operandCheckedType.name(), operand, field); return IntermediateResult.create( attribute, InterpreterUtil.valueOrUnknown(fieldValue, expr.id())); @@ -416,7 +418,8 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall try { Object dispatchResult = overload.getDefinition().apply(argArray); if (celOptions.unwrapWellKnownTypesOnFunctionDispatch()) { - dispatchResult = typeProvider.adapt(dispatchResult); + CelType checkedType = getCheckedTypeOrThrow(expr); + dispatchResult = typeProvider.adapt(checkedType.name(), dispatchResult); } return IntermediateResult.create(attr, dispatchResult); } catch (CelRuntimeException ce) { @@ -510,6 +513,17 @@ private IntermediateResult evalConditional(ExecutionFrame frame, CelCall callExp } } + private CelType getCheckedTypeOrThrow(CelExpr expr) throws CelEvaluationException { + return ast.getType(expr.id()).orElseThrow(() -> + CelEvaluationExceptionBuilder.newBuilder( + "expected a runtime type for expression ID '%d' from checked expression, but found" + + " none.", + expr.id()) + .setErrorCode(CelErrorCode.TYPE_NOT_FOUND) + .setMetadata(metadata, expr.id()) + .build()); + } + private IntermediateResult mergeBooleanUnknowns(IntermediateResult lhs, IntermediateResult rhs) throws CelEvaluationException { // TODO: migrate clients to a common type that reports both expr-id unknowns @@ -635,18 +649,7 @@ private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr) return argResult; } - CelType checkedType = - ast.getType(typeExprArg.id()) - .orElseThrow( - () -> - CelEvaluationExceptionBuilder.newBuilder( - "expected a runtime type for '%s' from checked expression, but found" - + " none.", - argResult.getClass().getSimpleName()) - .setErrorCode(CelErrorCode.TYPE_NOT_FOUND) - .setMetadata(metadata, typeExprArg.id()) - .build()); - + CelType checkedType = getCheckedTypeOrThrow(typeExprArg); CelType checkedTypeValue = typeResolver.adaptType(checkedType); return IntermediateResult.create( typeResolver.resolveObjectType(argResult.value(), checkedTypeValue)); @@ -706,7 +709,8 @@ private Optional maybeEvalOptionalSelectField( } String field = callExpr.args().get(1).constant().stringValue(); - boolean hasField = (boolean) typeProvider.hasField(lhsResult.value(), field); + CelType checkedType = getCheckedTypeOrThrow(expr); + boolean hasField = (boolean) typeProvider.hasField(checkedType.name(), lhsResult.value(), field); if (!hasField) { // Protobuf sets default (zero) values to uninitialized fields. // In case of CEL's optional values, we want to explicitly return Optional.none() diff --git a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java index 179453e59..75564ac7d 100644 --- a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java @@ -98,7 +98,7 @@ public DescriptorMessageProvider(ProtoMessageFactory protoMessageFactory, CelOpt @Override @SuppressWarnings("unchecked") - public @Nullable Object selectField(Object message, String fieldName) { + public @Nullable Object selectField(String unusedTypeName, Object message, String fieldName) { boolean isOptionalMessage = false; if (message instanceof Optional) { isOptionalMessage = true; @@ -139,15 +139,16 @@ public DescriptorMessageProvider(ProtoMessageFactory protoMessageFactory, CelOpt /** Adapt object to its message value. */ @Override - public Object adapt(Object message) { + public Object adapt(String unusedTypeName, Object message) { if (message instanceof Message) { return protoAdapter.adaptProtoToValue((Message) message); } + return message; } @Override - public Object hasField(Object message, String fieldName) { + public Object hasField(String unusedTypeName, Object message, String fieldName) { if (message instanceof Optional) { Optional optionalMessage = (Optional) message; if (!optionalMessage.isPresent()) { diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 5a16d616a..8fa1f1360 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -20,13 +20,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import dev.cel.common.internal.DefaultLiteDescriptorPool; +import dev.cel.common.values.ProtoLiteCelValueConverter; +import dev.cel.common.values.ProtoMessageLiteValueProvider; import javax.annotation.concurrent.ThreadSafe; -import com.google.protobuf.MessageLiteOrBuilder; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; +import dev.cel.common.values.CelValueProvider; import java.util.Arrays; import java.util.HashMap; -import java.util.Map; +import java.util.Optional; @ThreadSafe final class LiteRuntimeImpl implements CelLiteRuntime { @@ -55,6 +58,7 @@ static final class Builder implements CelLiteRuntimeBuilder { @VisibleForTesting CelOptions celOptions; @VisibleForTesting final HashMap customFunctionBindings; @VisibleForTesting CelStandardFunctions celStandardFunctions; + @VisibleForTesting CelValueProvider celValueProvider; @Override public CelLiteRuntimeBuilder setOptions(CelOptions celOptions) { @@ -79,6 +83,12 @@ public CelLiteRuntimeBuilder addFunctionBindings(Iterable bi return this; } + @Override + public CelLiteRuntimeBuilder setValueProvider(CelValueProvider celValueProvider) { + this.celValueProvider = celValueProvider; + return this; + } + /** Throws if an unsupported flag in CelOptions is toggled. */ private static void assertAllowedCelOptions(CelOptions celOptions) { String prefix = "Misconfigured CelOptions: "; @@ -137,37 +147,23 @@ public CelLiteRuntime build() { dispatcher.add( overloadId, func.getArgTypes(), (args) -> func.getDefinition().apply(args))); - // TODO: provide implementations for dependencies + CelValueProvider valueProvider = celValueProvider; + if (valueProvider == null) { + valueProvider = (structType, fields) -> Optional.empty(); + } + + // TODO: Combine value providers if necessary + RuntimeTypeProvider runtimeTypeProvider = null; + if (valueProvider instanceof ProtoMessageLiteValueProvider) { + runtimeTypeProvider = new CelValueRuntimeTypeProvider((ProtoMessageLiteValueProvider) valueProvider); + } else { + runtimeTypeProvider = new CelValueRuntimeTypeProvider(celValueProvider, + ProtoLiteCelValueConverter.newInstance(DefaultLiteDescriptorPool.newInstance(ImmutableSet.of()))); + } + Interpreter interpreter = new DefaultInterpreter( - TypeResolver.create(), - new RuntimeTypeProvider() { - @Override - public Object createMessage(String messageName, Map values) { - throw new UnsupportedOperationException("Not implemented yet"); - } - - @Override - public Object selectField(Object message, String fieldName) { - throw new UnsupportedOperationException("Not implemented yet"); - } - - @Override - public Object hasField(Object message, String fieldName) { - throw new UnsupportedOperationException("Not implemented yet"); - } - - @Override - public Object adapt(Object message) { - if (message instanceof MessageLiteOrBuilder) { - throw new UnsupportedOperationException("Not implemented yet"); - } - - return message; - } - }, - dispatcher, - celOptions); + TypeResolver.create(), runtimeTypeProvider, dispatcher, celOptions); return new LiteRuntimeImpl( interpreter, celOptions, customFunctionBindings.values(), celStandardFunctions); diff --git a/runtime/src/main/java/dev/cel/runtime/MessageProvider.java b/runtime/src/main/java/dev/cel/runtime/MessageProvider.java index 96a803fdb..b1361ce6b 100644 --- a/runtime/src/main/java/dev/cel/runtime/MessageProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/MessageProvider.java @@ -29,11 +29,11 @@ public interface MessageProvider { Object createMessage(String messageName, Map values); /** Select field from message. */ - Object selectField(Object message, String fieldName); + Object selectField(String typeName, Object message, String fieldName); /** Check whether a field is set on message. */ - Object hasField(Object message, String fieldName); + Object hasField(String typeName, Object message, String fieldName); - /** Adapt object to its message value with source location metadata on failure . */ - Object adapt(Object message); + /** Adapt object to its message value with source location metadata on failure. */ + Object adapt(String typeName, Object message); } diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java deleted file mode 100644 index 36bd054f3..000000000 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2023 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.runtime; - -import com.google.common.annotations.VisibleForTesting; -import com.google.errorprone.annotations.Immutable; -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.CelDescriptorPool; -import dev.cel.common.internal.DynamicProto; -import dev.cel.common.values.CelValue; -import dev.cel.common.values.CelValueProvider; -import dev.cel.common.values.ProtoCelValueConverter; -import dev.cel.common.values.SelectableValue; -import dev.cel.common.values.StringValue; -import java.util.Map; -import java.util.NoSuchElementException; - -/** Bridge between the old RuntimeTypeProvider and CelValueProvider APIs. */ -@Internal -@Immutable -public final class RuntimeTypeProviderLegacyImpl implements RuntimeTypeProvider { - - private final CelValueProvider valueProvider; - private final ProtoCelValueConverter protoCelValueConverter; - - @VisibleForTesting - public RuntimeTypeProviderLegacyImpl( - CelOptions celOptions, - CelValueProvider valueProvider, - CelDescriptorPool celDescriptorPool, - DynamicProto dynamicProto) { - this.valueProvider = valueProvider; - this.protoCelValueConverter = - ProtoCelValueConverter.newInstance(celOptions, celDescriptorPool, dynamicProto); - } - - @Override - public Object createMessage(String messageName, Map values) { - return unwrapCelValue( - valueProvider - .newValue(messageName, values) - .orElseThrow( - () -> - new NoSuchElementException( - "Could not generate a new value for message name: " + messageName))); - } - - @Override - @SuppressWarnings("unchecked") - public Object selectField(Object message, String fieldName) { - CelValue convertedCelValue = protoCelValueConverter.fromJavaObjectToCelValue(message); - if (!(convertedCelValue instanceof SelectableValue)) { - throw new CelRuntimeException( - new IllegalArgumentException( - String.format( - "Error resolving field '%s'. Field selections must be performed on messages or" - + " maps.", - fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); - } - - SelectableValue selectableValue = (SelectableValue) convertedCelValue; - - return unwrapCelValue(selectableValue.select(StringValue.create(fieldName))); - } - - @Override - @SuppressWarnings("unchecked") - public Object hasField(Object message, String fieldName) { - CelValue convertedCelValue = protoCelValueConverter.fromJavaObjectToCelValue(message); - if (!(convertedCelValue instanceof SelectableValue)) { - throw new CelRuntimeException( - new IllegalArgumentException( - String.format( - "Error resolving field '%s'. Field selections must be performed on messages or" - + " maps.", - fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); - } - - SelectableValue selectableValue = (SelectableValue) convertedCelValue; - - return selectableValue.find(StringValue.create(fieldName)).isPresent(); - } - - @Override - public Object adapt(Object message) { - if (message instanceof CelUnknownSet) { - return message; // CelUnknownSet is handled specially for iterative evaluation. No need to - // adapt to CelValue. - } - return unwrapCelValue(protoCelValueConverter.fromJavaObjectToCelValue(message)); - } - - /** - * DefaultInterpreter cannot handle CelValue and instead expects plain Java objects. - * - *

This will become unnecessary once we introduce a rewrite of a Cel runtime. - */ - private Object unwrapCelValue(CelValue object) { - return protoCelValueConverter.fromCelValueToJavaObject(object); - } -} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 0f568b720..f75e964af 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -1,5 +1,6 @@ load("@rules_java//java:defs.bzl", "java_library") load("//:cel_android_rules.bzl", "cel_android_local_test") +load("//:java_lite_proto_cel_library.bzl", "java_lite_proto_cel_library") load("//:testing.bzl", "junit4_test_suites") load("//compiler/tools:compile_cel.bzl", "compile_cel") @@ -40,6 +41,35 @@ compile_cel( expression = "''.isEmpty() && [].isEmpty()", ) +compile_cel( + name = "compiled_proto3_select_primitives_all_ored", + environment = "//testing/environment:proto3_message_variables", + expression = "proto3.single_int32 == 1 || proto3.single_int64 == 2 || proto3.single_uint32 == 3u || proto3.single_uint64 == 4u ||" + + "proto3.single_sint32 == 5 || proto3.single_sint64 == 6 || proto3.single_fixed32 == 7u || proto3.single_fixed64 == 8u ||" + + "proto3.single_sfixed32 == 9 || proto3.single_sfixed64 == 10 || proto3.single_float == 1.5 || proto3.single_double == 2.5 ||" + + "proto3.single_bool || proto3.single_string == 'hello world' || proto3.single_bytes == b\'abc\'", + proto_srcs = ["@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_proto"], +) + +compile_cel( + name = "compiled_proto3_select_primitives", + environment = "//testing/environment:proto3_message_variables", + expression = "proto3.single_int32 == 1 && proto3.single_int64 == 2 && proto3.single_uint32 == 3u && proto3.single_uint64 == 4u &&" + + "proto3.single_sint32 == 5 && proto3.single_sint64 == 6 && proto3.single_fixed32 == 7u && proto3.single_fixed64 == 8u &&" + + "proto3.single_sfixed32 == 9 && proto3.single_sfixed64 == 10 && proto3.single_float == 1.5 && proto3.single_double == 2.5 &&" + + "proto3.single_bool && proto3.single_string == 'hello world' && proto3.single_bytes == b\'abc\'", + proto_srcs = ["@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_proto"], +) + +compile_cel( + name = "compiled_proto3_select_wrappers", + environment = "//testing/environment:proto3_message_variables", + expression = "proto3.single_int32_wrapper == 1 && proto3.single_int64_wrapper == 2 && proto3.single_float_wrapper == 1.5 &&" + + "proto3.single_double_wrapper == 2.5 && proto3.single_uint32_wrapper == 3u && proto3.single_uint64_wrapper == 4u &&" + + "proto3.single_string_wrapper == 'hello world' && proto3.single_bool_wrapper && proto3.single_bytes_wrapper == b\'abc\'", + proto_srcs = ["@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_proto"], +) + filegroup( name = "compiled_exprs", # keep sorted @@ -50,6 +80,9 @@ filegroup( ":compiled_list_literal", ":compiled_one_plus_two", ":compiled_primitive_variables", + ":compiled_proto3_select_primitives", + ":compiled_proto3_select_primitives_all_ored", + ":compiled_proto3_select_wrappers", ], ) @@ -60,6 +93,7 @@ java_library( ["*.java"], exclude = [ "CelValueInterpreterTest.java", + "CelLiteDescriptorInterpreterTest.java", "InterpreterTest.java", ], ), @@ -88,6 +122,7 @@ java_library( "//common/types:cel_v1alpha1_types", "//common/types:message_type_provider", "//common/values:cel_value_provider", + "//common/values:proto_message_lite_value_provider", "//compiler", "//compiler:compiler_builder", "//extensions:optional_library", @@ -113,6 +148,7 @@ java_library( "//runtime:type_resolver", "//runtime:unknown_attributes", "//runtime:unknown_options", + "//testing:test_all_types_cel_java_proto_lite", "@cel_spec//proto/cel/expr:checked_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", @@ -125,6 +161,7 @@ java_library( "@maven//:com_google_truth_extensions_truth_proto_extension", "@maven//:junit_junit", "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -169,20 +206,39 @@ cel_android_local_test( "//common:proto_ast_android", "//common/ast:ast_android", "//common/types:types_android", + "//common/values:proto_message_lite_value_provider", "//runtime:evaluation_exception", "//runtime:function_binding_android", "//runtime:lite_runtime_android", "//runtime:lite_runtime_factory_android", "//runtime:lite_runtime_impl_android", "//runtime:standard_functions_android", + "//runtime:unknown_attributes_android", + "//testing:test_all_types_cel_java_proto_lite", "@cel_spec//proto/cel/expr:checked_java_proto_lite", - "@maven//:com_google_protobuf_protobuf_java", + "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto_lite", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) +java_library( + name = "cel_lite_descriptor_interpreter_test", + testonly = 1, + srcs = [ + "CelLiteDescriptorInterpreterTest.java", + ], + deps = [ + "//extensions:optional_library", + "//runtime", + "//testing:base_interpreter_test", + "//testing:test_all_types_cel_java_proto_lite", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + junit4_test_suites( name = "test_suites", shard_count = 4, @@ -192,6 +248,7 @@ junit4_test_suites( ], src_dir = "src/test/java", deps = [ + ":cel_lite_descriptor_interpreter_test", ":cel_value_interpreter_test", ":interpreter_test", ":tests", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorEvaluationTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorEvaluationTest.java new file mode 100644 index 000000000..499413479 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorEvaluationTest.java @@ -0,0 +1,500 @@ +// 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.runtime; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.UnsignedLong; +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; +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; +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; +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.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import dev.cel.common.values.ProtoMessageLiteValueProvider; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypes.NestedEnum; +import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage; +import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor; +import dev.cel.parser.CelStandardMacro; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class CelLiteDescriptorEvaluationTest { + private static final CelCompiler CEL_COMPILER = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .addVar("content", SimpleType.DYN) + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer("cel.expr.conformance.proto3") + .build(); + + private static final CelLiteRuntime CEL_RUNTIME = + CelLiteRuntimeFactory.newLiteRuntimeBuilder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setValueProvider( + ProtoMessageLiteValueProvider.newInstance( + TestAllTypesCelLiteDescriptor.getDescriptor()) + ) + .build(); + + @Test + public void messageCreation_emptyMessage() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("TestAllTypes{}").getAst(); + + TestAllTypes simpleTest = (TestAllTypes) CEL_RUNTIME.createProgram(ast).eval(); + + assertThat(simpleTest).isEqualTo(TestAllTypes.getDefaultInstance()); + } + + @Test + public void messageCreation_fieldsPopulated() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("TestAllTypes{single_int32: 4}").getAst(); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); + + assertThat(e.getMessage()).contains("Message creation with prepopulated fields is not supported yet."); + } + + @Test + @TestParameters("{expression: 'msg.single_int32 == 1'}") + @TestParameters("{expression: 'msg.single_int64 == 2'}") + @TestParameters("{expression: 'msg.single_uint32 == 3u'}") + @TestParameters("{expression: 'msg.single_uint64 == 4u'}") + @TestParameters("{expression: 'msg.single_sint32 == 5'}") + @TestParameters("{expression: 'msg.single_sint64 == 6'}") + @TestParameters("{expression: 'msg.single_fixed32 == 7u'}") + @TestParameters("{expression: 'msg.single_fixed64 == 8u'}") + @TestParameters("{expression: 'msg.single_sfixed32 == 9'}") + @TestParameters("{expression: 'msg.single_sfixed64 == 10'}") + @TestParameters("{expression: 'msg.single_float == 1.5'}") + @TestParameters("{expression: 'msg.single_double == 2.5'}") + @TestParameters("{expression: 'msg.single_bool == true'}") + @TestParameters("{expression: 'msg.single_string == \"foo\"'}") + @TestParameters("{expression: 'msg.single_bytes == b\"abc\"'}") + @TestParameters("{expression: 'msg.optional_bool == true'}") + public void fieldSelection_literals(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .setSingleInt32(1) + .setSingleInt64(2L) + .setSingleUint32(3) + .setSingleUint64(4L) + .setSingleSint32(5) + .setSingleSint64(6L) + .setSingleFixed32(7) + .setSingleFixed64(8L) + .setSingleSfixed32(9) + .setSingleSfixed64(10L) + .setSingleFloat(1.5f) + .setSingleDouble(2.5d) + .setSingleBool(true) + .setSingleString("foo") + .setSingleBytes(ByteString.copyFromUtf8("abc")) + .setOptionalBool(true) + .build(); + + boolean result = (boolean) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isTrue(); + } + + @Test + @TestParameters("{expression: 'msg.single_uint32'}") + @TestParameters("{expression: 'msg.single_uint64'}") + public void fieldSelection_unsigned(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = TestAllTypes.newBuilder().setSingleUint32(4).setSingleUint64(4L).build(); + + Object result = CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isEqualTo(UnsignedLong.valueOf(4L)); + } + + @Test + @TestParameters("{expression: 'msg.repeated_int32'}") + @TestParameters("{expression: 'msg.repeated_int64'}") + @SuppressWarnings("unchecked") + public void fieldSelection_packedRepeatedInts(String expression) throws Exception { + // Note: non-LEN delimited primitives such as ints are packed by default in proto3 + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .addRepeatedInt32(1) + .addRepeatedInt32(2) + .addRepeatedInt64(1L) + .addRepeatedInt64(2L) + .build(); + + List result = + (List) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).containsExactly(1L, 2L).inOrder(); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldSelection_repeatedStrings() throws Exception { + // Note: len-delimited fields, such as string and messages are not packed. + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("msg.repeated_string").getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .addRepeatedString("hello") + .addRepeatedString("world") + .build(); + + List result = + (List) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).containsExactly("hello", "world").inOrder(); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldSelection_repeatedBoolWrappers() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("msg.repeated_bool_wrapper").getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .addRepeatedBoolWrapper(BoolValue.of(true)) + .addRepeatedBoolWrapper(BoolValue.of(false)) + .addRepeatedBoolWrapper(BoolValue.of(true)) + .build(); + + List result = + (List) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).containsExactly(true, false, true).inOrder(); + } + + @Test + @TestParameters("{expression: 'msg.map_string_int32'}") + @TestParameters("{expression: 'msg.map_string_int64'}") + @SuppressWarnings("unchecked") + public void fieldSelection_map(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .putMapStringInt32("a", 1) + .putMapStringInt32("b", 2) + .putMapStringInt64("a", 1L) + .putMapStringInt64("b", 2L) + .build(); + + Map result = + (Map) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).containsExactly("a", 1L, "b", 2L); + } + + @Test + @TestParameters("{expression: 'msg.single_int32_wrapper == 1'}") + @TestParameters("{expression: 'msg.single_int64_wrapper == 2'}") + @TestParameters("{expression: 'msg.single_uint32_wrapper == 3u'}") + @TestParameters("{expression: 'msg.single_uint64_wrapper == 4u'}") + @TestParameters("{expression: 'msg.single_float_wrapper == 1.5'}") + @TestParameters("{expression: 'msg.single_double_wrapper == 2.5'}") + @TestParameters("{expression: 'msg.single_bool_wrapper == true'}") + @TestParameters("{expression: 'msg.single_string_wrapper == \"foo\"'}") + @TestParameters("{expression: 'msg.single_bytes_wrapper == b\"abc\"'}") + public void fieldSelection_wrappers(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .setSingleInt32Wrapper(Int32Value.of(1)) + .setSingleInt64Wrapper(Int64Value.of(2L)) + .setSingleUint32Wrapper(UInt32Value.of(3)) + .setSingleUint64Wrapper(UInt64Value.of(4L)) + .setSingleFloatWrapper(FloatValue.of(1.5f)) + .setSingleDoubleWrapper(DoubleValue.of(2.5d)) + .setSingleBoolWrapper(BoolValue.of(true)) + .setSingleStringWrapper(StringValue.of("foo")) + .setSingleBytesWrapper(BytesValue.of(ByteString.copyFromUtf8("abc"))) + .build(); + + boolean result = (boolean) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isTrue(); + } + + @Test + @TestParameters("{expression: 'msg.single_int32_wrapper'}") + @TestParameters("{expression: 'msg.single_int64_wrapper'}") + @TestParameters("{expression: 'msg.single_uint32_wrapper'}") + @TestParameters("{expression: 'msg.single_uint64_wrapper'}") + @TestParameters("{expression: 'msg.single_float_wrapper'}") + @TestParameters("{expression: 'msg.single_double_wrapper'}") + @TestParameters("{expression: 'msg.single_bool_wrapper'}") + @TestParameters("{expression: 'msg.single_string_wrapper'}") + @TestParameters("{expression: 'msg.single_bytes_wrapper'}") + public void fieldSelection_wrappersNullability(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = TestAllTypes.newBuilder().build(); + + Object result = CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isEqualTo(NullValue.NULL_VALUE); + } + + @Test + 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(); + + Duration result = + (Duration) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isEqualTo(Durations.fromMinutes(10)); + } + + @Test + 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(); + + Timestamp result = + (Timestamp) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isEqualTo(Timestamps.fromSeconds(50)); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldSelection_jsonStruct() throws Exception { + String expression = "msg.single_struct"; + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = TestAllTypes.newBuilder().setSingleStruct( + Struct.newBuilder() + .putFields("one", Values.of(1)) + .putFields("two", Values.of(true)) + ).build(); + + Map result = + (Map) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).containsExactly("one", 1.0d, "two", true).inOrder(); + } + + @Test + public void fieldSelection_jsonValue() throws Exception { + String expression = "msg.single_value"; + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = TestAllTypes.newBuilder().setSingleValue( + Values.of("foo") + ).build(); + + String result = + (String) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isEqualTo("foo"); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldSelection_jsonListValue() throws Exception { + String expression = "msg.list_value"; + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = TestAllTypes.newBuilder().setListValue( + ListValue.newBuilder().addValues(Values.of(true)).addValues(Values.of("foo")) + ).build(); + + List result = + (List) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).containsExactly(true, "foo").inOrder(); + } + + @Test + @TestParameters("{expression: 'has(msg.single_int32)'}") + @TestParameters("{expression: 'has(msg.single_int64)'}") + @TestParameters("{expression: 'has(msg.single_int32_wrapper)'}") + @TestParameters("{expression: 'has(msg.single_int64_wrapper)'}") + @TestParameters("{expression: 'has(msg.repeated_int32)'}") + @TestParameters("{expression: 'has(msg.repeated_int64)'}") + @TestParameters("{expression: 'has(msg.repeated_int32_wrapper)'}") + @TestParameters("{expression: 'has(msg.repeated_int64_wrapper)'}") + @TestParameters("{expression: 'has(msg.map_string_int32)'}") + @TestParameters("{expression: 'has(msg.map_string_int64)'}") + @TestParameters("{expression: 'has(msg.map_bool_int32_wrapper)'}") + @TestParameters("{expression: 'has(msg.map_bool_int64_wrapper)'}") + public void presenceTest_proto3_evaluatesToFalse(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .setSingleInt32(0) + .addAllRepeatedInt32(ImmutableList.of()) + .addAllRepeatedInt32Wrapper(ImmutableList.of()) + .putAllMapBoolInt32(ImmutableMap.of()) + .putAllMapBoolInt32Wrapper(ImmutableMap.of()) + .build(); + + boolean result = (boolean) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isFalse(); + } + + @Test + @TestParameters("{expression: 'has(msg.single_int32)'}") + @TestParameters("{expression: 'has(msg.single_int64)'}") + @TestParameters("{expression: 'has(msg.single_int32_wrapper)'}") + @TestParameters("{expression: 'has(msg.single_int64_wrapper)'}") + @TestParameters("{expression: 'has(msg.repeated_int32)'}") + @TestParameters("{expression: 'has(msg.repeated_int64)'}") + @TestParameters("{expression: 'has(msg.repeated_int32_wrapper)'}") + @TestParameters("{expression: 'has(msg.repeated_int64_wrapper)'}") + @TestParameters("{expression: 'has(msg.map_string_int32)'}") + @TestParameters("{expression: 'has(msg.map_string_int64)'}") + @TestParameters("{expression: 'has(msg.map_string_int32_wrapper)'}") + @TestParameters("{expression: 'has(msg.map_string_int64_wrapper)'}") + public void presenceTest_proto3_evaluatesToTrue(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expression).getAst(); + TestAllTypes msg = + TestAllTypes.newBuilder() + .setSingleInt32(1) + .setSingleInt64(2) + .setSingleInt32Wrapper(Int32Value.of(0)) + .setSingleInt64Wrapper(Int64Value.of(0)) + .addAllRepeatedInt32(ImmutableList.of(1)) + .addAllRepeatedInt64(ImmutableList.of(2L)) + .addAllRepeatedInt32Wrapper(ImmutableList.of(Int32Value.of(0))) + .addAllRepeatedInt64Wrapper(ImmutableList.of(Int64Value.of(0L))) + .putAllMapStringInt32Wrapper(ImmutableMap.of("a", Int32Value.of(1))) + .putAllMapStringInt64Wrapper(ImmutableMap.of("b", Int64Value.of(2L))) + .putMapStringInt32("a", 1) + .putMapStringInt64("b", 2) + .build(); + + boolean result = (boolean) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + + assertThat(result).isTrue(); + } + + @Test + public void nestedMessage_traversalThroughSetField() throws Exception { + CelAbstractSyntaxTree ast = + CEL_COMPILER + .compile("msg.single_nested_message.bb == 43 && has(msg.single_nested_message)") + .getAst(); + TestAllTypes nestedMessage = + TestAllTypes.newBuilder() + .setSingleNestedMessage(NestedMessage.newBuilder().setBb(43)) + .build(); + + boolean result = + (boolean) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", nestedMessage)); + + assertThat(result).isTrue(); + } + + @Test + public void nestedMessage_safeTraversal() throws Exception { + CelAbstractSyntaxTree ast = + CEL_COMPILER + .compile("msg.single_nested_message.bb == 43") + .getAst(); + TestAllTypes nestedMessage = + TestAllTypes.newBuilder() + .setSingleNestedMessage(NestedMessage.getDefaultInstance()) + .build(); + + boolean result = + (boolean) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", nestedMessage)); + + assertThat(result).isFalse(); + } + + @Test + public void enumSelection() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("msg.single_nested_enum").getAst(); + TestAllTypes nestedMessage = + TestAllTypes.newBuilder().setSingleNestedEnum(NestedEnum.BAR).build(); + Long result = (Long) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", nestedMessage)); + + assertThat(result).isEqualTo(NestedEnum.BAR.getNumber()); + } + + @SuppressWarnings("ImmutableEnumChecker") // Test only + private enum DefaultValueTestCase { + INT32("msg.single_int32", 0L), + INT64("msg.single_int64", 0L), + UINT32("msg.single_uint32", UnsignedLong.ZERO), + UINT64("msg.single_uint64", UnsignedLong.ZERO), + SINT32("msg.single_sint32", 0L), + SINT64("msg.single_sint64", 0L), + FIXED32("msg.single_fixed32", 0L), + FIXED64("msg.single_fixed64", 0L), + SFIXED32("msg.single_sfixed32", 0L), + SFIXED64("msg.single_sfixed64", 0L), + FLOAT("msg.single_float", 0.0d), + DOUBLE("msg.single_double", 0.0d), + BOOL("msg.single_bool", false), + STRING("msg.single_string", ""), + BYTES("msg.single_bytes", ByteString.EMPTY), + ENUM("msg.standalone_enum", 0L), + OPTIONAL_BOOL("msg.optional_bool", false), + REPEATED_STRING("msg.repeated_string", Collections.unmodifiableList(new ArrayList<>())), + MAP_INT32_BOOL("msg.map_int32_bool", Collections.unmodifiableMap(new HashMap<>())), + ; + + private final String expression; + private final Object expectedValue; + + DefaultValueTestCase(String expression, Object expectedValue) { + this.expression = expression; + this.expectedValue = expectedValue; + } + } + + @Test + public void unsetField_defaultValue(@TestParameter DefaultValueTestCase testCase) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(testCase.expression).getAst(); + + Object result = CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", TestAllTypes.getDefaultInstance())); + + assertThat(result).isEqualTo(testCase.expectedValue); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorInterpreterTest.java new file mode 100644 index 000000000..4531b3b74 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorInterpreterTest.java @@ -0,0 +1,93 @@ +// 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.runtime; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor; +import dev.cel.extensions.CelOptionalLibrary; +import dev.cel.testing.BaseInterpreterTest; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class CelLiteDescriptorInterpreterTest extends BaseInterpreterTest { + public CelLiteDescriptorInterpreterTest(@TestParameter InterpreterTestOption testOption) { + super( + testOption.celOptions.toBuilder().enableCelValue(true).build(), + testOption.useNativeCelType, + CelRuntimeFactory.standardCelRuntimeBuilder() + .addCelLiteDescriptors(TestAllTypesCelLiteDescriptor.getDescriptor()) + .addLibraries(CelOptionalLibrary.INSTANCE) + .setOptions(testOption.celOptions.toBuilder().enableCelValue(true).build()) + .build()); + } + + @Override + public void dynamicMessage_adapted() throws Exception { + // Dynamic message is not supported in Protolite + skipBaselineVerification(); + } + + @Override + public void dynamicMessage_dynamicDescriptor() throws Exception { + // Dynamic message is not supported in Protolite + skipBaselineVerification(); + } + + // All the tests below rely on message creation with fields populated. They are excluded for time being until this support is added. + @Override + public void wrappers() throws Exception { + skipBaselineVerification(); + } + @Override + public void jsonConversions() { + skipBaselineVerification(); + } + + @Override + public void nestedEnums() { + skipBaselineVerification(); + } + + @Override + public void messages() throws Exception { + skipBaselineVerification(); + } + + @Override + public void packUnpackAny() { + skipBaselineVerification(); + } + + @Override + public void lists() throws Exception { + skipBaselineVerification(); + } + + @Override + public void maps() throws Exception { + skipBaselineVerification(); + } + + @Override + public void jsonValueTypes() { + skipBaselineVerification(); + } + + @Override + public void messages_error() { + skipBaselineVerification(); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java index 5e5dfbe7f..02e5167d9 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java @@ -17,6 +17,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.google.protobuf.BoolValue; +import com.google.protobuf.BytesValue; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; +import com.google.protobuf.StringValue; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; +import dev.cel.common.values.ProtoMessageLiteValueProvider; import dev.cel.expr.CheckedExpr; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; @@ -32,6 +42,8 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.types.SimpleType; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor; import dev.cel.runtime.CelLiteRuntime.Program; import java.net.URL; import java.util.List; @@ -221,6 +233,101 @@ public void eval_customFunctions() throws Exception { assertThat(result).isTrue(); } + @Test + public void eval_proto3Message_unknowns() throws Exception { + CelLiteRuntime runtime = + CelLiteRuntimeFactory.newLiteRuntimeBuilder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setValueProvider(ProtoMessageLiteValueProvider.newInstance( + TestAllTypesCelLiteDescriptor.getDescriptor())) + .build(); + CelAbstractSyntaxTree ast = readCheckedExpr("compiled_proto3_select_primitives"); + Program program = runtime.createProgram(ast); + + CelUnknownSet result = (CelUnknownSet) program.eval(); + + assertThat(result.unknownExprIds()).hasSize(15); + } + + @Test + public void eval_proto3Message_primitiveWithDefaults() throws Exception { + CelLiteRuntime runtime = + CelLiteRuntimeFactory.newLiteRuntimeBuilder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setValueProvider(ProtoMessageLiteValueProvider.newInstance( + TestAllTypesCelLiteDescriptor.getDescriptor())) + .build(); + // Ensures that all branches of the OR conditions are evaluated, and that appropriate defaults are + // returned for primitives. + CelAbstractSyntaxTree ast = readCheckedExpr("compiled_proto3_select_primitives_all_ored"); + Program program = runtime.createProgram(ast); + + boolean result = (boolean) program.eval(ImmutableMap.of("proto3", TestAllTypes.newBuilder().build())); + + assertThat(result).isFalse(); + } + + @Test + public void eval_protoMessage_primitives() throws Exception { + CelLiteRuntime runtime = + CelLiteRuntimeFactory.newLiteRuntimeBuilder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setValueProvider(ProtoMessageLiteValueProvider.newInstance( + TestAllTypesCelLiteDescriptor.getDescriptor())) + .build(); + CelAbstractSyntaxTree ast = readCheckedExpr("compiled_proto3_select_primitives"); + Program program = runtime.createProgram(ast); + + boolean result = (boolean) program.eval( + ImmutableMap.of("proto3", + TestAllTypes.newBuilder() + .setSingleInt32(1) + .setSingleInt64(2L) + .setSingleUint32(3) + .setSingleUint64(4L) + .setSingleSint32(5) + .setSingleSint64(6L) + .setSingleFixed32(7) + .setSingleFixed64(8L) + .setSingleSfixed32(9) + .setSingleSfixed64(10L) + .setSingleFloat(1.5f) + .setSingleDouble(2.5d) + .setSingleBool(true) + .setSingleString("hello world") + .setSingleBytes(ByteString.copyFromUtf8("abc")) + .build())); + + assertThat(result).isTrue(); + } + + @Test + public void eval_protoMessage_wrappers() throws Exception { + CelLiteRuntime runtime = + CelLiteRuntimeFactory.newLiteRuntimeBuilder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setValueProvider(ProtoMessageLiteValueProvider.newInstance( + TestAllTypesCelLiteDescriptor.getDescriptor())) + .build(); + CelAbstractSyntaxTree ast = readCheckedExpr("compiled_proto3_select_wrappers"); + Program program = runtime.createProgram(ast); + + boolean result = (boolean) program.eval( + ImmutableMap.of("proto3", TestAllTypes.newBuilder() + .setSingleInt32Wrapper(Int32Value.of(1)) + .setSingleInt64Wrapper(Int64Value.of(2L)) + .setSingleUint32Wrapper(UInt32Value.of(3)) + .setSingleUint64Wrapper(UInt64Value.of(4L)) + .setSingleFloatWrapper(FloatValue.of(1.5f)) + .setSingleDoubleWrapper(DoubleValue.of(2.5d)) + .setSingleBoolWrapper(BoolValue.of(true)) + .setSingleStringWrapper(StringValue.of("hello world")) + .setSingleBytesWrapper(BytesValue.of(ByteString.copyFromUtf8("abc"))) + .build())); + + assertThat(result).isTrue(); + } + private static CelAbstractSyntaxTree readCheckedExpr(String compiledCelTarget) throws Exception { URL url = Resources.getResource(CelLiteRuntimeTest.class, compiledCelTarget + ".binarypb"); byte[] checkedExprBytes = Resources.toByteArray(url); diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java index 13199cbf3..d3d465d3f 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java @@ -48,6 +48,7 @@ import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.parser.CelStandardMacro; import dev.cel.parser.CelUnparserFactory; +import dev.cel.runtime.CelLiteRuntime.Program; import java.util.List; import java.util.Map; import java.util.Optional; @@ -288,6 +289,20 @@ public void trace_select() throws Exception { assertThat(result).isEqualTo(3L); } + @Test + public void fooTest() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .addVar("proto3", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .addMessageTypes(TestAllTypes.getDescriptor()).build(); + CelAbstractSyntaxTree ast = cel.compile("proto3.standalone_message").getAst(); + Program program = cel.createProgram(ast); + + Object result = program.eval(ImmutableMap.of("proto3", TestAllTypes.newBuilder().build())); + + assertThat(result).isNotNull(); + } + @Test public void trace_struct() throws Exception { CelEvaluationListener listener = diff --git a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java index 5dcab8c10..f0b42148e 100644 --- a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java @@ -140,24 +140,24 @@ public void createMessage_badFieldError() { @Test public void hasField_mapKeyFound() { - assertThat(provider.hasField(ImmutableMap.of("hello", "world"), "hello")).isEqualTo(true); + assertThat(provider.hasField(TestAllTypes.getDescriptor().getFullName(), ImmutableMap.of("hello", "world"), "hello")).isEqualTo(true); } @Test public void hasField_mapKeyNotFound() { - assertThat(provider.hasField(ImmutableMap.of(), "hello")).isEqualTo(false); + assertThat(provider.hasField(TestAllTypes.getDescriptor().getFullName(), ImmutableMap.of(), "hello")).isEqualTo(false); } @Test public void selectField_mapKeyFound() { - assertThat(provider.selectField(ImmutableMap.of("hello", "world"), "hello")).isEqualTo("world"); + assertThat(provider.selectField(TestAllTypes.getDescriptor().getFullName(), ImmutableMap.of("hello", "world"), "hello")).isEqualTo("world"); } @Test public void selectField_mapKeyNotFound() { CelRuntimeException e = Assert.assertThrows( - CelRuntimeException.class, () -> provider.selectField(ImmutableMap.of(), "hello")); + CelRuntimeException.class, () -> provider.selectField(TestAllTypes.getDescriptor().getFullName(), ImmutableMap.of(), "hello")); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); } @@ -166,7 +166,7 @@ public void selectField_mapKeyNotFound() { public void selectField_unsetWrapperField() { assertThat( provider.selectField( - dev.cel.expr.conformance.proto3.TestAllTypes.getDefaultInstance(), + TestAllTypes.getDescriptor().getFullName(), dev.cel.expr.conformance.proto3.TestAllTypes.getDefaultInstance(), "single_int64_wrapper")) .isEqualTo(NullValue.NULL_VALUE); } @@ -175,7 +175,7 @@ public void selectField_unsetWrapperField() { public void selectField_nonProtoObjectError() { CelRuntimeException e = Assert.assertThrows( - CelRuntimeException.class, () -> provider.selectField("hello", "not_a_field")); + CelRuntimeException.class, () -> provider.selectField(TestAllTypes.getDescriptor().getFullName(), "hello", "not_a_field")); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); } @@ -194,6 +194,7 @@ public void selectField_extensionUsingDynamicTypes() { long result = (long) provider.selectField( + TestAllTypes.getDescriptor().getFullName(), TestAllTypes.newBuilder().setExtension(TestAllTypesExtensions.int32Ext, 10).build(), TestAllTypesProto.getDescriptor().getPackage() + ".int32_ext"); diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index d4ace9a7c..2f76bee94 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") +load("//:java_lite_proto_cel_library.bzl", "java_lite_proto_cel_library") package( default_applicable_licenses = ["//:license"], @@ -45,3 +46,9 @@ java_library( name = "expr_value_utils", exports = ["//testing/src/main/java/dev/cel/testing/utils:expr_value_utils"], ) + +java_lite_proto_cel_library( + name = "test_all_types_cel_java_proto_lite", + java_descriptor_class_prefix = "TestAllTypes", + deps = ["@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_proto"], +) diff --git a/testing/environment/BUILD.bazel b/testing/environment/BUILD.bazel index d21ce77d3..9ef685038 100644 --- a/testing/environment/BUILD.bazel +++ b/testing/environment/BUILD.bazel @@ -23,3 +23,13 @@ alias( name = "custom_functions", actual = "//testing/src/test/resources/environment:custom_functions", ) + +alias( + name = "proto2_message_variables", + actual = "//testing/src/test/resources/environment:proto2_message_variables", +) + +alias( + name = "proto3_message_variables", + actual = "//testing/src/test/resources/environment:proto3_message_variables", +) diff --git a/testing/src/main/java/dev/cel/testing/BUILD.bazel b/testing/src/main/java/dev/cel/testing/BUILD.bazel index 1923807f4..97298e8a4 100644 --- a/testing/src/main/java/dev/cel/testing/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/BUILD.bazel @@ -116,5 +116,6 @@ java_library( "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:junit_junit", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 1494c2197..bb17bd6e4 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -125,14 +125,21 @@ protected enum InterpreterTestOption { private CelRuntime celRuntime; public BaseInterpreterTest(CelOptions celOptions, boolean useNativeCelType) { - super(useNativeCelType); - this.celOptions = celOptions; - this.celRuntime = + this( + celOptions, + useNativeCelType, CelRuntimeFactory.standardCelRuntimeBuilder() .addLibraries(CelOptionalLibrary.INSTANCE) .addFileTypes(TEST_FILE_DESCRIPTORS) .setOptions(celOptions) - .build(); + .build()); + } + + public BaseInterpreterTest( + CelOptions celOptions, boolean useNativeCelType, CelRuntime celRuntime) { + super(useNativeCelType); + this.celOptions = celOptions; + this.celRuntime = celRuntime; } @Override diff --git a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel index d511b95ba..6d4e41fb0 100644 --- a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel @@ -23,5 +23,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_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/testing/src/test/resources/environment/BUILD.bazel b/testing/src/test/resources/environment/BUILD.bazel index dfae7e3d2..5cf3f061c 100644 --- a/testing/src/test/resources/environment/BUILD.bazel +++ b/testing/src/test/resources/environment/BUILD.bazel @@ -27,3 +27,13 @@ filegroup( name = "custom_functions", srcs = ["custom_functions.yaml"], ) + +filegroup( + name = "proto2_message_variables", + srcs = ["proto2_message_variables.yaml"], +) + +filegroup( + name = "proto3_message_variables", + srcs = ["proto3_message_variables.yaml"], +) diff --git a/testing/src/test/resources/environment/proto2_message_variables.yaml b/testing/src/test/resources/environment/proto2_message_variables.yaml new file mode 100644 index 000000000..ac06fb1a2 --- /dev/null +++ b/testing/src/test/resources/environment/proto2_message_variables.yaml @@ -0,0 +1,18 @@ +# 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. + +name: "proto2-message-variables" +variables: +- name: "proto2" + type_name: "cel.expr.conformance.proto2.TestAllTypes" diff --git a/testing/src/test/resources/environment/proto3_message_variables.yaml b/testing/src/test/resources/environment/proto3_message_variables.yaml new file mode 100644 index 000000000..12f39c7fa --- /dev/null +++ b/testing/src/test/resources/environment/proto3_message_variables.yaml @@ -0,0 +1,18 @@ +# 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. + +name: "proto3-message-variables" +variables: +- name: "proto3" + type_name: "cel.expr.conformance.proto3.TestAllTypes" 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 3cf20ebb6..47db17360 100644 --- a/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel @@ -31,6 +31,7 @@ java_library( "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], )