diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index f52cfc36c..95b367684 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -35,7 +35,6 @@ java_library( java_library( name = "errors", - visibility = ["//visibility:public"], exports = ["//common/src/main/java/dev/cel/common/internal:errors"], ) @@ -43,3 +42,28 @@ java_library( name = "env_visitor", exports = ["//common/src/main/java/dev/cel/common/internal:env_visitor"], ) + +java_library( + name = "default_instance_message_factory", + exports = ["//common/src/main/java/dev/cel/common/internal:default_instance_message_factory"], +) + +java_library( + name = "well_known_proto", + exports = ["//common/src/main/java/dev/cel/common/internal:well_known_proto"], +) + +java_library( + name = "proto_message_factory", + exports = ["//common/src/main/java/dev/cel/common/internal:proto_message_factory"], +) + +java_library( + name = "default_message_factory", + exports = ["//common/src/main/java/dev/cel/common/internal:default_message_factory"], +) + +java_library( + name = "cel_descriptor_pools", + exports = ["//common/src/main/java/dev/cel/common/internal:cel_descriptor_pools"], +) 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 e345633a2..104a82279 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -24,6 +24,13 @@ ENV_VISITOR_SOURCES = [ "EnvVisitor.java", ] +# keep sorted +CEL_DESCRIPTOR_POOL_SOURCES = [ + "CelDescriptorPool.java", + "CombinedDescriptorPool.java", + "DefaultDescriptorPool.java", +] + java_library( name = "internal", srcs = INTERNAL_SOURCES, @@ -86,12 +93,25 @@ java_library( # keep sorted DYNAMIC_PROTO_SOURCES = [ - "DefaultInstanceMessageFactory.java", "DynamicProto.java", "ProtoAdapter.java", "ProtoRegistryProvider.java", ] +java_library( + name = "default_instance_message_factory", + srcs = ["DefaultInstanceMessageFactory.java"], + tags = [ + ], + deps = [ + "//common/annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +# keep sorted + java_library( name = "dynamic_proto", srcs = DYNAMIC_PROTO_SOURCES, @@ -99,6 +119,7 @@ java_library( ], deps = [ ":converter", + ":default_instance_message_factory", "//:auto_value", "//common", "//common:error_codes", @@ -155,3 +176,56 @@ java_library( "@cel_spec//proto/cel/expr:expr_java_proto", ], ) + +java_library( + name = "well_known_proto", + srcs = ["WellKnownProto.java"], + tags = [ + ], + deps = [ + "//common/annotations", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "default_message_factory", + srcs = ["DefaultMessageFactory.java"], + tags = [ + ], + deps = [ + ":cel_descriptor_pools", + ":default_instance_message_factory", + ":proto_message_factory", + "//common/annotations", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "proto_message_factory", + srcs = ["ProtoMessageFactory.java"], + tags = [ + ], + deps = [ + ":cel_descriptor_pools", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "cel_descriptor_pools", + srcs = CEL_DESCRIPTOR_POOL_SOURCES, + tags = [ + ], + deps = [ + ":well_known_proto", + "//common", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) diff --git a/common/src/main/java/dev/cel/common/internal/CelDescriptorPool.java b/common/src/main/java/dev/cel/common/internal/CelDescriptorPool.java new file mode 100644 index 000000000..b2c0533b1 --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/CelDescriptorPool.java @@ -0,0 +1,46 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; +import dev.cel.common.annotations.Internal; +import java.util.Optional; + +/** + * {@link CelDescriptorPool} allows lookup of descriptors for message types and field descriptors + * for Proto2 extension messages. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +public interface CelDescriptorPool { + + /** Finds the descriptor by fully qualified message type. */ + Optional findDescriptor(String name); + + /** Finds the corresponding field descriptor for an extension field on a message. */ + Optional findExtensionDescriptor( + Descriptor containingDescriptor, String fieldName); + + /** + * Retrieves the registered extension registry. This is specifically needed to handle unpacking + * Any messages containing Proto2 extension messages. + */ + ExtensionRegistry getExtensionRegistry(); +} diff --git a/common/src/main/java/dev/cel/common/internal/CombinedDescriptorPool.java b/common/src/main/java/dev/cel/common/internal/CombinedDescriptorPool.java new file mode 100644 index 000000000..975e5bdbf --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/CombinedDescriptorPool.java @@ -0,0 +1,85 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; +import dev.cel.common.annotations.Internal; +import java.util.Optional; + +/** + * The {@link CombinedDescriptorPool} takes one or more {@link CelDescriptorPool} instances and + * supports descriptor lookups in the order the descriptor pools are added to the constructor. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +public final class CombinedDescriptorPool implements CelDescriptorPool { + private final ImmutableList descriptorPools; + + @SuppressWarnings("Immutable") // ExtensionRegistry is immutable, just not marked as such. + private final ExtensionRegistry extensionRegistry; + + public static CombinedDescriptorPool create(ImmutableList descriptorPools) { + return new CombinedDescriptorPool(descriptorPools); + } + + @Override + public Optional findDescriptor(String name) { + for (CelDescriptorPool descriptorPool : descriptorPools) { + Optional maybeDescriptor = descriptorPool.findDescriptor(name); + if (maybeDescriptor.isPresent()) { + return maybeDescriptor; + } + } + + return Optional.empty(); + } + + @Override + public Optional findExtensionDescriptor( + Descriptor containingDescriptor, String fieldName) { + for (CelDescriptorPool descriptorPool : descriptorPools) { + Optional maybeExtensionDescriptor = + descriptorPool.findExtensionDescriptor(containingDescriptor, fieldName); + if (maybeExtensionDescriptor.isPresent()) { + return maybeExtensionDescriptor; + } + } + + return Optional.empty(); + } + + @Override + public ExtensionRegistry getExtensionRegistry() { + return extensionRegistry; + } + + private CombinedDescriptorPool(ImmutableList descriptorPools) { + this.descriptorPools = descriptorPools; + // TODO: Combine the extension registry. This will become necessary once we accept + // ExtensionRegistry through runtime builder. + this.extensionRegistry = + descriptorPools.stream() + .map(CelDescriptorPool::getExtensionRegistry) + .filter(e -> !e.equals(ExtensionRegistry.getEmptyRegistry())) + .findFirst() + .orElse(ExtensionRegistry.getEmptyRegistry()); + } +} diff --git a/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java b/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java new file mode 100644 index 000000000..66dbf2e1e --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java @@ -0,0 +1,96 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static java.util.Arrays.stream; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; +import dev.cel.common.CelDescriptors; +import dev.cel.common.annotations.Internal; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A descriptor pool that has descriptors pre-loaded for well-known types defined by {@link + * WellKnownProto}. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +public final class DefaultDescriptorPool implements CelDescriptorPool { + private static final ImmutableMap WELL_KNOWN_TYPE_DESCRIPTORS = + stream(WellKnownProto.values()) + .collect(toImmutableMap(WellKnownProto::typeName, WellKnownProto::descriptor)); + + /** A DefaultDescriptorPool instance with just well known types loaded. */ + public static final DefaultDescriptorPool INSTANCE = + new DefaultDescriptorPool(WELL_KNOWN_TYPE_DESCRIPTORS, ImmutableMultimap.of()); + + // K: Fully qualified message type name, V: Message descriptor + private final ImmutableMap descriptorMap; + + // K: Fully qualified message type name (of containing descriptor) + // V: Field descriptor for the extension message + private final ImmutableMultimap extensionDescriptorMap; + + public static DefaultDescriptorPool create(CelDescriptors celDescriptors) { + Map descriptorMap = new HashMap<>(); // Using a hashmap to allow deduping + stream(WellKnownProto.values()).forEach(d -> descriptorMap.put(d.typeName(), d.descriptor())); + + for (Descriptor descriptor : celDescriptors.messageTypeDescriptors()) { + descriptorMap.putIfAbsent(descriptor.getFullName(), descriptor); + } + + return new DefaultDescriptorPool( + ImmutableMap.copyOf(descriptorMap), celDescriptors.extensionDescriptors()); + } + + @Override + public Optional findDescriptor(String name) { + return Optional.ofNullable(descriptorMap.get(name)); + } + + @Override + public Optional findExtensionDescriptor( + Descriptor containingDescriptor, String fieldName) { + String typeName = containingDescriptor.getFullName(); + ImmutableCollection fieldDescriptors = extensionDescriptorMap.get(typeName); + + return fieldDescriptors.stream().filter(d -> d.getFullName().equals(fieldName)).findFirst(); + } + + @Override + public ExtensionRegistry getExtensionRegistry() { + // TODO: Populate one from runtime builder. + return ExtensionRegistry.getEmptyRegistry(); + } + + private DefaultDescriptorPool( + ImmutableMap descriptorMap, + ImmutableMultimap extensionDescriptorMap) { + this.descriptorMap = checkNotNull(descriptorMap); + this.extensionDescriptorMap = checkNotNull(extensionDescriptorMap); + } +} diff --git a/common/src/main/java/dev/cel/common/internal/DefaultMessageFactory.java b/common/src/main/java/dev/cel/common/internal/DefaultMessageFactory.java new file mode 100644 index 000000000..381a5de9e --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DefaultMessageFactory.java @@ -0,0 +1,60 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.Message; +import dev.cel.common.annotations.Internal; +import java.util.Optional; + +/** DefaultMessageFactory produces {@link Message.Builder} instances by protobuf name. */ +@Internal +public final class DefaultMessageFactory implements ProtoMessageFactory { + private final CelDescriptorPool celDescriptorPool; + + public static DefaultMessageFactory create(CelDescriptorPool celDescriptorPool) { + return new DefaultMessageFactory(celDescriptorPool); + } + + @Override + public CelDescriptorPool getDescriptorPool() { + return celDescriptorPool; + } + + @Override + public Optional newBuilder(String messageName) { + Optional descriptor = celDescriptorPool.findDescriptor(messageName); + if (!descriptor.isPresent()) { + return Optional.empty(); + } + + // If the descriptor that's resolved does not match the descriptor instance in the message + // factory, the call to fetch the prototype will return null, and a dynamic proto message + // should be used as a fallback. + Optional message = + DefaultInstanceMessageFactory.getInstance().getPrototype(descriptor.get()); + + if (message.isPresent()) { + return message.map(Message::toBuilder); + } + + return Optional.of(DynamicMessage.newBuilder(descriptor.get())); + } + + private DefaultMessageFactory(CelDescriptorPool celDescriptorPool) { + this.celDescriptorPool = celDescriptorPool; + } +} diff --git a/common/src/main/java/dev/cel/common/internal/ProtoMessageFactory.java b/common/src/main/java/dev/cel/common/internal/ProtoMessageFactory.java new file mode 100644 index 000000000..d7719ff4f --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/ProtoMessageFactory.java @@ -0,0 +1,63 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.Message; +import java.util.Optional; + +/** {@code ProtoMessageFactory} provides a method to create a protobuf builder objects by name. */ +@Immutable +@FunctionalInterface +public interface ProtoMessageFactory { + + /** + * Constructs a new {@link Message.Builder} for a fully qualified proto message type. An empty + * result is returned if a descriptor is missing for the message type name. + */ + Optional newBuilder(String messageName); + + /** Gets the underlying descriptor pool used to construct proto messages. */ + default CelDescriptorPool getDescriptorPool() { + return DefaultDescriptorPool.INSTANCE; + } + + /** + * The {@link CombinedMessageFactory} takes one or more {@link ProtoMessageFactory} instances and + * attempts to create a {@code Message.Builder} instance for a given message name by calling each + * message factory in the order that they are provided to the constructor. + */ + @Immutable + final class CombinedMessageFactory implements ProtoMessageFactory { + + private final ImmutableList messageFactories; + + public CombinedMessageFactory(Iterable messageFactories) { + this.messageFactories = ImmutableList.copyOf(messageFactories); + } + + @Override + public Optional newBuilder(String messageName) { + for (ProtoMessageFactory messageFactory : messageFactories) { + Optional builder = messageFactory.newBuilder(messageName); + if (builder.isPresent()) { + return builder; + } + } + return Optional.empty(); + } + } +} diff --git a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java new file mode 100644 index 000000000..f3520f447 --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java @@ -0,0 +1,81 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.protobuf.Any; +import com.google.protobuf.BoolValue; +import com.google.protobuf.BytesValue; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.Duration; +import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; +import com.google.protobuf.ListValue; +import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Timestamp; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; +import com.google.protobuf.Value; +import dev.cel.common.annotations.Internal; + +/** + * WellKnownProto types used throughout CEL. These types are specially handled to ensure that + * bidirectional conversion between CEL native values and these well-known types is performed + * consistently across runtimes. + */ +@Internal +public enum WellKnownProto { + JSON_VALUE(Value.getDescriptor()), + JSON_STRUCT_VALUE(Struct.getDescriptor()), + JSON_LIST_VALUE(ListValue.getDescriptor()), + ANY_VALUE(Any.getDescriptor()), + BOOL_VALUE(BoolValue.getDescriptor(), true), + BYTES_VALUE(BytesValue.getDescriptor(), true), + DOUBLE_VALUE(DoubleValue.getDescriptor(), true), + FLOAT_VALUE(FloatValue.getDescriptor(), true), + INT32_VALUE(Int32Value.getDescriptor(), true), + INT64_VALUE(Int64Value.getDescriptor(), true), + STRING_VALUE(StringValue.getDescriptor(), true), + UINT32_VALUE(UInt32Value.getDescriptor(), true), + UINT64_VALUE(UInt64Value.getDescriptor(), true), + DURATION_VALUE(Duration.getDescriptor()), + TIMESTAMP_VALUE(Timestamp.getDescriptor()); + + private final Descriptor descriptor; + private final boolean isWrapperType; + + WellKnownProto(Descriptor descriptor) { + this(descriptor, /* isWrapperType= */ false); + } + + WellKnownProto(Descriptor descriptor, boolean isWrapperType) { + this.descriptor = descriptor; + this.isWrapperType = isWrapperType; + } + + public Descriptor descriptor() { + return descriptor; + } + + public String typeName() { + return descriptor.getFullName(); + } + + boolean isWrapperType() { + return isWrapperType; + } +} 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 846219e42..c5b37578b 100644 --- a/common/src/test/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/internal/BUILD.bazel @@ -15,11 +15,18 @@ java_library( "//common:options", "//common/ast", "//common/internal", + "//common/internal:cel_descriptor_pools", "//common/internal:comparison_functions", "//common/internal:converter", + "//common/internal:default_instance_message_factory", + "//common/internal:default_message_factory", "//common/internal:dynamic_proto", "//common/internal:errors", "//common/internal:proto_equality", + "//common/internal:proto_message_factory", + "//common/resources/testdata/proto2:messages_extensions_proto2_java_proto", + "//common/resources/testdata/proto2:messages_proto2_java_proto", + "//common/resources/testdata/proto2:test_all_types_java_proto", "//common/resources/testdata/proto3:test_all_types_java_proto", "//common/src/test/resources:default_instance_message_test_protos_java_proto", "//common/src/test/resources:multi_file_java_proto", diff --git a/common/src/test/java/dev/cel/common/internal/CombinedDescriptorPoolTest.java b/common/src/test/java/dev/cel/common/internal/CombinedDescriptorPoolTest.java new file mode 100644 index 000000000..07f33fc8b --- /dev/null +++ b/common/src/test/java/dev/cel/common/internal/CombinedDescriptorPoolTest.java @@ -0,0 +1,109 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.ExtensionRegistry; +import com.google.protobuf.Value; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.testing.testdata.proto2.MessagesProto2Extensions; +import dev.cel.testing.testdata.proto2.Proto2Message; +import dev.cel.testing.testdata.proto2.TestAllTypesProto.TestAllTypes; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CombinedDescriptorPoolTest { + + @Test + public void findDescriptor_descriptorReturnedFromBothPool() { + CelDescriptorPool dynamicDescriptorPool = + DefaultDescriptorPool.create( + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + TestAllTypes.getDescriptor().getFile())); + CombinedDescriptorPool combinedDescriptorPool = + CombinedDescriptorPool.create( + ImmutableList.of(DefaultDescriptorPool.INSTANCE, dynamicDescriptorPool)); + + assertThat(combinedDescriptorPool.findDescriptor(Value.getDescriptor().getFullName())) + .hasValue( + Value.getDescriptor()); // Retrieved from default descriptor pool (well-known-type) + assertThat(combinedDescriptorPool.findDescriptor(TestAllTypes.getDescriptor().getFullName())) + .hasValue(TestAllTypes.getDescriptor()); // Retrieved from the dynamic descriptor pool. + } + + @Test + public void findDescriptor_returnsEmpty() { + CelDescriptorPool descriptorPool = + DefaultDescriptorPool.create( + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + TestAllTypes.getDescriptor().getFile())); + CombinedDescriptorPool combinedDescriptorPool = + CombinedDescriptorPool.create( + ImmutableList.of(DefaultDescriptorPool.INSTANCE, descriptorPool)); + + assertThat(combinedDescriptorPool.findDescriptor("bogus")).isEmpty(); + } + + @Test + public void findExtensionDescriptor_success() { + CelDescriptorPool dynamicDescriptorPool = + DefaultDescriptorPool.create( + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + MessagesProto2Extensions.getDescriptor().getFile())); + CombinedDescriptorPool combinedDescriptorPool = + CombinedDescriptorPool.create( + ImmutableList.of(DefaultDescriptorPool.INSTANCE, dynamicDescriptorPool)); + + Optional fieldDescriptor = + combinedDescriptorPool.findExtensionDescriptor( + Proto2Message.getDescriptor(), "dev.cel.testing.testdata.proto2.test_all_types_ext"); + + assertThat(fieldDescriptor).isPresent(); + assertThat(fieldDescriptor.get().isExtension()).isTrue(); + assertThat(fieldDescriptor.get().getFullName()) + .isEqualTo("dev.cel.testing.testdata.proto2.test_all_types_ext"); + } + + @Test + public void findExtensionDescriptor_returnsEmpty() { + CelDescriptorPool dynamicDescriptorPool = + DefaultDescriptorPool.create( + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + MessagesProto2Extensions.getDescriptor().getFile())); + CombinedDescriptorPool combinedDescriptorPool = + CombinedDescriptorPool.create( + ImmutableList.of(DefaultDescriptorPool.INSTANCE, dynamicDescriptorPool)); + + assertThat( + combinedDescriptorPool.findExtensionDescriptor(TestAllTypes.getDescriptor(), "bogus")) + .isEmpty(); + } + + @Test + public void getExtensionRegistry_onDefaultPool_returnsEmptyRegistry() { + CombinedDescriptorPool combinedDescriptorPool = + CombinedDescriptorPool.create(ImmutableList.of(DefaultDescriptorPool.INSTANCE)); + + assertThat(combinedDescriptorPool.getExtensionRegistry()) + .isEqualTo(ExtensionRegistry.getEmptyRegistry()); + } +} diff --git a/common/src/test/java/dev/cel/common/internal/DefaultMessageFactoryTest.java b/common/src/test/java/dev/cel/common/internal/DefaultMessageFactoryTest.java new file mode 100644 index 000000000..68f104685 --- /dev/null +++ b/common/src/test/java/dev/cel/common/internal/DefaultMessageFactoryTest.java @@ -0,0 +1,121 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; +import com.google.protobuf.DescriptorProtos.FileDescriptorSet; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.TextFormat; +import com.google.protobuf.Value; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelDescriptors; +import dev.cel.common.internal.ProtoMessageFactory.CombinedMessageFactory; +import dev.cel.testing.testdata.proto2.TestAllTypesProto.TestAllTypes; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class DefaultMessageFactoryTest { + + @Test + public void newBuilder_wellKnownType_producesNewMessage() { + DefaultMessageFactory messageFactory = + DefaultMessageFactory.create(DefaultDescriptorPool.INSTANCE); + + Value.Builder valueBuilder = + (Value.Builder) messageFactory.newBuilder("google.protobuf.Value").get(); + + assertThat(valueBuilder.setStringValue("hello").build()) + .isEqualTo(Value.newBuilder().setStringValue("hello").build()); + } + + @Test + public void newBuilder_withDescriptor_producesNewMessageBuilder() { + CelDescriptorPool celDescriptorPool = + DefaultDescriptorPool.create( + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + TestAllTypes.getDescriptor().getFile())); + DefaultMessageFactory messageFactory = DefaultMessageFactory.create(celDescriptorPool); + + TestAllTypes.Builder builder = + (TestAllTypes.Builder) + messageFactory.newBuilder("dev.cel.testing.testdata.proto2.TestAllTypes").get(); + + assertThat(builder.setSingleInt64(5L).build()) + .isEqualTo(TestAllTypes.newBuilder().setSingleInt64(5L).build()); + } + + @Test + public void newBuilder_unknownMessage_returnsEmpty() { + DefaultMessageFactory messageFactory = + DefaultMessageFactory.create(DefaultDescriptorPool.INSTANCE); + + assertThat(messageFactory.newBuilder("unknown_message")).isEmpty(); + } + + @Test + public void newBuilder_unequalDescriptorForSameMessage_returnsDynamicMessage() throws Exception { + String fdsContent = + Resources.toString(Resources.getResource(Ascii.toLowerCase("value.fds")), UTF_8); + FileDescriptorSet fds = TextFormat.parse(fdsContent, FileDescriptorSet.class); + ImmutableSet files = + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fds); + CelDescriptors celDescriptors = CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(files); + + DefaultMessageFactory messageFactory = + DefaultMessageFactory.create(DefaultDescriptorPool.create(celDescriptors)); + + assertThat(messageFactory.newBuilder("google.api.expr.Value")).isPresent(); + assertThat(messageFactory.newBuilder("google.api.expr.Value").get()) + .isInstanceOf(DynamicMessage.Builder.class); + } + + @Test + public void getDescriptorPoolTest() { + CelDescriptorPool celDescriptorPool = + DefaultDescriptorPool.create( + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + TestAllTypes.getDescriptor().getFile())); + DefaultMessageFactory messageFactory = DefaultMessageFactory.create(celDescriptorPool); + + assertThat(messageFactory.getDescriptorPool()).isEqualTo(celDescriptorPool); + } + + @Test + public void combinedMessageFactoryTest() { + CombinedMessageFactory messageFactory = + new ProtoMessageFactory.CombinedMessageFactory( + ImmutableList.of( + DefaultMessageFactory.create(DefaultDescriptorPool.INSTANCE), + (messageName) -> + messageName.equals("test") + ? Optional.of(TestAllTypes.newBuilder()) + : Optional.empty())); + + assertThat(messageFactory.newBuilder("test").get().build()) + .isEqualTo(TestAllTypes.getDefaultInstance()); + assertThat(messageFactory.newBuilder("bogus")).isEmpty(); + } +}