Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -329,6 +329,7 @@ java_library(
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/exceptions:invalid_argument",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All@@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand DownExpand Up@@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand DownExpand Up@@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand DownExpand Up@@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand DownExpand Up@@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All@@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All@@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand DownExpand Up@@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand DownExpand Up@@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
import dev.cel.common.CelContainer;
import dev.cel.common.CelValidationException;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
Expand DownExpand Up@@ -90,7 +91,8 @@ public final class CelNativeTypesExtensionsTest {
TestGetterFieldTypeMismatchPojo.class,
TestAbstractPojo.class,
TestURLPojo.class,
PojoWithEnum.class);
PojoWithEnum.class,
TestArrayPojo.class);

private static final Cel CEL =
CelFactory.plannerCelBuilder()
Expand DownExpand Up@@ -323,10 +325,10 @@ public void nativeTypes_anonymousClass_throwsException() {

@Test
public void nativeTypes_createStruct_privateConstructor() throws Exception {
Object result = eval("TestPrivateConstructorPojo{value:" + " 'hello'}");
TestPrivateConstructorPojo result =
(TestPrivateConstructorPojo) eval("TestPrivateConstructorPojo{value:" + " 'hello'}");

assertThat(result).isInstanceOf(TestPrivateConstructorPojo.class);
assertThat(((TestPrivateConstructorPojo) result).value).isEqualTo("hello");
assertThat(result.value).isEqualTo("hello");
}

@Test
Expand DownExpand Up@@ -375,10 +377,9 @@ public void nativeTypes_missingNoArgConstructor_throws() throws Exception {

@Test
public void nativeTypes_createWithDeepConversion() throws Exception {
Object result = eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");

assertThat(result).isInstanceOf(TestDeepConversionPojo.class);
TestDeepConversionPojo pojo = (TestDeepConversionPojo) result;
TestDeepConversionPojo pojo =
(TestDeepConversionPojo)
eval("TestDeepConversionPojo{ints: [1, 2], floats: {'a': 1.0, 'b': 2.0}}");
assertThat(pojo.ints.get(0)).isEqualTo(1);
assertThat(pojo.floats).containsEntry("a", 1.0f);
}
Expand All@@ -398,11 +399,92 @@ public void nativeTypes_unsupportedTypeSet_throwsOnRegistration() throws Excepti
}

@Test
public void nativeTypes_arrayType_throwsOnRegistration() throws Exception {
IllegalArgumentException e =
public void nativeTypes_arrayType_construction() throws Exception {
String expr =
"TestArrayPojo{"
+ " strings: ['a', 'b'],"
+ " ints: [1, 2],"
+ " nesteds: [TestNestedType{value: 'nested'}],"
+ " matrix: [[1, 2], [3, 4]],"
+ " nestedMatrix: [[TestNestedType{value: 'm1'}], [TestNestedType{value: 'm2'}]],"
+ " byteArrays: [b'foo', b'bar']"
+ "}";

TestArrayPojo pojo = (TestArrayPojo) eval(expr);

assertThat(pojo.strings).isEqualTo(new String[] {"a", "b"});
assertThat(pojo.ints).isEqualTo(new int[] {1, 2});
assertThat(pojo.nesteds).hasLength(1);
assertThat(pojo.nesteds[0].value).isEqualTo("nested");
assertThat(pojo.matrix).hasLength(2);
assertThat(pojo.matrix[0]).isEqualTo(new int[] {1, 2});
assertThat(pojo.matrix[1]).isEqualTo(new int[] {3, 4});
assertThat(pojo.nestedMatrix).hasLength(2);
assertThat(pojo.nestedMatrix[0][0].value).isEqualTo("m1");
assertThat(pojo.nestedMatrix[1][0].value).isEqualTo("m2");
assertThat(pojo.byteArrays).hasLength(2);
assertThat(pojo.byteArrays[0]).isEqualTo("foo".getBytes(UTF_8));
assertThat(pojo.byteArrays[1]).isEqualTo("bar".getBytes(UTF_8));
}

@Test
public void nativeTypes_arrayType_selection() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
String expr =
"pojo.strings[1] == 'b'"
+ " && pojo.ints[0] == 1"
+ " && pojo.nesteds[0].value == 'nested'"
+ " && pojo.matrix[1][0] == 3"
+ " && pojo.nestedMatrix[1][0].value == 'm2'"
+ " && pojo.byteArrays[1] == b'bar'";
CelAbstractSyntaxTree ast = cel.compile(expr).getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", "b"};
input.ints = new int[] {1, 2};
TestNestedType nested = new TestNestedType();
nested.value = "nested";
input.nesteds = new TestNestedType[] {nested};
input.matrix = new int[][] {{1, 2}, {3, 4}};
TestNestedType m1 = new TestNestedType();
m1.value = "m1";
TestNestedType m2 = new TestNestedType();
m2.value = "m2";
input.nestedMatrix = new TestNestedType[][] {{m1}, {m2}};
input.byteArrays = new byte[][] {"foo".getBytes(UTF_8), "bar".getBytes(UTF_8)};

assertThat(program.eval(ImmutableMap.of("pojo", input))).isEqualTo(true);
}

@Test
public void nativeTypes_arrayWithNullElement_throws() throws Exception {
CelNativeTypesExtensions extensions = CelExtensions.nativeTypes(TestArrayPojo.class);
Cel cel =
CelFactory.plannerCelBuilder()
.setContainer(CelContainer.ofName("dev.cel.extensions.CelNativeTypesExtensionsTest"))
.addCompilerLibraries(extensions)
.addRuntimeLibraries(extensions)
.addVar("pojo", StructTypeReference.create(TestArrayPojo.class.getCanonicalName()))
.build();
CelAbstractSyntaxTree ast = cel.compile("pojo.strings").getAst();
CelRuntime.Program program = cel.createProgram(ast);

TestArrayPojo input = new TestArrayPojo();
input.strings = new String[] {"a", null, "c"};

CelEvaluationException e =
assertThrows(
IllegalArgumentException.class, () -> CelExtensions.nativeTypes(TestArrayPojo.class));
assertThat(e).hasMessageThat().contains("Unsupported type for property 'values'");
CelEvaluationException.class, () -> program.eval(ImmutableMap.of("pojo", input)));
assertThat(e).hasCauseThat().isInstanceOf(CelInvalidArgumentException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("Element at index 1 is null.");
}

@Test
Expand DownExpand Up@@ -662,10 +744,7 @@ public void nativeTypes_createWithUint_fromUnsignedLong() throws Exception {
.getAst();
CelRuntime.Program program = celRuntime.createProgram(ast);

Object result = program.eval();

assertThat(result).isInstanceOf(TestAllTypesPublicFieldsPojo.class);
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) result;
TestAllTypesPublicFieldsPojo pojo = (TestAllTypesPublicFieldsPojo) program.eval();
assertThat(pojo.uintVal).isEqualTo(UnsignedLong.fromLongBits(42L));
}

