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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
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
82 changes: 70 additions & 12 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.ObjIntConsumer;

/**
* CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
Expand All@@ -74,6 +75,24 @@ public abstract class CelEnvironment {
"strings", CanonicalCelExtension.STRINGS,
"comprehensions", CanonicalCelExtension.COMPREHENSIONS);

private static final ImmutableMap<String, ObjIntConsumer<CelOptions.Builder>> LIMIT_HANDLERS =
ImmutableMap.of(
"cel.limit.expression_code_points",
(options, value) -> options.maxExpressionCodePointSize(value),
"cel.limit.parse_error_recovery",
(options, value) -> options.maxParseErrorRecoveryLimit(value),
"cel.limit.parse_recursion_depth",
(options, value) -> options.maxParseRecursionDepth(value));

private static final ImmutableMap<String, BooleanOptionConsumer> FEATURE_HANDLERS =
ImmutableMap.of(
"cel.feature.macro_call_tracking",
(options, enabled) -> options.populateMacroCalls(enabled),
"cel.feature.backtick_escape_syntax",
(options, enabled) -> options.enableQuotedIdentifierSyntax(enabled),
"cel.feature.cross_type_numeric_comparisons",
(options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled));

/** Environment source in textual format (ex: textproto, YAML). */
public abstract Optional<Source> source();

Expand DownExpand Up@@ -112,6 +131,9 @@ public abstract class CelEnvironment {
/** Feature flags to enable in the environment. */
public abstract ImmutableSet<FeatureFlag> features();

/** Limits to set in the environment. */
public abstract ImmutableSet<Limit> limits();

/** Builder for {@link CelEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
Expand DownExpand Up@@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) {
return setFeatures(ImmutableSet.copyOf(featureFlags));
}

public abstract Builder setFeatures(ImmutableSet<FeatureFlag> macros);
public abstract Builder setFeatures(ImmutableSet<FeatureFlag> featureFlags);

@CanIgnoreReturnValue
public Builder setLimits(Limit... limits) {
return setLimits(ImmutableSet.copyOf(limits));
}

public abstract Builder setLimits(ImmutableSet<Limit> limits);

abstract CelEnvironment autoBuild();

Expand DownExpand Up@@ -200,13 +229,14 @@ public static Builder newBuilder() {
.setContainer(CelContainer.ofName(""))
.setVariables(ImmutableSet.of())
.setFunctions(ImmutableSet.of())
.setFeatures(ImmutableSet.of());
.setFeatures(ImmutableSet.of())
.setLimits(ImmutableSet.of());
}

/** Extends the provided {@link CelCompiler} environment with this configuration. */
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
CelCompilerBuilder compilerBuilder =
Expand DownExpand Up@@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)

/** Extends the provided {@link Cel} environment with this configuration. */
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
celOptions = applyFeatureFlags(celOptions);
celOptions = applyEnvironmentOptions(celOptions);
try {
// Casting is necessary to only extend the compiler here
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
Expand All@@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
}
}

private CelOptions applyFeatureFlags(CelOptions celOptions) {
private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
CelOptions.Builder optionsBuilder = celOptions.toBuilder();
for (FeatureFlag featureFlag : features()) {
if (featureFlag.name().equals("cel.feature.macro_call_tracking")) {
optionsBuilder.populateMacroCalls(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) {
optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled());
} else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) {
optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled());
} else {
BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
}
consumer.accept(optionsBuilder, featureFlag.enabled());
}
for (Limit limit : limits()) {
int value = limit.value() < 0 ? -1 : limit.value();
ObjIntConsumer<CelOptions.Builder> consumer = LIMIT_HANDLERS.get(limit.name());
if (consumer == null) {
throw new IllegalArgumentException("Unknown limit: " + limit.name());
}
consumer.accept(optionsBuilder, value);
}
return optionsBuilder.build();
}
Expand DownExpand Up@@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) {
}
}

/**
* Represents a configurable limit in the environment.
*
* <p>A negative value indicates no limit. If not specified, the limit should be set to the
* library default.
*/
@AutoValue
public abstract static class Limit {
/** Normalized name of the limit (e.g. cel.limit.expression_code_points */
public abstract String name();

/** The value of the limit, -1 means no limit. */
public abstract int value();

public static Limit create(String name, int value) {
return new AutoValue_CelEnvironment_Limit(name, value);
}
}

