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/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..9d48fc865 --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/CelLiteDescriptorPool.java @@ -0,0 +1,28 @@ +// 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 com.google.protobuf.MessageLite; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.util.Optional; + +/** TODO: Replace with CelLiteDescriptor */ +@Immutable +public interface CelLiteDescriptorPool { + Optional findDescriptorByTypeName(String protoTypeName); + + Optional findDescriptor(MessageLite msg); +} 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..c79a97488 --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DefaultLiteDescriptorPool.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.common.internal; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.MessageLite; +import dev.cel.common.annotations.Internal; +import dev.cel.protobuf.CelLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.util.Optional; + +/** Descriptor pool for {@link CelLiteDescriptor}s. */ +@Immutable +@Internal +public final class DefaultLiteDescriptorPool implements CelLiteDescriptorPool { + private final ImmutableMap protoFqnToMessageInfo; + private final ImmutableMap protoJavaClassNameToMessageInfo; + + public static DefaultLiteDescriptorPool newInstance(ImmutableSet descriptors) { + return new DefaultLiteDescriptorPool(descriptors); + } + + @Override + public Optional findDescriptorByTypeName(String protoTypeName) { + return Optional.ofNullable(protoFqnToMessageInfo.get(protoTypeName)); + } + + @Override + public Optional findDescriptor(MessageLite msg) { + String className = msg.getClass().getName(); + return Optional.ofNullable(protoJavaClassNameToMessageInfo.get(className)); + } + + private static MessageLiteDescriptor newMessageInfo(WellKnownProto wellKnownProto) { + ImmutableMap.Builder fieldInfoMap = ImmutableMap.builder(); + switch (wellKnownProto) { + case JSON_STRUCT_VALUE: + fieldInfoMap.put( + "fields", + new FieldDescriptor( + "google.protobuf.Struct.fields", + "MESSAGE", + "Fields", + FieldDescriptor.CelFieldValueType.MAP.toString(), + FieldDescriptor.Type.MESSAGE.toString(), + String.valueOf(false), + "com.google.protobuf.Struct$FieldsEntry", + "google.protobuf.Struct.FieldsEntry")); + break; + case BOOL_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.BoolValue", + "BOOLEAN", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.BOOL)); + break; + case BYTES_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.BytesValue", + "BYTE_STRING", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.BYTES)); + break; + case DOUBLE_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.DoubleValue", + "DOUBLE", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.DOUBLE)); + break; + case FLOAT_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.FloatValue", + "FLOAT", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.FLOAT)); + break; + case INT32_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.Int32Value", + "INT", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.INT32)); + break; + case INT64_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.Int64Value", + "LONG", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.INT64)); + break; + case STRING_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.StringValue", + "STRING", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.STRING)); + break; + case UINT32_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.UInt32Value", + "INT", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.UINT32)); + break; + case UINT64_VALUE: + fieldInfoMap.put( + "value", + newPrimitiveFieldInfo( + "google.protobuf.UInt64Value", + "LONG", + FieldDescriptor.CelFieldValueType.SCALAR, + FieldDescriptor.Type.UINT64)); + break; + case JSON_VALUE: + case JSON_LIST_VALUE: + case DURATION: + case TIMESTAMP: + // TODO: Complete these + break; + default: + break; + } + + return new MessageLiteDescriptor( + wellKnownProto.typeName(), wellKnownProto.javaClassName(), fieldInfoMap.buildOrThrow()); + } + + private static FieldDescriptor newPrimitiveFieldInfo( + String fullyQualifiedProtoName, + String javaTypeName, + FieldDescriptor.CelFieldValueType valueType, + FieldDescriptor.Type protoFieldType) { + return new FieldDescriptor( + fullyQualifiedProtoName + ".value", + javaTypeName, + "Value", + valueType.toString(), + protoFieldType.toString(), + String.valueOf(false), + "", + fullyQualifiedProtoName); + } + + private DefaultLiteDescriptorPool(ImmutableSet descriptors) { + ImmutableMap.Builder protoFqnMapBuilder = ImmutableMap.builder(); + ImmutableMap.Builder protoJavaClassNameMapBuilder = + ImmutableMap.builder(); + for (WellKnownProto wellKnownProto : WellKnownProto.values()) { + MessageLiteDescriptor wktMessageInfo = newMessageInfo(wellKnownProto); + protoFqnMapBuilder.put(wellKnownProto.typeName(), wktMessageInfo); + protoJavaClassNameMapBuilder.put(wellKnownProto.javaClassName(), wktMessageInfo); + } + + for (CelLiteDescriptor descriptor : descriptors) { + protoFqnMapBuilder.putAll(descriptor.getProtoTypeNamesToDescriptors()); + protoJavaClassNameMapBuilder.putAll(descriptor.getProtoJavaClassNameToDescriptors()); + } + + this.protoFqnToMessageInfo = protoFqnMapBuilder.buildOrThrow(); + this.protoJavaClassNameToMessageInfo = protoJavaClassNameMapBuilder.buildOrThrow(); + } +} 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..a16abb8fc 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; @@ -45,6 +51,10 @@ public static String getFullyQualifiedJavaClassName(Descriptor descriptor) { return getFullyQualifiedJavaClassNameImpl(descriptor); } + public static String getFullyQualifiedJavaClassName(EnumDescriptor descriptor) { + return getFullyQualifiedJavaClassNameImpl(descriptor); + } + private static String getFullyQualifiedJavaClassNameImpl(GenericDescriptor descriptor) { StringBuilder fullClassName = new StringBuilder(); 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..c91fc430e 100644 --- a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java +++ b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java @@ -73,13 +73,9 @@ public enum WellKnownProto { FIELD_MASK("google.protobuf.FieldMask", FieldMask.class.getName(), /* isWrapperType= */ true), ; - private static final ImmutableMap WELL_KNOWN_PROTO_MAP; - - static { - WELL_KNOWN_PROTO_MAP = - stream(WellKnownProto.values()) - .collect(toImmutableMap(WellKnownProto::typeName, Function.identity())); - } + private static final ImmutableMap WELL_KNOWN_PROTO_MAP = + stream(WellKnownProto.values()) + .collect(toImmutableMap(WellKnownProto::typeName, Function.identity())); private final String wellKnownProtoFullName; private final String javaClassName; 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..7038253da 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,82 @@ 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:default_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:default_lite_descriptor_pool_android", + "//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: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..566bd4ea7 --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -0,0 +1,229 @@ +// 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.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.CelOptions; +import dev.cel.common.annotations.Internal; +import dev.cel.common.internal.WellKnownProto; +import java.time.Duration; +import java.time.Instant; + +/** + * {@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 { + + /** + * 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); + + 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(), celOptions.enableUnsignedLongs()); + case UINT64_VALUE: + return UintValue.create( + ((UInt64Value) message).getValue(), celOptions.enableUnsignedLongs()); + 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(CelOptions celOptions) { + super(celOptions); + } +} 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/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..14cd4fa81 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.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,7 +46,7 @@ */ @Immutable @Internal -public final class ProtoCelValueConverter extends CelValueConverter { +public final class ProtoCelValueConverter extends BaseProtoCelValueConverter { private final CelDescriptorPool celDescriptorPool; private final DynamicProto dynamicProto; @@ -76,27 +56,6 @@ public static ProtoCelValueConverter newInstance( 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); - } - /** Adapts a Protobuf message into a {@link CelValue}. */ public CelValue fromProtoMessageToCelValue(MessageOrBuilder message) { Preconditions.checkNotNull(message); @@ -122,41 +81,8 @@ 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); } } @@ -173,10 +99,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()); @@ -237,96 +159,6 @@ 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); 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..21e78fc86 --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java @@ -0,0 +1,157 @@ +// 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.primitives.UnsignedLong; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.google.protobuf.Internal.EnumLite; +import com.google.protobuf.MessageLite; +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Internal; +import dev.cel.common.internal.DefaultLiteDescriptorPool; +import dev.cel.common.internal.ReflectionUtil; +import dev.cel.common.internal.WellKnownProto; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.lang.reflect.Method; +import java.util.NoSuchElementException; +import java.util.Optional; + +/** + * {@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 DefaultLiteDescriptorPool descriptorPool; + + public static ProtoLiteCelValueConverter newInstance( + CelOptions celOptions, DefaultLiteDescriptorPool celLiteDescriptorPool) { + return new ProtoLiteCelValueConverter(celOptions, celLiteDescriptorPool); + } + + /** Adapts the protobuf message field into {@link CelValue}. */ + public CelValue fromProtoMessageFieldToCelValue(MessageLite msg, FieldDescriptor fieldInfo) { + checkNotNull(msg); + checkNotNull(fieldInfo); + + Method getterMethod = ReflectionUtil.getMethod(msg.getClass(), fieldInfo.getGetterName()); + Object fieldValue = ReflectionUtil.invoke(getterMethod, msg); + + switch (fieldInfo.getProtoFieldType()) { + case UINT32: + fieldValue = UnsignedLong.valueOf((int) fieldValue); + break; + case UINT64: + fieldValue = UnsignedLong.valueOf((long) fieldValue); + break; + default: + break; + } + + return fromJavaObjectToCelValue(fieldValue); + } + + @Override + public CelValue fromJavaObjectToCelValue(Object value) { + checkNotNull(value); + + if (value instanceof MessageLite) { + return fromProtoMessageToCelValue((MessageLite) value); + } else if (value instanceof MessageLite.Builder) { + return fromProtoMessageToCelValue(((MessageLite.Builder) value).build()); + } else if (value instanceof EnumLite) { + // Coerce proto enum values back into int + Method method = ReflectionUtil.getMethod(value.getClass(), "getNumber"); + value = ReflectionUtil.invoke(method, value); + } + + return super.fromJavaObjectToCelValue(value); + } + + public CelValue fromProtoMessageToCelValue(MessageLite msg) { + MessageLiteDescriptor messageInfo = + descriptorPool + .findDescriptor(msg) + .orElseThrow( + () -> + new NoSuchElementException( + "Could not find message info for class: " + msg.getClass())); + WellKnownProto wellKnownProto = + WellKnownProto.getByTypeName(messageInfo.getFullyQualifiedProtoTypeName()); + + if (wellKnownProto == null) { + return ProtoMessageLiteValue.create( + msg, messageInfo.getFullyQualifiedProtoTypeName(), descriptorPool, this); + } + + switch (wellKnownProto) { + case ANY_VALUE: + return unpackAnyMessage((Any) msg); + default: + return super.fromWellKnownProtoToCelValue(msg, wellKnownProto); + } + } + + private CelValue unpackAnyMessage(Any anyMsg) { + String typeUrl = + getTypeNameFromTypeUrl(anyMsg.getTypeUrl()) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format("malformed type URL: %s", anyMsg.getTypeUrl()))); + MessageLiteDescriptor messageInfo = + descriptorPool + .findDescriptorByTypeName(typeUrl) + .orElseThrow( + () -> + new NoSuchElementException( + "Could not find message info for any packed message's type name: " + + anyMsg)); + + Method method = + ReflectionUtil.getMethod( + messageInfo.getFullyQualifiedProtoJavaClassName(), "parseFrom", ByteString.class); + ByteString packedBytes = anyMsg.getValue(); + MessageLite unpackedMsg = (MessageLite) ReflectionUtil.invoke(method, null, packedBytes); + + return fromProtoMessageToCelValue(unpackedMsg); + } + + private static Optional getTypeNameFromTypeUrl(String typeUrl) { + int pos = typeUrl.lastIndexOf('/'); + if (pos != -1) { + return Optional.of(typeUrl.substring(pos + 1)); + } + return Optional.empty(); + } + + private ProtoLiteCelValueConverter( + CelOptions celOptions, DefaultLiteDescriptorPool celLiteDescriptorPool) { + super(celOptions); + 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..8f4dba468 --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java @@ -0,0 +1,128 @@ +// 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.common.base.Preconditions; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.MessageLite; +import dev.cel.common.internal.DefaultLiteDescriptorPool; +import dev.cel.common.internal.ReflectionUtil; +import dev.cel.common.internal.WellKnownProto; +import dev.cel.common.types.StructTypeReference; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.lang.reflect.Method; +import java.util.Optional; +import org.jspecify.annotations.Nullable; + +/** 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 DefaultLiteDescriptorPool descriptorPool(); + + abstract ProtoLiteCelValueConverter protoLiteCelValueConverter(); + + @Override + public boolean isZeroValue() { + return value().getDefaultInstanceForType().equals(value()); + } + + @Override + public CelValue select(StringValue field) { + MessageLiteDescriptor messageInfo = + descriptorPool().findDescriptorByTypeName(celType().name()).get(); + FieldDescriptor fieldInfo = messageInfo.getFieldInfoMap().get(field.value()); + if (fieldInfo.getProtoFieldType().equals(FieldDescriptor.Type.MESSAGE) + && WellKnownProto.isWrapperType(fieldInfo.getFieldProtoTypeName())) { + PresenceTestResult presenceTestResult = presenceTest(field); + // Special semantics for wrapper types per CEL spec. NullValue is returned instead of the + // default value for unset fields. + if (!presenceTestResult.hasPresence()) { + return NullValue.NULL_VALUE; + } + + return presenceTestResult.selectedValue().get(); + } + + return protoLiteCelValueConverter().fromProtoMessageFieldToCelValue(value(), fieldInfo); + } + + @Override + public Optional find(StringValue field) { + PresenceTestResult presenceTestResult = presenceTest(field); + + return presenceTestResult.selectedValue(); + } + + private PresenceTestResult presenceTest(StringValue field) { + MessageLiteDescriptor messageInfo = + descriptorPool().findDescriptorByTypeName(celType().name()).get(); + FieldDescriptor fieldInfo = messageInfo.getFieldInfoMap().get(field.value()); + CelValue selectedValue = null; + boolean presenceTestResult; + if (fieldInfo.getHasHasser()) { + Method hasserMethod = ReflectionUtil.getMethod(value().getClass(), fieldInfo.getHasserName()); + presenceTestResult = (boolean) ReflectionUtil.invoke(hasserMethod, value()); + } else { + // Lists, Maps and Opaque Values + selectedValue = + protoLiteCelValueConverter().fromProtoMessageFieldToCelValue(value(), fieldInfo); + presenceTestResult = !selectedValue.isZeroValue(); + } + + if (!presenceTestResult) { + return PresenceTestResult.create(null); + } + + if (selectedValue == null) { + selectedValue = + protoLiteCelValueConverter().fromProtoMessageFieldToCelValue(value(), fieldInfo); + } + + return PresenceTestResult.create(selectedValue); + } + + @AutoValue + abstract static class PresenceTestResult { + abstract boolean hasPresence(); + + abstract Optional selectedValue(); + + static PresenceTestResult create(@Nullable CelValue presentValue) { + Optional maybePresentValue = Optional.ofNullable(presentValue); + return new AutoValue_ProtoMessageLiteValue_PresenceTestResult( + maybePresentValue.isPresent(), maybePresentValue); + } + } + + public static ProtoMessageLiteValue create( + MessageLite value, + String protoFqn, + DefaultLiteDescriptorPool descriptorPool, + ProtoLiteCelValueConverter protoLiteCelValueConverter) { + Preconditions.checkNotNull(value); + return new AutoValue_ProtoMessageLiteValue( + value, StructTypeReference.create(protoFqn), 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..76ffb7f85 --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java @@ -0,0 +1,213 @@ +// 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.ImmutableMap.toImmutableMap; +import static java.util.Arrays.stream; + +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.Ints; +import com.google.common.primitives.UnsignedLong; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.Any; +import com.google.protobuf.Internal; +import com.google.protobuf.MessageLite; +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DefaultInstanceMessageLiteFactory; +import dev.cel.common.internal.DefaultLiteDescriptorPool; +import dev.cel.common.internal.ProtoLiteAdapter; +import dev.cel.common.internal.ReflectionUtil; +import dev.cel.common.internal.WellKnownProto; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Function; + +/** + * {@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 static final ImmutableMap CLASS_NAME_TO_WELL_KNOWN_PROTO_MAP; + private final ProtoLiteCelValueConverter protoLiteCelValueConverter; + private final DefaultLiteDescriptorPool descriptorPool; + private final ProtoLiteAdapter protoLiteAdapter; + + static { + CLASS_NAME_TO_WELL_KNOWN_PROTO_MAP = + stream(WellKnownProto.values()) + .collect(toImmutableMap(WellKnownProto::javaClassName, Function.identity())); + } + + @Override + public Optional newValue(String structType, Map fields) { + MessageLiteDescriptor messageInfo = + descriptorPool.findDescriptorByTypeName(structType).orElse(null); + + if (messageInfo == null) { + return Optional.empty(); + } + + MessageLite msg = + DefaultInstanceMessageLiteFactory.getInstance() + .getPrototype( + messageInfo.getFullyQualifiedProtoTypeName(), + messageInfo.getFullyQualifiedProtoJavaClassName()) + .orElse(null); + + if (msg == null) { + return Optional.empty(); + } + + MessageLite.Builder msgBuilder = msg.toBuilder(); + for (Map.Entry entry : fields.entrySet()) { + FieldDescriptor fieldInfo = messageInfo.getFieldInfoMap().get(entry.getKey()); + + Method setterMethod = + ReflectionUtil.getMethod( + msgBuilder.getClass(), fieldInfo.getSetterName(), fieldInfo.getFieldJavaClass()); + Object newFieldValue = + adaptToProtoFieldCompatibleValue( + entry.getValue(), fieldInfo, setterMethod.getParameters()[0]); + msgBuilder = + (MessageLite.Builder) ReflectionUtil.invoke(setterMethod, msgBuilder, newFieldValue); + } + + return Optional.of(protoLiteCelValueConverter.fromProtoMessageToCelValue(msgBuilder.build())); + } + + private Object adaptToProtoFieldCompatibleValue( + Object value, FieldDescriptor fieldInfo, Parameter parameter) { + Class parameterType = parameter.getType(); + if (parameterType.isAssignableFrom(Iterable.class)) { + ParameterizedType listParamType = (ParameterizedType) parameter.getParameterizedType(); + Class listParamActualTypeClass = + getActualTypeClass(listParamType.getActualTypeArguments()[0]); + + List copiedList = new ArrayList<>(); + for (Object element : (Iterable) value) { + copiedList.add( + adaptToProtoFieldCompatibleValueImpl(element, fieldInfo, listParamActualTypeClass)); + } + return copiedList; + } else if (parameterType.isAssignableFrom(Map.class)) { + ParameterizedType mapParamType = (ParameterizedType) parameter.getParameterizedType(); + Class keyActualType = getActualTypeClass(mapParamType.getActualTypeArguments()[0]); + Class valueActualType = getActualTypeClass(mapParamType.getActualTypeArguments()[1]); + + Map copiedMap = new LinkedHashMap<>(); + for (Map.Entry entry : ((Map) value).entrySet()) { + Object adaptedKey = + adaptToProtoFieldCompatibleValueImpl(entry.getKey(), fieldInfo, keyActualType); + Object adaptedValue = + adaptToProtoFieldCompatibleValueImpl(entry.getValue(), fieldInfo, valueActualType); + copiedMap.put(adaptedKey, adaptedValue); + } + return copiedMap; + } + + return adaptToProtoFieldCompatibleValueImpl(value, fieldInfo, parameter.getType()); + } + + private Object adaptToProtoFieldCompatibleValueImpl( + Object value, FieldDescriptor fieldInfo, Class parameterType) { + WellKnownProto wellKnownProto = CLASS_NAME_TO_WELL_KNOWN_PROTO_MAP.get(parameterType.getName()); + if (wellKnownProto != null) { + switch (wellKnownProto) { + case ANY_VALUE: + String typeUrl = fieldInfo.getFieldProtoTypeName(); + if (value instanceof MessageLite) { + MessageLite messageLite = (MessageLite) value; + typeUrl = + descriptorPool + .findDescriptor(messageLite) + .orElseThrow( + () -> + new NoSuchElementException( + "Could not find message info for class: " + messageLite.getClass())) + .getFullyQualifiedProtoTypeName(); + } + return protoLiteAdapter.adaptValueToAny(value, typeUrl); + default: + return protoLiteAdapter.adaptValueToWellKnownProto(value, wellKnownProto); + } + } + + if (value instanceof UnsignedLong) { + value = ((UnsignedLong) value).longValue(); + } + + if (parameterType.equals(int.class) || parameterType.equals(Integer.class)) { + return intCheckedCast((long) value); + } else if (parameterType.equals(float.class) || parameterType.equals(Float.class)) { + return ((Double) value).floatValue(); + } else if (Internal.EnumLite.class.isAssignableFrom(parameterType)) { + // CEL coerces enums into int. We need to adapt it back into an actual proto enum. + Method method = ReflectionUtil.getMethod(parameterType, "forNumber", int.class); + return ReflectionUtil.invoke(method, null, intCheckedCast((long) value)); + } else if (parameterType.equals(Any.class)) { + return protoLiteAdapter.adaptValueToAny(value, fieldInfo.getFullyQualifiedProtoFieldName()); + } + + return value; + } + + private static int intCheckedCast(long value) { + try { + return Ints.checkedCast(value); + } catch (IllegalArgumentException e) { + throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + } + } + + private static Class getActualTypeClass(Type paramType) { + if (paramType instanceof WildcardType) { + return (Class) ((WildcardType) paramType).getUpperBounds()[0]; + } + + return (Class) paramType; + } + + public static ProtoMessageLiteValueProvider newInstance( + ProtoLiteCelValueConverter protoLiteCelValueConverter, + ProtoLiteAdapter protoLiteAdapter, + DefaultLiteDescriptorPool celLiteDescriptorPool) { + return new ProtoMessageLiteValueProvider( + protoLiteCelValueConverter, protoLiteAdapter, celLiteDescriptorPool); + } + + private ProtoMessageLiteValueProvider( + ProtoLiteCelValueConverter protoLiteCelValueConverter, + ProtoLiteAdapter protoLiteAdapter, + DefaultLiteDescriptorPool celLiteDescriptorPool) { + this.protoLiteCelValueConverter = protoLiteCelValueConverter; + this.descriptorPool = celLiteDescriptorPool; + this.protoLiteAdapter = protoLiteAdapter; + } +} 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/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/ProtoMessageValueProviderTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java index 2ce416053..ccd0f10c0 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java @@ -247,7 +247,7 @@ public void newValue_onCombinedProvider() { ProtoMessageValueProvider protoMessageValueProvider = ProtoMessageValueProvider.newInstance(DYNAMIC_PROTO, CelOptions.DEFAULT); CelValueProvider combinedProvider = - new CombinedCelValueProvider(celValueProvider, protoMessageValueProvider); + CombinedCelValueProvider.newInstance(celValueProvider, protoMessageValueProvider); ProtoMessageValue protoMessageValue = (ProtoMessageValue) 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..2f7f6a876 --- /dev/null +++ b/java_lite_proto_cel_library.bzl @@ -0,0 +1,99 @@ +# 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("@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, + ) + + descriptor_codegen_deps = [ + "//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..b4c367854 --- /dev/null +++ b/protobuf/BUILD.bazel @@ -0,0 +1,19 @@ +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"], +) + +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..40647206e --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel @@ -0,0 +1,78 @@ +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", + 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", + "//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", + "//: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", + ], +) 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..4dd175554 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java @@ -0,0 +1,419 @@ +// 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.ByteString; +import dev.cel.common.annotations.Internal; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 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; + + @SuppressWarnings("Immutable") // Copied to unmodifiable map + private final Map protoJavaClassNameToDescriptors; + + public Map getProtoTypeNamesToDescriptors() { + return protoFqnToDescriptors; + } + + public Map getProtoJavaClassNameToDescriptors() { + return protoJavaClassNameToDescriptors; + } + + /** + * 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; + private final String fullyQualifiedProtoJavaClassName; + + @SuppressWarnings("Immutable") // Copied to unmodifiable map + private final Map fieldInfoMap; + + public String getFullyQualifiedProtoTypeName() { + return fullyQualifiedProtoTypeName; + } + + public String getFullyQualifiedProtoJavaClassName() { + return fullyQualifiedProtoJavaClassName; + } + + public Map getFieldInfoMap() { + return fieldInfoMap; + } + + public MessageLiteDescriptor( + String fullyQualifiedProtoTypeName, + String fullyQualifiedProtoJavaClassName, + Map fieldInfoMap) { + this.fullyQualifiedProtoTypeName = checkNotNull(fullyQualifiedProtoTypeName); + this.fullyQualifiedProtoJavaClassName = checkNotNull(fullyQualifiedProtoJavaClassName); + // This is a cheap operation. View over the existing map with mutators disabled. + this.fieldInfoMap = checkNotNull(Collections.unmodifiableMap(fieldInfoMap)); + } + } + + /** + * Describes a field of a protobuf messagelite type. + * + *

