diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 260f13bf2..4cb3a75e7 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -526,6 +526,24 @@ public void program_withVars() throws Exception { assertThat(program.eval(ImmutableMap.of("variable", "hello"))).isEqualTo(true); } + @Test + public void program_withCelValue() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .setOptions(CelOptions.current().enableCelValue(true).build()) + .addDeclarations( + Decl.newBuilder() + .setName("variable") + .setIdent(IdentDecl.newBuilder().setType(CelTypes.STRING)) + .build()) + .setResultType(SimpleType.BOOL) + .build(); + + CelRuntime.Program program = cel.createProgram(cel.compile("variable == 'hello'").getAst()); + + assertThat(program.eval(ImmutableMap.of("variable", "hello"))).isEqualTo(true); + } + @Test public void program_withProtoVars() throws Exception { Cel cel = @@ -1285,6 +1303,26 @@ public void programAdvanceEvaluation_nestedSelect() throws Exception { .isEqualTo(CelUnknownSet.create(CelAttribute.fromQualifiedIdentifier("com.google.a"))); } + @Test + public void programAdvanceEvaluation_nestedSelect_withCelValue() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .setOptions( + CelOptions.current().enableUnknownTracking(true).enableCelValue(true).build()) + .addVar("com", MapType.create(SimpleType.STRING, SimpleType.DYN)) + .addFunctionBindings() + .setResultType(SimpleType.BOOL) + .build(); + CelRuntime.Program program = cel.createProgram(cel.compile("com.google.a || false").getAst()); + + assertThat( + program.advanceEvaluation( + UnknownContext.create( + fromMap(ImmutableMap.of()), + ImmutableList.of(CelAttributePattern.fromQualifiedIdentifier("com.google.a"))))) + .isEqualTo(CelUnknownSet.create(CelAttribute.fromQualifiedIdentifier("com.google.a"))); + } + @Test public void programAdvanceEvaluation_argumentMergeErrorPriority() throws Exception { Cel cel = diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java index b4a944daa..eaa812e2b 100644 --- a/common/src/main/java/dev/cel/common/CelOptions.java +++ b/common/src/main/java/dev/cel/common/CelOptions.java @@ -87,6 +87,8 @@ public abstract class CelOptions { public abstract boolean enableUnknownTracking(); + public abstract boolean enableCelValue(); + public abstract int comprehensionMaxIterations(); public abstract Builder toBuilder(); @@ -176,6 +178,7 @@ public static Builder newBuilder() { .errorOnDuplicateMapKeys(false) .resolveTypeDependencies(true) .enableUnknownTracking(false) + .enableCelValue(false) .comprehensionMaxIterations(-1); } @@ -428,6 +431,15 @@ public abstract static class Builder { */ public abstract Builder enableUnknownTracking(boolean value); + /** + * Enables the usage of {@code CelValue} for the runtime. It is a native value representation of + * CEL that wraps Java native objects, and comes with extended capabilities, such as allowing + * value constructs not understood by CEL (ex: POJOs). + * + *

Warning: This option is experimental. + */ + public abstract Builder enableCelValue(boolean value); + /** * Limit the total number of iterations permitted within comprehension loops. * diff --git a/common/src/main/java/dev/cel/common/types/TypeType.java b/common/src/main/java/dev/cel/common/types/TypeType.java index 797284986..44905bbc7 100644 --- a/common/src/main/java/dev/cel/common/types/TypeType.java +++ b/common/src/main/java/dev/cel/common/types/TypeType.java @@ -26,8 +26,6 @@ @Immutable public abstract class TypeType extends CelType { - static final TypeType TYPE = create(SimpleType.DYN); - @Override public CelKind kind() { return CelKind.TYPE; @@ -38,6 +36,16 @@ public String name() { return "type"; } + /** Retrieves the underlying type name of the type-kind held. */ + public String containingTypeName() { + CelType containingType = type(); + if (containingType.kind() == CelKind.DYN) { + return "type"; + } + + return containingType.name(); + } + @Override public abstract ImmutableList parameters(); 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 782960b0c..0e896e7ac 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CelValueProvider.java @@ -41,7 +41,7 @@ public interface CelValueProvider { final class CombinedCelValueProvider implements CelValueProvider { private final ImmutableList celValueProviders; - CombinedCelValueProvider(CelValueProvider first, CelValueProvider second) { + public CombinedCelValueProvider(CelValueProvider first, CelValueProvider second) { Preconditions.checkNotNull(first); Preconditions.checkNotNull(second); celValueProviders = ImmutableList.of(first, second); diff --git a/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java b/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java index ee91712ec..4517a203c 100644 --- a/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java @@ -68,6 +68,7 @@ public void get_nonExistentKey_throws() { CelRuntimeException exception = assertThrows(CelRuntimeException.class, () -> mapValue.get(IntValue.create(100L))); + assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(exception).hasMessageThat().contains("key '100' is not present in map."); } diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index d9f3020e1..b08b341f8 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -45,3 +45,9 @@ java_library( name = "evaluation_listener", exports = ["//runtime/src/main/java/dev/cel/runtime:evaluation_listener"], ) + +java_library( + name = "runtime_type_provider_legacy", + visibility = ["//visibility:public"], + exports = ["//runtime/src/main/java/dev/cel/runtime:runtime_type_provider_legacy"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index d1b4da86c..d713b5113 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -147,9 +147,8 @@ java_library( tags = [ ], deps = [ - ":base", ":evaluation_listener", - ":interpreter", + ":runtime_type_provider_legacy", ":unknown_attributes", "//:auto_value", "//common", @@ -161,6 +160,9 @@ java_library( "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", "//common/types:cel_types", + "//common/values:cel_value_provider", + "//common/values:proto_message_value_provider", + "//runtime:interpreter", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -212,6 +214,29 @@ java_library( ], ) +java_library( + name = "runtime_type_provider_legacy", + srcs = ["RuntimeTypeProviderLegacyImpl.java"], + deps = [ + ":unknown_attributes", + "//common:options", + "//common/annotations", + "//common/internal:cel_descriptor_pools", + "//common/internal:dynamic_proto", + "//common/types", + "//common/types:type_providers", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//common/values:proto_message_value", + "//runtime:interpreter", + "@cel_spec//proto/cel/expr:expr_java_proto", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "interpreter_util", srcs = ["InterpreterUtil.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java index 1408a2069..2090e6bfa 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java @@ -22,6 +22,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import dev.cel.common.CelOptions; +import dev.cel.common.values.CelValueProvider; import java.util.function.Function; /** Interface for building an instance of CelRuntime */ @@ -138,6 +139,10 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder setTypeFactory(Function typeFactory); + /** Sets the {@code celValueProvider} for resolving values during evaluation. */ + @CanIgnoreReturnValue + CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider); + /** Enable or disable the standard CEL library functions and variables. */ @CanIgnoreReturnValue CelRuntimeBuilder setStandardEnvironmentEnabled(boolean value); diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 549977846..a98ed056f 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -40,6 +40,8 @@ // CEL-Internal-3 import dev.cel.common.internal.ProtoMessageFactory; import dev.cel.common.types.CelTypes; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.ProtoMessageValueProvider; import java.util.Arrays; import java.util.Optional; import java.util.function.Function; @@ -82,6 +84,7 @@ public static final class Builder implements CelRuntimeBuilder { private boolean standardEnvironmentEnabled; private Function customTypeFactory; private ExtensionRegistry extensionRegistry; + private CelValueProvider celValueProvider; @Override @CanIgnoreReturnValue @@ -142,6 +145,13 @@ public Builder setTypeFactory(Function typeFactory) { return this; } + @Override + @CanIgnoreReturnValue + public CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider) { + this.celValueProvider = celValueProvider; + return this; + } + @Override @CanIgnoreReturnValue public Builder setStandardEnvironmentEnabled(boolean value) { @@ -179,11 +189,14 @@ public CelRuntimeLegacyImpl build() { // Add libraries, such as extensions celRuntimeLibraries.build().forEach(celLibrary -> celLibrary.setRuntimeOptions(this)); + CelDescriptors celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + fileTypes.build(), options.resolveTypeDependencies()); + CelDescriptorPool celDescriptorPool = newDescriptorPool( - fileTypes.build(), - extensionRegistry, - options); + celDescriptors, + extensionRegistry); @SuppressWarnings("Immutable") ProtoMessageFactory runtimeTypeFactory = @@ -222,20 +235,30 @@ public CelRuntimeLegacyImpl build() { } })); + RuntimeTypeProvider runtimeTypeProvider; + + if (options.enableCelValue()) { + CelValueProvider messageValueProvider = + ProtoMessageValueProvider.newInstance(dynamicProto, options); + if (celValueProvider != null) { + messageValueProvider = + new CelValueProvider.CombinedCelValueProvider(celValueProvider, messageValueProvider); + } + + runtimeTypeProvider = + new RuntimeTypeProviderLegacyImpl( + options, messageValueProvider, celDescriptorPool, dynamicProto); + } else { + runtimeTypeProvider = new DescriptorMessageProvider(runtimeTypeFactory, options); + } + return new CelRuntimeLegacyImpl( - new DefaultInterpreter( - new DescriptorMessageProvider(runtimeTypeFactory, options), dispatcher, options), - options); + new DefaultInterpreter(runtimeTypeProvider, dispatcher, options), options); } private static CelDescriptorPool newDescriptorPool( - ImmutableSet fileTypeSet, - ExtensionRegistry extensionRegistry, - CelOptions celOptions) { - CelDescriptors celDescriptors = - CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( - fileTypeSet, celOptions.resolveTypeDependencies()); - + CelDescriptors celDescriptors, + ExtensionRegistry extensionRegistry) { ImmutableList.Builder descriptorPools = new ImmutableList.Builder<>(); descriptorPools.add(DefaultDescriptorPool.create(celDescriptors, extensionRegistry)); diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java new file mode 100644 index 000000000..841d702a2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java @@ -0,0 +1,130 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.runtime; + +import dev.cel.expr.Type; +import dev.cel.expr.Value; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.Immutable; +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.types.CelType; +import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.ProtoCelValueConverter; +import dev.cel.common.values.SelectableValue; +import dev.cel.common.values.StringValue; +import java.util.Map; +import java.util.NoSuchElementException; +import org.jspecify.nullness.Nullable; + +/** Bridge between the old RuntimeTypeProvider and CelValueProvider APIs. */ +@Internal +@Immutable +public final class RuntimeTypeProviderLegacyImpl implements RuntimeTypeProvider { + + private final CelValueProvider valueProvider; + private final ProtoCelValueConverter protoCelValueConverter; + private final TypeResolver standardTypeResolver; + + @VisibleForTesting + public RuntimeTypeProviderLegacyImpl( + CelOptions celOptions, + CelValueProvider valueProvider, + CelDescriptorPool celDescriptorPool, + DynamicProto dynamicProto) { + this.valueProvider = valueProvider; + this.protoCelValueConverter = + ProtoCelValueConverter.newInstance(celOptions, celDescriptorPool, dynamicProto); + this.standardTypeResolver = StandardTypeResolver.getInstance(celOptions); + } + + @Override + public Object createMessage(String messageName, Map values) { + return unwrapCelValue( + valueProvider + .newValue(messageName, values) + .orElseThrow( + () -> + new NoSuchElementException( + "Could not generate a new value for message name: " + messageName))); + } + + @Override + @SuppressWarnings("unchecked") + public Object selectField(Object message, String fieldName) { + SelectableValue selectableValue = + (SelectableValue) protoCelValueConverter.fromJavaObjectToCelValue(message); + + return unwrapCelValue(selectableValue.select(StringValue.create(fieldName))); + } + + @Override + @SuppressWarnings("unchecked") + public Object hasField(Object message, String fieldName) { + SelectableValue selectableValue = + (SelectableValue) protoCelValueConverter.fromJavaObjectToCelValue(message); + + return selectableValue.find(StringValue.create(fieldName)).isPresent(); + } + + @Override + public Object adapt(Object message) { + if (message instanceof CelUnknownSet) { + return message; // CelUnknownSet is handled specially for iterative evaluation. No need to + // adapt to CelValue. + } + return unwrapCelValue(protoCelValueConverter.fromJavaObjectToCelValue(message)); + } + + @Override + public Value resolveObjectType(Object obj, Value checkedTypeValue) { + // Presently, Java only supports evaluation of checked-only expressions. + Preconditions.checkNotNull(checkedTypeValue); + return standardTypeResolver.resolveObjectType(obj, checkedTypeValue); + } + + @Override + public Value adaptType(CelType type) { + Preconditions.checkNotNull(type); + if (type instanceof TypeType) { + return createTypeValue(((TypeType) type).containingTypeName()); + } + + return createTypeValue(type.name()); + } + + @Override + public @Nullable Value adaptType(@Nullable Type type) { + throw new UnsupportedOperationException("This should only be called with native CelType."); + } + + /** + * DefaultInterpreter cannot handle CelValue and instead expects plain Java objects. + * + *

This will become unnecessary once we introduce a rewrite of a Cel runtime. + */ + private Object unwrapCelValue(CelValue object) { + return protoCelValueConverter.fromCelValueToJavaObject(object); + } + + private static Value createTypeValue(String name) { + return Value.newBuilder().setTypeValue(name).build(); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 52c200358..fea8d6e43 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -8,6 +8,7 @@ java_library( srcs = glob( ["*.java"], exclude = [ + "CelValueInterpreterTest.java", "InterpreterTest.java", ], ), @@ -70,6 +71,22 @@ java_library( ], ) +java_library( + name = "cel_value_interpreter_test", + testonly = 1, + srcs = [ + "CelValueInterpreterTest.java", + ], + deps = [ + # "//java/com/google/testing/testsize:annotations", + "//common:options", + "//testing:base_interpreter_test", + "//testing:cel_value_sync", + "//testing:eval", + "@maven//:junit_junit", + ], +) + junit4_test_suites( name = "test_suites", shard_count = 4, @@ -79,6 +96,7 @@ junit4_test_suites( ], src_dir = "src/test/java", deps = [ + ":cel_value_interpreter_test", ":interpreter_test", ":tests", ], diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java index 72c318e37..61794aea0 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java @@ -2,7 +2,7 @@ // // 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 +// You may obtain a copy of the License aj // // https://www.apache.org/licenses/LICENSE-2.0 // diff --git a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java new file mode 100644 index 000000000..4c657a0c8 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java @@ -0,0 +1,88 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.runtime; + +// import com.google.testing.testsize.MediumTest; +import dev.cel.common.CelOptions; +import dev.cel.testing.BaseInterpreterTest; +import dev.cel.testing.Eval; +import dev.cel.testing.EvalCelValueSync; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** Tests for {@link Interpreter} and related functionality using {@code CelValue}. */ +// @MediumTest +@RunWith(Parameterized.class) +public class CelValueInterpreterTest extends BaseInterpreterTest { + + private static final CelOptions SIGNED_UINT_TEST_OPTIONS = + CelOptions.current() + .enableTimestampEpoch(true) + .enableHeterogeneousNumericComparisons(true) + .enableCelValue(true) + .comprehensionMaxIterations(1_000) + .build(); + + public CelValueInterpreterTest(boolean declareWithCelType, Eval eval) { + super(declareWithCelType, eval); + } + + /** Test relies on PartialMessage, which is deprecated and not supported for CelValue. */ + @Override + @Test + public void unknownField() { + skipBaselineVerification(); + } + + /** Test relies on PartialMessage, which is deprecated and not supported for CelValue. */ + @Override + @Test + public void unknownResultSet() { + skipBaselineVerification(); + } + + @Parameters + public static List testData() { + return new ArrayList<>( + Arrays.asList( + new Object[][] { + // SYNC_PROTO_TYPE + { + /* declareWithCelType= */ false, + new EvalCelValueSync(TEST_FILE_DESCRIPTORS, TEST_OPTIONS) + }, + // SYNC_PROTO_TYPE_SIGNED_UINT + { + /* declareWithCelType= */ false, + new EvalCelValueSync(TEST_FILE_DESCRIPTORS, SIGNED_UINT_TEST_OPTIONS) + }, + // SYNC_CEL_TYPE + { + /* declareWithCelType= */ true, + new EvalCelValueSync(TEST_FILE_DESCRIPTORS, TEST_OPTIONS) + }, + // SYNC_CEL_TYPE_SIGNED_UINT + { + /* declareWithCelType= */ true, + new EvalCelValueSync(TEST_FILE_DESCRIPTORS, SIGNED_UINT_TEST_OPTIONS) + }, + })); + } +} diff --git a/runtime/src/test/resources/maps.baseline b/runtime/src/test/resources/maps.baseline index bc61849dd..da0f857ad 100644 --- a/runtime/src/test/resources/maps.baseline +++ b/runtime/src/test/resources/maps.baseline @@ -141,4 +141,3 @@ declare b { bindings: {b=true} error: evaluation error at test_location:15: duplicate map key [true] error_code: DUPLICATE_ATTRIBUTE - diff --git a/runtime/src/test/resources/typeComparisons.baseline b/runtime/src/test/resources/typeComparisons.baseline index 514fabf04..3af41c3da 100644 --- a/runtime/src/test/resources/typeComparisons.baseline +++ b/runtime/src/test/resources/typeComparisons.baseline @@ -33,6 +33,11 @@ Source: type(TestAllTypes) == type bindings: {} result: true +Source: type(TestAllTypes{}) == TestAllTypes +=====> +bindings: {} +result: true + Source: type(null) == null_type =====> bindings: {} diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index 04b070b3c..bd958decc 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel @@ -34,6 +34,11 @@ java_library( exports = ["//testing/src/main/java/dev/cel/testing:sync"], ) +java_library( + name = "cel_value_sync", + exports = ["//testing/src/main/java/dev/cel/testing:cel_value_sync"], +) + java_library( name = "eval", exports = ["//testing/src/main/java/dev/cel/testing:eval"], diff --git a/testing/src/main/java/dev/cel/testing/BUILD.bazel b/testing/src/main/java/dev/cel/testing/BUILD.bazel index 1ffb77b47..f641dd56f 100644 --- a/testing/src/main/java/dev/cel/testing/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/BUILD.bazel @@ -81,6 +81,27 @@ java_library( ], ) +java_library( + name = "cel_value_sync", + testonly = 1, + srcs = ["EvalCelValueSync.java"], + deps = [ + ":eval", + "//common", + "//common:options", + "//common/internal:cel_descriptor_pools", + "//common/internal:default_message_factory", + "//common/internal:dynamic_proto", + "//common/internal:proto_message_factory", + "//common/values:cel_value_provider", + "//common/values:proto_message_value_provider", + "//runtime:interpreter", + "//runtime:runtime_type_provider_legacy", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + java_library( name = "sync", testonly = 1, @@ -92,7 +113,6 @@ java_library( "//common/internal:cel_descriptor_pools", "//common/internal:default_message_factory", "//runtime:interpreter", - "@cel_spec//proto/cel/expr:expr_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", ], @@ -105,10 +125,10 @@ java_library( "Eval.java", ], deps = [ + "//common", "//common:options", "//runtime:base", "//runtime:interpreter", - "@cel_spec//proto/cel/expr:expr_java_proto", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -132,7 +152,6 @@ java_library( "//common/resources/testdata/proto3:test_all_types_java_proto", "//common/types:cel_types", "//runtime:interpreter", - "//runtime/testdata:test_java_proto", "@cel_spec//proto/cel/expr:expr_java_proto", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 03fa65e28..88a4fd8ba 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -93,7 +93,7 @@ private Object runTest(Activation activation) throws Exception { testOutput().println("bindings: " + activation); Object result = null; try { - result = eval.eval(CelProtoAbstractSyntaxTree.fromCelAst(ast).toCheckedExpr(), activation); + result = eval.eval(ast, activation); if (result instanceof ByteString) { // Note: this call may fail for printing byte sequences that are not valid UTF-8, but works // pretty well for test purposes. @@ -1756,6 +1756,10 @@ public void typeComparisons() throws Exception { source = "type(TestAllTypes) == type"; runTest(Activation.EMPTY); + // Test whether the type resolution of a proto object is recognized as the message's type. + source = "type(TestAllTypes{}) == TestAllTypes"; + runTest(Activation.EMPTY); + // Test whether null resolves to null_type. source = "type(null) == null_type"; runTest(Activation.EMPTY); diff --git a/testing/src/main/java/dev/cel/testing/Eval.java b/testing/src/main/java/dev/cel/testing/Eval.java index 6c9d4a92d..2a2b4f5e8 100644 --- a/testing/src/main/java/dev/cel/testing/Eval.java +++ b/testing/src/main/java/dev/cel/testing/Eval.java @@ -14,10 +14,10 @@ package dev.cel.testing; -import dev.cel.expr.CheckedExpr; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CheckReturnValue; import com.google.protobuf.Descriptors.FileDescriptor; +import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; import dev.cel.runtime.Activation; import dev.cel.runtime.InterpreterException; @@ -39,8 +39,6 @@ public interface Eval { /** Adapts a Java POJO to a CEL value. */ Object adapt(Object value) throws InterpreterException; - /** - * Evaluates a {@code CheckedExpr} against a set of inputs represented by the {@code Activation}. - */ - Object eval(CheckedExpr checkedExpr, Activation activation) throws Exception; + /** Evaluates an {@code ast} against a set of inputs represented by the {@code Activation}. */ + Object eval(CelAbstractSyntaxTree ast, Activation activation) throws Exception; } diff --git a/testing/src/main/java/dev/cel/testing/EvalCelValueSync.java b/testing/src/main/java/dev/cel/testing/EvalCelValueSync.java new file mode 100644 index 000000000..5b6a69bfa --- /dev/null +++ b/testing/src/main/java/dev/cel/testing/EvalCelValueSync.java @@ -0,0 +1,95 @@ +// 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.testing; + +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Descriptors.FileDescriptor; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelDescriptors; +import dev.cel.common.CelOptions; +import dev.cel.common.internal.DefaultDescriptorPool; +import dev.cel.common.internal.DefaultMessageFactory; +import dev.cel.common.internal.DynamicProto; +import dev.cel.common.internal.ProtoMessageFactory; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.ProtoMessageValueProvider; +import dev.cel.runtime.Activation; +import dev.cel.runtime.DefaultDispatcher; +import dev.cel.runtime.DefaultInterpreter; +import dev.cel.runtime.Interpreter; +import dev.cel.runtime.InterpreterException; +import dev.cel.runtime.Registrar; +import dev.cel.runtime.RuntimeTypeProvider; +import dev.cel.runtime.RuntimeTypeProviderLegacyImpl; + +/** + * The {@link EvalSync} class represents common concerns for synchronous evaluation using {@code + * CelValue}. + */ +public final class EvalCelValueSync implements Eval { + + private final ImmutableList fileDescriptors; + private final DefaultDispatcher dispatcher; + private final Interpreter interpreter; + private final RuntimeTypeProvider typeProvider; + private final CelOptions celOptions; + + public EvalCelValueSync(ImmutableList fileDescriptors, CelOptions celOptions) { + this.fileDescriptors = fileDescriptors; + this.dispatcher = DefaultDispatcher.create(celOptions); + this.celOptions = celOptions; + this.typeProvider = newTypeProvider(fileDescriptors); + this.interpreter = new DefaultInterpreter(typeProvider, dispatcher, celOptions); + } + + private RuntimeTypeProvider newTypeProvider(ImmutableList fileDescriptors) { + CelDescriptors celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors); + DefaultDescriptorPool celDescriptorPool = DefaultDescriptorPool.create(celDescriptors); + ProtoMessageFactory messageFactory = DefaultMessageFactory.create(celDescriptorPool); + DynamicProto dynamicProto = DynamicProto.create(messageFactory); + CelValueProvider messageValueProvider = + ProtoMessageValueProvider.newInstance(dynamicProto, celOptions); + + return new RuntimeTypeProviderLegacyImpl( + celOptions, messageValueProvider, celDescriptorPool, dynamicProto); + } + + @Override + public ImmutableList fileDescriptors() { + return fileDescriptors; + } + + @Override + public Registrar registrar() { + return dispatcher; + } + + @Override + public CelOptions celOptions() { + return celOptions; + } + + @Override + public Object adapt(Object value) throws InterpreterException { + return typeProvider.adapt(value); + } + + @Override + public Object eval(CelAbstractSyntaxTree ast, Activation activation) throws Exception { + return interpreter.createInterpretable(ast).eval(activation); + } +} diff --git a/testing/src/main/java/dev/cel/testing/EvalSync.java b/testing/src/main/java/dev/cel/testing/EvalSync.java index 403134454..562043ce6 100644 --- a/testing/src/main/java/dev/cel/testing/EvalSync.java +++ b/testing/src/main/java/dev/cel/testing/EvalSync.java @@ -14,9 +14,9 @@ package dev.cel.testing; -import dev.cel.expr.CheckedExpr; import com.google.common.collect.ImmutableList; import com.google.protobuf.Descriptors.FileDescriptor; +import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelDescriptors; import dev.cel.common.CelOptions; @@ -73,7 +73,7 @@ public Object adapt(Object value) throws InterpreterException { } @Override - public Object eval(CheckedExpr checkedExpr, Activation activation) throws Exception { - return interpreter.createInterpretable(checkedExpr).eval(activation); + public Object eval(CelAbstractSyntaxTree ast, Activation activation) throws Exception { + return interpreter.createInterpretable(ast).eval(activation); } } diff --git a/testing/src/test/java/dev/cel/testing/EvalSyncTest.java b/testing/src/test/java/dev/cel/testing/EvalSyncTest.java index 0e15fa310..e516d9780 100644 --- a/testing/src/test/java/dev/cel/testing/EvalSyncTest.java +++ b/testing/src/test/java/dev/cel/testing/EvalSyncTest.java @@ -33,7 +33,6 @@ import com.google.type.Expr; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; -import dev.cel.common.CelProtoAbstractSyntaxTree; import dev.cel.common.types.CelType; import dev.cel.common.types.SimpleType; import dev.cel.compiler.CelCompiler; @@ -141,10 +140,7 @@ public EvalWithoutActivationTests(String expr, Object evaluatedResult) { @Test public void evaluateExpr_returnsExpectedResult() throws Exception { CelAbstractSyntaxTree ast = COMPILER.compile(expr).getAst(); - assertThat( - EVAL.eval( - CelProtoAbstractSyntaxTree.fromCelAst(ast).toCheckedExpr(), Activation.EMPTY)) - .isEqualTo(evaluatedResult); + assertThat(EVAL.eval(ast, Activation.EMPTY)).isEqualTo(evaluatedResult); } } @@ -182,11 +178,7 @@ public EvalWithActivationTests( @Test public void expr_returnsExpectedResult() throws Exception { CelAbstractSyntaxTree ast = compiler.compile(expr).getAst(); - assertThat( - EVAL.eval( - CelProtoAbstractSyntaxTree.fromCelAst(ast).toCheckedExpr(), - Activation.of("x", paramValue))) - .isEqualTo(evaluatedResult); + assertThat(EVAL.eval(ast, Activation.of("x", paramValue))).isEqualTo(evaluatedResult); } } }