diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java
index f6b985065..f0db128c1 100644
--- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java
+++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java
@@ -327,6 +327,9 @@ public Builder setTypeProvider(TypeProvider typeProvider) {
@Override
public CelBuilder setTypeProvider(CelTypeProvider celTypeProvider) {
compilerBuilder.setTypeProvider(celTypeProvider);
+ if (runtimeBuilder instanceof CelRuntimeImpl.Builder) {
+ runtimeBuilder.setTypeProvider(celTypeProvider);
+ }
return this;
}
diff --git a/common/src/main/java/dev/cel/common/values/OpaqueValue.java b/common/src/main/java/dev/cel/common/values/OpaqueValue.java
index 3350d05d4..8b3ac4574 100644
--- a/common/src/main/java/dev/cel/common/values/OpaqueValue.java
+++ b/common/src/main/java/dev/cel/common/values/OpaqueValue.java
@@ -14,13 +14,32 @@
package dev.cel.common.values;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.Immutable;
import dev.cel.common.types.OpaqueType;
-/** OpaqueValue is the value representation of OpaqueType. */
-@AutoValue
-@AutoValue.CopyAnnotations
-@SuppressWarnings("Immutable") // Java Object is mutable.
+/**
+ * OpaqueValue is the value representation of an {@link OpaqueType}.
+ *
+ *
Users may provide a custom opaque type that CEL can understand. Note that this is only
+ * supported for the Planner runtime. There are two primary modes of extending this class:
+ *
+ *
+ *
Direct Extension (Recommended): A domain object directly extends {@code OpaqueValue}
+ * and returns {@code this} for its {@link #value()} method. This approach allows the CEL
+ * engine to evaluate the object natively without stripping its type information, eliminating
+ * the need to register a custom {@link CelValueConverter}.
+ *
Wrapping: A domain object is wrapped into an {@code OpaqueValue} via the {@link
+ * #create(String, Object)} factory method. This is required when users cannot modify their
+ * existing POJOs to extend {@code OpaqueValue}. However, because the CEL runtime aggressively
+ * unwraps objects during evaluation, this mode necessitates implementing and registering a
+ * custom {@code CelValueConverter} that maps the unwrapped native Java object back into its
+ * corresponding {@code OpaqueValue}.
+ *
+ */
+@Immutable
public abstract class OpaqueValue extends CelValue {
@Override
@@ -31,7 +50,30 @@ public boolean isZeroValue() {
@Override
public abstract OpaqueType celType();
+ /**
+ * Creates an {@code OpaqueValue} by wrapping a domain object.
+ *
+ *
This method should only be used for the "Wrapping" extension mode (see class Javadoc) when
+ * users cannot modify their POJOs to directly extend {@code OpaqueValue}. Using this method
+ * necessitates implementing and registering a custom {@link CelValueConverter}.
+ *
+ * @param name The name of the opaque type.
+ * @param value The raw Java object to wrap.
+ */
public static OpaqueValue create(String name, Object value) {
- return new AutoValue_OpaqueValue(value, OpaqueType.create(name));
+ return new AutoValue_OpaqueValue_OpaqueValueWrapper(
+ checkNotNull(value), OpaqueType.create(name));
+ }
+
+ @AutoValue
+ @AutoValue.CopyAnnotations
+ @Immutable
+ @SuppressWarnings("Immutable")
+ abstract static class OpaqueValueWrapper extends OpaqueValue {
+ @Override
+ public abstract Object value();
+
+ @Override
+ public abstract OpaqueType celType();
}
}
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 cd7c24a63..76c761567 100644
--- a/common/src/test/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel
@@ -35,6 +35,7 @@ java_library(
"//testing/protos:test_all_types_cel_java_proto3",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
+ "@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_guava_guava_testlib",
"@maven//:com_google_protobuf_protobuf_java",
diff --git a/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java b/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java
index d97bcd28a..326572842 100644
--- a/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java
@@ -17,7 +17,17 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.bundle.Cel;
+import dev.cel.bundle.CelFactory;
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypeProvider;
import dev.cel.common.types.OpaqueType;
+import java.util.Map;
+import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -37,4 +47,177 @@ public void opaqueValue_construct() {
public void create_nullValue_throws() {
assertThrows(NullPointerException.class, () -> OpaqueValue.create("opaque_type_name", null));
}
+
+ private static final OpaqueType CUSTOM_OPAQUE_TYPE = OpaqueType.create("custom_opaque_type");
+
+ private static final CelTypeProvider CUSTOM_OPAQUE_TYPE_PROVIDER =
+ new CelTypeProvider() {
+ @Override
+ public ImmutableList types() {
+ return ImmutableList.of(CUSTOM_OPAQUE_TYPE);
+ }
+
+ @Override
+ public Optional findType(String typeName) {
+ return typeName.equals(CUSTOM_OPAQUE_TYPE.name())
+ ? Optional.of(CUSTOM_OPAQUE_TYPE)
+ : Optional.empty();
+ }
+ };
+
+ private static final CelValueProvider CUSTOM_OPAQUE_VALUE_PROVIDER =
+ new CelValueProvider() {
+ @Override
+ public Optional