/**
* Represents a configuration for a canonical CEL extension that can be enabled in the
* environment.
Expand DownExpand Up@@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() {
}
}
}

@FunctionalInterface
private static interface BooleanOptionConsumer {
void accept(CelOptions.Builder options, boolean value);
}
}
17 changes: 17 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) {
featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
}
envBuilder.setFeatures(featureFlags.build());
ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();
if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.expression_code_points", options.maxExpressionCodePointSize()));
}
if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit()));
}
if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) {
limits.add(
CelEnvironment.Limit.create(
"cel.limit.parse_recursion_depth", options.maxParseRecursionDepth()));
}
envBuilder.setLimits(limits.build());
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.MappingNode;
Expand DownExpand Up@@ -188,6 +189,70 @@ private ImmutableSet<CelEnvironment.FeatureFlag> parseFeatures(
return featureFlags.build();
}

private ImmutableSet<CelEnvironment.Limit> parseLimits(ParserContext<Node> ctx, Node node) {
long valueId = ctx.collectMetadata(node);
if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) {
ctx.reportError(valueId, "Unsupported limits format");
}

ImmutableSet.Builder<CelEnvironment.Limit> limits = ImmutableSet.builder();

SequenceNode featureListNode = (SequenceNode) node;
for (Node featureMapNode : featureListNode.getValue()) {
long featureMapId = ctx.collectMetadata(featureMapNode);
if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) {
continue;
}

MappingNode featureMap = (MappingNode) featureMapNode;
String name = "";
Optional<Integer> value = Optional.empty();
// Shorthand syntax for limit: "cel.limit.foo: 1"
if (featureMap.getValue().size() == 1) {
NodeTuple nodeTuple = featureMap.getValue().get(0);
Node keyNode = nodeTuple.getKeyNode();
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
if (!keyName.equals("name") && !keyName.equals("value")) {
limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode)));
continue;
}
// Fall through to check against the long syntax.
}
// Long syntax for limit:
// limits:
// - name: cel.limit.foo
// value: 1
for (NodeTuple nodeTuple : featureMap.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(keyNode);
Node valueNode = nodeTuple.getValueNode();
String keyName = ((ScalarNode) keyNode).getValue();
switch (keyName) {
case "name":
name = newString(ctx, valueNode);
break;
case "value":
value = Optional.of(newInteger(ctx, valueNode));
break;
default:
ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName));
break;
}
}
if (name.isEmpty()) {
ctx.reportError(featureMapId, "Missing required attribute(s): name");
continue;
}
if (!value.isPresent()) {
ctx.reportError(featureMapId, "Missing required attribute(s): value");
continue;
}
limits.add(CelEnvironment.Limit.create(name, value.get()));
}
return limits.build();
}

private ImmutableSet<Alias> parseAliases(ParserContext<Node> ctx, Node node) {
ImmutableSet.Builder<Alias> aliasSetBuilder = ImmutableSet.builder();
long valueId = ctx.collectMetadata(node);
Expand DownExpand Up@@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
case "features":
builder.setFeatures(parseFeatures(ctx, valueNode));
break;
case "limits":
builder.setLimits(parseLimits(ctx, valueNode));
break;
default:
ctx.reportError(id, "Unknown config tag: " + fieldName);
// continue handling the rest of the nodes
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() {
this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias());
this.multiRepresenters.put(CelContainer.class, new RepresentContainer());
this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag());
this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit());
}

public static String toYaml(CelEnvironment environment) {
Expand DownExpand Up@@ -98,6 +99,9 @@ public Node representData(Object data) {
if (!environment.features().isEmpty()) {
configMap.put("features", environment.features().asList());
}
if (!environment.limits().isEmpty()) {
configMap.put("limits", environment.limits().asList());
}
return represent(configMap.buildOrThrow());
}
}
Expand DownExpand Up@@ -275,4 +279,17 @@ public Node representData(Object data) {
.buildOrThrow());
}
}

private final class RepresentLimit implements Represent {

@Override
public Node representData(Object data) {
CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
return represent(
ImmutableMap.builder()
.put("name", limit.name())
.put("value", limit.value() < 0 ? -1 : limit.value())
.buildOrThrow());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,5 +260,34 @@ public void container() {
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}

@Test
public void options() {
Cel cel =
CelFactory.standardCelBuilder()
.setOptions(
CelOptions.current()
.maxExpressionCodePointSize(100)
.maxParseErrorRecoveryLimit(10)
.maxParseRecursionDepth(10)
.enableQuotedIdentifierSyntax(true)
.enableHeterogeneousNumericComparisons(true)
.populateMacroCalls(true)
.build())
.build();

CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
assertThat(celEnvironment.features())
.containsExactly(
CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
assertThat(celEnvironment.limits())
.containsExactly(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
}
}

50 changes: 50 additions & 0 deletions bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception {
assertThat(result).isTrue();
}

@Test
public void extend_allLimits() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(
CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
.build();

Cel cel =
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT);
CelOptions checkerOptions = cel.toCheckerBuilder().options();
assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);

CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
Long result = (Long) cel.createProgram(ast).eval();
assertThat(result).isEqualTo(15L);

CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.contains("expression code point size exceeds limit: size: 21, limit 20");
}

@Test
public void extend_unsupportedFeatureFlag_throws() throws Exception {
CelEnvironment environment =
Expand All@@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception {
assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
}

@Test
public void extend_unsupportedLimit_throws() throws Exception {
CelEnvironment environment =
CelEnvironment.newBuilder()
.setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
.build();

IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
environment.extend(
CelFactory.standardCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.build(),
CelOptions.DEFAULT));
assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
}

@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
Expand Down
Loading
Loading