Expand DownExpand Up@@ -782,6 +861,8 @@ public void nativeTypes_nullSafeTraversal() throws Exception {
assertThat(cel.createProgram(cel.compile("pojo.int64Val").getAst()).eval(vars)).isEqualTo(0L);
assertThat(cel.createProgram(cel.compile("pojo.nestedVal.value").getAst()).eval(vars))
.isEqualTo("");
assertThat(cel.createProgram(cel.compile("size(pojo.arrayVal) == 0").getAst()).eval(vars))
.isEqualTo(true);
CelAbstractSyntaxTree abstractPojoAst = cel.compile("pojo.abstractPojo.value").getAst();
CelRuntime.Program abstractPojoProgram = cel.createProgram(abstractPojoAst);
CelEvaluationException e =
Expand DownExpand Up@@ -942,6 +1023,7 @@ public String get() {
public double doubleVal;
public float floatVal;
public byte[] bytesVal;
public String[] arrayVal;
public Duration durationVal;
public Instant timestampVal;
public TestNestedType nestedVal;
Expand DownExpand Up@@ -1259,7 +1341,12 @@ public static class TestWildcardPojo {
}

public static class TestArrayPojo {
public String[] values;
public String[] strings;
public int[] ints;
public TestNestedType[] nesteds;
public int[][] matrix;
public TestNestedType[][] nestedMatrix;
public byte[][] byteArrays;
}

public static class TestOptionalUrlPojo {
Expand Down
Loading