CEL Library Internals. Do Not Use. + */ + @Internal + @Immutable + public static final class FieldDescriptor { + private final JavaType javaType; + private final String fieldJavaClassName; + private final String fieldProtoTypeName; + private final String fullyQualifiedProtoFieldName; + private final String methodSuffixName; + private final Type protoFieldType; + private final CelFieldValueType celFieldValueType; + private final boolean hasHasser; + + /** + * 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 + } + + // Lazily-loaded field + @SuppressWarnings("Immutable") + private volatile Class fieldJavaClass; + + /** + * Returns the {@link Class} object for this field. In case of protobuf messages, the class + * object is lazily loaded then memoized. + */ + public Class getFieldJavaClass() { + if (fieldJavaClass == null) { + synchronized (this) { + if (fieldJavaClass == null) { + fieldJavaClass = loadNonPrimitiveFieldTypeClass(); + } + } + } + return fieldJavaClass; + } + + /** + * Gets the field's java type. + * + *

This is exactly the same as com.google.protobuf.Descriptors#JavaType + */ + public JavaType getJavaType() { + return javaType; + } + + /** + * Returns the method suffix name as part of getters or setters of the field in the protobuf + * message's builder. (Ex: for a field named single_string, "SingleString" is returned). + */ + public String getMethodSuffixName() { + return methodSuffixName; + } + + /** + * Returns the setter name for the field used in protobuf message's builder (Ex: + * setSingleString). + */ + public String getSetterName() { + String prefix = ""; + switch (celFieldValueType) { + case SCALAR: + prefix = "set"; + break; + case LIST: + prefix = "addAll"; + break; + case MAP: + prefix = "putAll"; + break; + } + return prefix + getMethodSuffixName(); + } + + /** + * Returns the getter name for the field used in protobuf message's builder (Ex: + * getSingleString). + */ + public String getGetterName() { + String suffix = ""; + switch (celFieldValueType) { + case SCALAR: + break; + case LIST: + suffix = "List"; + break; + case MAP: + suffix = "Map"; + break; + } + return "get" + getMethodSuffixName() + suffix; + } + + /** + * Returns the hasser name for the field (Ex: hasSingleString). + * + * @throws IllegalArgumentException If the message does not have a hasser. + */ + public String getHasserName() { + if (!getHasHasser()) { + throw new IllegalArgumentException("This message does not have a hasser."); + } + return "has" + getMethodSuffixName(); + } + + /** + * Returns the fully qualified java class name for the underlying field. (Ex: + * com.google.protobuf.StringValue). Returns an empty string for primitives . + */ + public String getFieldJavaClassName() { + return fieldJavaClassName; + } + + 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; + } + + public boolean getHasHasser() { + return hasHasser && celFieldValueType.equals(CelFieldValueType.SCALAR); + } + + /** + * Gets the fully qualified protobuf message field name, including its package name (ex: + * cel.expr.conformance.proto3.TestAllTypes.single_string) + */ + public String getFullyQualifiedProtoFieldName() { + return fullyQualifiedProtoFieldName; + } + + /** + * 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 fullyQualifiedProtoTypeName Fully qualified protobuf type name including the namespace + * (ex: cel.expr.conformance.proto3.TestAllTypes) + * @param javaTypeName Canonical Java type name (ex: Long, Double, Float, Message... see + * Descriptors#JavaType) + * @param methodSuffixName Suffix used to decorate the getters/setters (eg: "foo" in "setFoo" + * and "getFoo") + * @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 + * Descriptors#Type) + * @param hasHasser True if the message has a presence test method (ex: wrappers). + * @param fieldJavaClassName Fully qualified Java class name for the field, including its + * package name. Empty if the field is a primitive. + * @param fieldProtoTypeName Fully qualified protobuf type name for the field. Empty if the + * field is a primitive. + */ + @Internal + public FieldDescriptor( + String fullyQualifiedProtoTypeName, + String javaTypeName, + String methodSuffixName, + String celFieldValueType, // LIST, MAP, SCALAR + String protoFieldType, // INT32, SINT32, GROUP, MESSAGE... (See Descriptors#Type) + String hasHasser, // + String fieldJavaClassName, + String fieldProtoTypeName) { + this.fullyQualifiedProtoFieldName = checkNotNull(fullyQualifiedProtoTypeName); + this.javaType = JavaType.valueOf(javaTypeName); + this.methodSuffixName = checkNotNull(methodSuffixName); + this.fieldJavaClassName = checkNotNull(fieldJavaClassName); + this.celFieldValueType = CelFieldValueType.valueOf(checkNotNull(celFieldValueType)); + this.protoFieldType = Type.valueOf(protoFieldType); + this.hasHasser = Boolean.parseBoolean(hasHasser); + this.fieldProtoTypeName = checkNotNull(fieldProtoTypeName); + this.fieldJavaClass = getPrimitiveFieldTypeClass(); + } + + @SuppressWarnings("ReturnMissingNullable") // Avoid taking a dependency on jspecify.nullable. + private Class getPrimitiveFieldTypeClass() { + switch (celFieldValueType) { + case LIST: + return Iterable.class; + case MAP: + return Map.class; + case SCALAR: + return getScalarFieldTypeClass(); + } + + throw new IllegalStateException("Unexpected celFieldValueType: " + celFieldValueType); + } + + @SuppressWarnings("ReturnMissingNullable") // Avoid taking a dependency on jspecify.nullable. + private Class getScalarFieldTypeClass() { + switch (javaType) { + case INT: + return int.class; + case LONG: + return long.class; + case FLOAT: + return float.class; + case DOUBLE: + return double.class; + case BOOLEAN: + return boolean.class; + case STRING: + return String.class; + case BYTE_STRING: + return ByteString.class; + default: + // Non-primitives must be lazily loaded during instantiation of the runtime environment, + // where the generated messages are linked into the binary via java_lite_proto_library. + return null; + } + } + + private Class loadNonPrimitiveFieldTypeClass() { + if (!javaType.equals(JavaType.ENUM) && !javaType.equals(JavaType.MESSAGE)) { + throw new IllegalArgumentException("Unexpected java type name for " + javaType); + } + + try { + return Class.forName(fieldJavaClassName); + } catch (ClassNotFoundException e) { + throw new LinkageError(String.format("Could not find class %s", fieldJavaClassName), e); + } + } + } + + protected CelLiteDescriptor(List messageInfoList) { + Map protoFqnMap = + new HashMap<>(getMapInitialCapacity(messageInfoList.size())); + Map protoJavaClassNameMap = + new HashMap<>(getMapInitialCapacity(messageInfoList.size())); + for (MessageLiteDescriptor msgInfo : messageInfoList) { + protoFqnMap.put(msgInfo.getFullyQualifiedProtoTypeName(), msgInfo); + protoJavaClassNameMap.put(msgInfo.getFullyQualifiedProtoJavaClassName(), msgInfo); + } + + this.protoFqnToDescriptors = Collections.unmodifiableMap(protoFqnMap); + this.protoJavaClassNameToDescriptors = Collections.unmodifiableMap(protoJavaClassNameMap); + } + + /** + * 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..2627da82d --- /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) + .setMessageInfoList(descriptorCollector.collectMessageInfo(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..ff6966a69 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java @@ -0,0 +1,96 @@ +// 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 dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +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 messageInfoList(); + + ImmutableMap getTemplateMap() { + return ImmutableMap.of( + "package_name", packageName(), + "descriptor_class_name", descriptorClassName(), + "version", version(), + "message_info_list", messageInfoList()); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setPackageName(String packageName); + + abstract Builder setDescriptorClassName(String className); + + abstract Builder setVersion(String version); + + abstract Builder setMessageInfoList(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/ProtoDescriptorCollector.java b/protobuf/src/main/java/dev/cel/protobuf/ProtoDescriptorCollector.java new file mode 100644 index 000000000..6ffa414e3 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/ProtoDescriptorCollector.java @@ -0,0 +1,129 @@ +// 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.collect.ImmutableSet.toImmutableSet; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.protobuf.Descriptors; +import com.google.protobuf.Descriptors.Descriptor; +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.FieldDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor.CelFieldValueType; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; + +/** + * ProtoDescriptorCollector inspects a {@link FileDescriptor} to collect message information into + * {@link MessageLiteDescriptor}. + */ +final class ProtoDescriptorCollector { + + private final DebugPrinter debugPrinter; + + ImmutableList collectMessageInfo(FileDescriptor targetFileDescriptor) { + ImmutableList.Builder messageInfoListBuilder = ImmutableList.builder(); + CelDescriptors celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + ImmutableList.of(targetFileDescriptor), /* resolveTypeDependencies= */ false); + ImmutableSet messageTypes = + celDescriptors.messageTypeDescriptors().stream() + .filter(d -> WellKnownProto.getByTypeName(d.getFullName()) == null) + .collect(toImmutableSet()); + + for (Descriptor descriptor : messageTypes) { + ImmutableMap.Builder fieldMap = ImmutableMap.builder(); + for (Descriptors.FieldDescriptor fieldDescriptor : descriptor.getFields()) { + String methodSuffixName = + CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, fieldDescriptor.getName()); + + String javaType = fieldDescriptor.getJavaType().toString(); + String embeddedFieldJavaClassName = ""; + String embeddedFieldProtoTypeName = ""; + switch (javaType) { + case "ENUM": + embeddedFieldJavaClassName = + ProtoJavaQualifiedNames.getFullyQualifiedJavaClassName( + fieldDescriptor.getEnumType()); + embeddedFieldProtoTypeName = fieldDescriptor.getEnumType().getFullName(); + break; + case "MESSAGE": + embeddedFieldJavaClassName = + ProtoJavaQualifiedNames.getFullyQualifiedJavaClassName( + fieldDescriptor.getMessageType()); + embeddedFieldProtoTypeName = fieldDescriptor.getMessageType().getFullName(); + break; + default: + break; + } + + CelFieldValueType fieldValueType; + if (fieldDescriptor.isMapField()) { + fieldValueType = CelFieldValueType.MAP; + } else if (fieldDescriptor.isRepeated()) { + fieldValueType = CelFieldValueType.LIST; + } else { + fieldValueType = CelFieldValueType.SCALAR; + } + + fieldMap.put( + fieldDescriptor.getName(), + new FieldDescriptor( + /* fullyQualifiedProtoTypeName= */ fieldDescriptor.getFullName(), + /* javaTypeName= */ javaType, + /* methodSuffixName= */ methodSuffixName, + /* celFieldValueType= */ fieldValueType.toString(), + /* protoFieldType= */ fieldDescriptor.getType().toString(), + /* hasHasser= */ String.valueOf(fieldDescriptor.hasPresence()), + /* fieldJavaClassName= */ embeddedFieldJavaClassName, + /* fieldProtoTypeName= */ embeddedFieldProtoTypeName)); + + debugPrinter.print( + String.format( + "Method suffix name in %s, for field %s: %s", + descriptor.getFullName(), fieldDescriptor.getFullName(), methodSuffixName)); + debugPrinter.print(String.format("FieldType: %s", fieldValueType)); + if (!embeddedFieldJavaClassName.isEmpty()) { + debugPrinter.print( + String.format( + "Java class name for field %s: %s", + fieldDescriptor.getName(), embeddedFieldJavaClassName)); + } + } + + messageInfoListBuilder.add( + new MessageLiteDescriptor( + descriptor.getFullName(), + ProtoJavaQualifiedNames.getFullyQualifiedJavaClassName(descriptor), + fieldMap.buildOrThrow())); + } + + return messageInfoListBuilder.build(); + } + + static ProtoDescriptorCollector newInstance(DebugPrinter debugPrinter) { + return new ProtoDescriptorCollector(debugPrinter); + } + + 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..9c65878d1 --- /dev/null +++ b/protobuf/src/main/java/dev/cel/protobuf/templates/cel_lite_descriptor_template.txt @@ -0,0 +1,70 @@ +// 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<>(${message_info_list?size}); + Map fieldDescriptors; + <#list message_info_list as message_info> + + fieldDescriptors = new HashMap<>(${message_info.fieldInfoMap?size}); + <#list message_info.fieldInfoMap as key, value> + fieldDescriptors.put("${key}", new FieldDescriptor( + "${value.fullyQualifiedProtoFieldName}", + "${value.javaType}", + "${value.methodSuffixName}", + "${value.celFieldValueType}", + "${value.protoFieldType}", + "${value.hasHasser}", + "${value.fieldJavaClassName}", + "${value.fieldProtoTypeName}" + )); + + + descriptors.add( + new MessageLiteDescriptor( + "${message_info.fullyQualifiedProtoTypeName}", + "${message_info.fullyQualifiedProtoJavaClassName}", + Collections.unmodifiableMap(fieldDescriptors)) + ); + + + return Collections.unmodifiableList(descriptors); + } + + private ${descriptor_class_name}() { + super(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..6a2a67dd2 --- /dev/null +++ b/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_java//java:defs.bzl", "java_library") +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_library( + name = "cel_lite_descriptor_test", + testonly = 1, + srcs = ["CelLiteDescriptorTest.java"], + 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", + ], +) + +junit4_test_suites( + name = "test_suites_proto_lite", + sizes = [ + "small", + ], + src_dir = "src/test/java", + deps = [ + ":cel_lite_descriptor_test", + ], +) 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..03a93b07b --- /dev/null +++ b/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java @@ -0,0 +1,344 @@ +// 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.protobuf.CodedInputStream; +import com.google.protobuf.WireFormat; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor.CelFieldValueType; +import dev.cel.protobuf.CelLiteDescriptor.FieldDescriptor.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).hasSize(3); + 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 getDescriptors_fromProtoTypeAndJavaClassNames_referenceEquals() { + Map protoNamesToDescriptors = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR.getProtoTypeNamesToDescriptors(); + Map javaClassNamesToDescriptors = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR.getProtoJavaClassNameToDescriptors(); + + assertThat(protoNamesToDescriptors.get("cel.expr.conformance.proto3.TestAllTypes")) + .isSameInstanceAs( + javaClassNamesToDescriptors.get("dev.cel.expr.conformance.proto3.TestAllTypes")); + assertThat( + protoNamesToDescriptors.get("cel.expr.conformance.proto3.TestAllTypes.NestedMessage")) + .isSameInstanceAs( + javaClassNamesToDescriptors.get( + "dev.cel.expr.conformance.proto3.TestAllTypes$NestedMessage")); + assertThat(protoNamesToDescriptors.get("cel.expr.conformance.proto3.NestedTestAllTypes")) + .isSameInstanceAs( + javaClassNamesToDescriptors.get("dev.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.getFullyQualifiedProtoTypeName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes"); + assertThat(testAllTypesDescriptor.getFullyQualifiedProtoJavaClassName()) + .isEqualTo("dev.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.getFieldInfoMap()).hasSize(243); + } + + @Test + public void fieldDescriptor_scalarField() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = testAllTypesDescriptor.getFieldInfoMap().get("single_string"); + + assertThat(fieldDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.SCALAR); + assertThat(fieldDescriptor.getJavaType()).isEqualTo(JavaType.STRING); + assertThat(fieldDescriptor.getProtoFieldType()).isEqualTo(FieldDescriptor.Type.STRING); + } + + @Test + public void fieldDescriptor_primitiveField_fullyQualifiedNames() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = testAllTypesDescriptor.getFieldInfoMap().get("single_string"); + + assertThat(fieldDescriptor.getFullyQualifiedProtoFieldName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes.single_string"); + assertThat(fieldDescriptor.getFieldProtoTypeName()).isEmpty(); + } + + @Test + public void fieldDescriptor_primitiveField_getFieldJavaClass() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = testAllTypesDescriptor.getFieldInfoMap().get("single_string"); + + assertThat(fieldDescriptor.getFieldJavaClass()).isEqualTo(String.class); + assertThat(fieldDescriptor.getFieldJavaClassName()).isEmpty(); + } + + @Test + public void fieldDescriptor_scalarField_builderMethods() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = testAllTypesDescriptor.getFieldInfoMap().get("single_string"); + + assertThat(fieldDescriptor.getHasHasser()).isFalse(); + assertThat(fieldDescriptor.getGetterName()).isEqualTo("getSingleString"); + assertThat(fieldDescriptor.getSetterName()).isEqualTo("setSingleString"); + } + + @Test + public void fieldDescriptor_getHasserName_throwsIfNotWrapper() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = testAllTypesDescriptor.getFieldInfoMap().get("single_string"); + + assertThrows(IllegalArgumentException.class, fieldDescriptor::getHasserName); + } + + @Test + public void fieldDescriptor_getHasserName_success() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("single_string_wrapper"); + + assertThat(fieldDescriptor.getHasHasser()).isTrue(); + assertThat(fieldDescriptor.getHasserName()).isEqualTo("hasSingleStringWrapper"); + } + + @Test + public void fieldDescriptor_mapField() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("map_bool_string"); + + assertThat(fieldDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.MAP); + assertThat(fieldDescriptor.getJavaType()).isEqualTo(JavaType.MESSAGE); + assertThat(fieldDescriptor.getProtoFieldType()).isEqualTo(FieldDescriptor.Type.MESSAGE); + } + + @Test + public void fieldDescriptor_mapField_builderMethods() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("map_bool_string"); + + assertThat(fieldDescriptor.getHasHasser()).isFalse(); + assertThat(fieldDescriptor.getGetterName()).isEqualTo("getMapBoolStringMap"); + assertThat(fieldDescriptor.getSetterName()).isEqualTo("putAllMapBoolString"); + } + + @Test + public void fieldDescriptor_mapField_getFieldJavaClass() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("map_bool_string"); + + assertThat(fieldDescriptor.getFieldJavaClass()).isEqualTo(Map.class); + assertThat(fieldDescriptor.getFieldJavaClassName()) + .isEqualTo("dev.cel.expr.conformance.proto3.TestAllTypes$MapBoolStringEntry"); + } + + @Test + public void fieldDescriptor_repeatedField() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("repeated_int64"); + + assertThat(fieldDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.LIST); + assertThat(fieldDescriptor.getJavaType()).isEqualTo(JavaType.LONG); + assertThat(fieldDescriptor.getProtoFieldType()).isEqualTo(FieldDescriptor.Type.INT64); + } + + @Test + public void fieldDescriptor_repeatedField_builderMethods() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("repeated_int64"); + + assertThat(fieldDescriptor.getHasHasser()).isFalse(); + assertThat(fieldDescriptor.getGetterName()).isEqualTo("getRepeatedInt64List"); + assertThat(fieldDescriptor.getSetterName()).isEqualTo("addAllRepeatedInt64"); + } + + @Test + public void fieldDescriptor_repeatedField_primitives_getFieldJavaClass() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("repeated_int64"); + + assertThat(fieldDescriptor.getFieldJavaClass()).isEqualTo(Iterable.class); + assertThat(fieldDescriptor.getFieldJavaClassName()).isEmpty(); + } + + @Test + public void fieldDescriptor_repeatedField_wrappers_getFieldJavaClass() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("repeated_double_wrapper"); + + assertThat(fieldDescriptor.getFieldJavaClass()).isEqualTo(Iterable.class); + assertThat(fieldDescriptor.getFieldJavaClassName()) + .isEqualTo("com.google.protobuf.DoubleValue"); + } + + @Test + public void fieldDescriptor_nestedMessage() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("standalone_message"); + + assertThat(fieldDescriptor.getCelFieldValueType()).isEqualTo(CelFieldValueType.SCALAR); + assertThat(fieldDescriptor.getJavaType()).isEqualTo(JavaType.MESSAGE); + assertThat(fieldDescriptor.getProtoFieldType()).isEqualTo(FieldDescriptor.Type.MESSAGE); + } + + @Test + public void fieldDescriptor_nestedMessage_getFieldJavaClass() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("standalone_message"); + + assertThat(fieldDescriptor.getFieldJavaClass()).isEqualTo(TestAllTypes.NestedMessage.class); + assertThat(fieldDescriptor.getFieldJavaClassName()) + .isEqualTo("dev.cel.expr.conformance.proto3.TestAllTypes$NestedMessage"); + } + + @Test + public void fieldDescriptor_nestedMessage_fullyQualifiedNames() { + MessageLiteDescriptor testAllTypesDescriptor = + TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR + .getProtoTypeNamesToDescriptors() + .get("cel.expr.conformance.proto3.TestAllTypes"); + FieldDescriptor fieldDescriptor = + testAllTypesDescriptor.getFieldInfoMap().get("standalone_message"); + + assertThat(fieldDescriptor.getFullyQualifiedProtoFieldName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes.standalone_message"); + assertThat(fieldDescriptor.getFieldProtoTypeName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes.NestedMessage"); + } + + @Test + public void smokeTest() throws Exception { + TestAllTypes testAllTypes = + TestAllTypes.newBuilder().setSingleBool(true).setSingleString("foo").build(); + byte[] bytes = testAllTypes.toByteArray(); + CodedInputStream inputStream = CodedInputStream.newInstance(bytes); + while (true) { + int tag = inputStream.readTag(); + if (tag == 0) { + break; + } + + int fieldType = WireFormat.getTagWireType(tag); + Object payload = null; + switch (fieldType) { + case WireFormat.WIRETYPE_VARINT: + payload = inputStream.readInt64(); + break; + case WireFormat.WIRETYPE_FIXED32: + payload = inputStream.readRawLittleEndian32(); + break; + case WireFormat.WIRETYPE_FIXED64: + payload = inputStream.readRawLittleEndian64(); + break; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + payload = inputStream.readBytes(); + break; + } + System.out.println(payload); + + int fieldNumber = WireFormat.getTagFieldNumber(tag); + System.out.println(fieldNumber); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index e4c4dbbc5..7fb16b0da 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", ], ) @@ -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", @@ -715,15 +722,18 @@ java_library( ":runtime_equality", ":runtime_helpers", ":runtime_type_provider", + ":runtime_type_provider_legacy", ":standard_functions", ":type_resolver", "//:auto_value", "//common:cel_ast", "//common:options", + "//common/internal:default_lite_descriptor_pool", + "//common/values:cel_value_provider", + "//common/values:proto_message_lite_value", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", ], ) @@ -743,15 +753,18 @@ cel_android_library( ":runtime_equality_android", ":runtime_helpers_android", ":runtime_type_provider_android", + ":runtime_type_provider_legacy_android", ":standard_functions_android", ":type_resolver_android", "//: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", "@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", ], ) @@ -844,20 +857,34 @@ java_library( ":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", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) +cel_android_library( + name = "runtime_type_provider_legacy_android", + srcs = ["RuntimeTypeProviderLegacyImpl.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:values_android", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "interpreter_util", srcs = ["InterpreterUtil.java"], @@ -921,6 +948,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..edf91d2c5 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -36,13 +36,20 @@ 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.ProtoLiteAdapter; 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 +137,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 +178,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 +310,42 @@ 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, options); + if (celValueProvider != null) { + messageValueProvider = + CombinedCelValueProvider.newInstance(celValueProvider, messageValueProvider); + } + + ProtoCelValueConverter protoCelValueConverter = + ProtoCelValueConverter.newInstance(options, celDescriptorPool, dynamicProto); + + runtimeTypeProvider = + new RuntimeTypeProviderLegacyImpl(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. + ProtoLiteAdapter protoLiteAdapter = new ProtoLiteAdapter(options.enableUnsignedLongs()); + ProtoLiteCelValueConverter protoLiteCelValueConverter = + ProtoLiteCelValueConverter.newInstance(options, celLiteDescriptorPool); + CelValueProvider messageValueProvider = + ProtoMessageLiteValueProvider.newInstance( + protoLiteCelValueConverter, protoLiteAdapter, celLiteDescriptorPool); + if (celValueProvider != null) { + messageValueProvider = + CombinedCelValueProvider.newInstance(celValueProvider, messageValueProvider); + } + + runtimeTypeProvider = + new RuntimeTypeProviderLegacyImpl(messageValueProvider, protoLiteCelValueConverter); } - runtimeTypeProvider = - new RuntimeTypeProviderLegacyImpl( - options, messageValueProvider, celDescriptorPool, dynamicProto); } else { runtimeTypeProvider = new DescriptorMessageProvider(runtimeTypeFactory, options); } @@ -407,6 +452,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/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 5a16d616a..1304ff78c 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -21,12 +21,14 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import javax.annotation.concurrent.ThreadSafe; -import com.google.protobuf.MessageLiteOrBuilder; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; +import dev.cel.common.internal.DefaultLiteDescriptorPool; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.ProtoLiteCelValueConverter; import java.util.Arrays; import java.util.HashMap; -import java.util.Map; +import java.util.Optional; @ThreadSafe final class LiteRuntimeImpl implements CelLiteRuntime { @@ -55,6 +57,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 +82,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 +146,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: Propagate descriptor through value provider? + DefaultLiteDescriptorPool celLiteDescriptorPool = + DefaultLiteDescriptorPool.newInstance(ImmutableSet.of()); + ProtoLiteCelValueConverter protoLiteCelValueConverter = + ProtoLiteCelValueConverter.newInstance(celOptions, celLiteDescriptorPool); + + RuntimeTypeProvider runtimeTypeProvider = + new RuntimeTypeProviderLegacyImpl(valueProvider, protoLiteCelValueConverter); + 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/RuntimeTypeProviderLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java index 36bd054f3..ac9669bb3 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java @@ -14,17 +14,13 @@ 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.BaseProtoCelValueConverter; 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; @@ -33,20 +29,15 @@ /** Bridge between the old RuntimeTypeProvider and CelValueProvider APIs. */ @Internal @Immutable -public final class RuntimeTypeProviderLegacyImpl implements RuntimeTypeProvider { +final class RuntimeTypeProviderLegacyImpl implements RuntimeTypeProvider { private final CelValueProvider valueProvider; - private final ProtoCelValueConverter protoCelValueConverter; + private final BaseProtoCelValueConverter protoCelValueConverter; - @VisibleForTesting - public RuntimeTypeProviderLegacyImpl( - CelOptions celOptions, - CelValueProvider valueProvider, - CelDescriptorPool celDescriptorPool, - DynamicProto dynamicProto) { + RuntimeTypeProviderLegacyImpl( + CelValueProvider valueProvider, BaseProtoCelValueConverter protoCelValueConverter) { this.valueProvider = valueProvider; - this.protoCelValueConverter = - ProtoCelValueConverter.newInstance(celOptions, celDescriptorPool, dynamicProto); + this.protoCelValueConverter = protoCelValueConverter; } @Override diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 0f568b720..9070bc180 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") @@ -60,6 +61,7 @@ java_library( ["*.java"], exclude = [ "CelValueInterpreterTest.java", + "CelLiteDescriptorInterpreterTest.java", "InterpreterTest.java", ], ), @@ -113,6 +115,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 +128,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", ], ) @@ -176,13 +180,28 @@ cel_android_local_test( "//runtime:lite_runtime_impl_android", "//runtime:standard_functions_android", "@cel_spec//proto/cel/expr:checked_java_proto_lite", - "@maven//:com_google_protobuf_protobuf_java", "@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 +211,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..7deba0c30 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorEvaluationTest.java @@ -0,0 +1,384 @@ +// 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 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.FloatValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; +import com.google.protobuf.NullValue; +import com.google.protobuf.StringValue; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelOptions; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import dev.cel.expr.conformance.proto3.NestedTestAllTypes; +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.Arrays; +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 CelRuntime CEL_RUNTIME = + CelRuntimeFactory.standardCelRuntimeBuilder() + .setOptions(CelOptions.current().enableCelValue(true).build()) + .addCelLiteDescriptors(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," + + "single_int64: 6," + + "single_float: 7.1," + + "single_double: 8.2," + + "single_nested_enum: TestAllTypes.NestedEnum.BAR," + + "repeated_int32: [1,2]," + + "repeated_int64: [3,4]," + + "map_string_int32: {'a': 1}," + + "map_string_int64: {'b': 2}," + + "single_int32_wrapper: google.protobuf.Int32Value{value: 9}," + + "single_int64_wrapper: google.protobuf.Int64Value{value: 10}," + + "single_float_wrapper: 11.1," + + "single_double_wrapper: 12.2," + + "single_uint32_wrapper: google.protobuf.UInt32Value{value: 13u}," + + "single_uint64_wrapper: google.protobuf.UInt64Value{value: 14u}," + + "oneof_type: NestedTestAllTypes {" + + " payload: TestAllTypes {" + + " single_bytes: b'abc'," + + " }" + + " }," + + "}") + .getAst(); + TestAllTypes expectedMessage = + TestAllTypes.newBuilder() + .setSingleInt32(4) + .setSingleInt64(6L) + .setSingleFloat(7.1f) + .setSingleDouble(8.2d) + .setSingleNestedEnum(NestedEnum.BAR) + .addAllRepeatedInt32(Arrays.asList(1, 2)) + .addAllRepeatedInt64(Arrays.asList(3L, 4L)) + .putMapStringInt32("a", 1) + .putMapStringInt64("b", 2) + .setSingleInt32Wrapper(Int32Value.of(9)) + .setSingleInt64Wrapper(Int64Value.of(10L)) + .setSingleFloatWrapper(FloatValue.of(11.1f)) + .setSingleDoubleWrapper(DoubleValue.of(12.2d)) + .setSingleUint32Wrapper(UInt32Value.of(13)) + .setSingleUint64Wrapper(UInt64Value.of(14L)) + .setOneofType( + NestedTestAllTypes.newBuilder() + .setPayload( + TestAllTypes.newBuilder().setSingleBytes(ByteString.copyFromUtf8("abc")))) + .build(); + + TestAllTypes simpleTest = (TestAllTypes) CEL_RUNTIME.createProgram(ast).eval(); + + assertThat(simpleTest).isEqualTo(expectedMessage); + } + + @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_list(String expression) throws Exception { + 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 + @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 + @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_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_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() 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 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()); + } + + @Test + public void anyMessage_packUnpack() throws Exception { + CelAbstractSyntaxTree ast = + CEL_COMPILER.compile("TestAllTypes { single_any: content }.single_any").getAst(); + TestAllTypes content = TestAllTypes.newBuilder().setSingleInt64(1L).build(); + + TestAllTypes result = + (TestAllTypes) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("content", content)); + + assertThat(result).isEqualTo(content); + } +} 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..bf752148a --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteDescriptorInterpreterTest.java @@ -0,0 +1,48 @@ +// 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(); + } +} 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/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/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", ], )