Skip to content
Open
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
32 changes: 31 additions & 1 deletion policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ java_library(
],
deps = [
":compiler",
"//common:cel_ast",
"//optimizer:ast_optimizer",
"@maven//:com_google_errorprone_error_prone_annotations",
],
Expand Down Expand Up @@ -201,14 +202,18 @@ java_library(
":compiled_rule",
":compiler",
":compiler_builder",
":decision_linker",
":policy",
":rule_composer",
":source",
":validation_exception",
"//bundle:cel",
"//common:cel_ast",
"//common:cel_issue",
"//common:cel_source",
"//common:compiler_common",
"//common:cel_validation_exception",
"//common:cel_validation_result",
"//common:cel_var_decl",
"//common:container",
"//common:source_location",
"//common/ast",
Expand All @@ -221,6 +226,7 @@ java_library(
"//optimizer:optimizer_builder",
"//optimizer/optimizers:common_subexpression_elimination",
"//optimizer/optimizers:constant_folding",
"//optimizer/optimizers:select_optimizer",
"//validator",
"//validator:ast_validator",
"//validator:validator_builder",
Expand Down Expand Up @@ -268,3 +274,27 @@ java_library(
"@maven//:org_jspecify_jspecify",
],
)

java_library(
name = "decision_linker",
srcs = ["PolicyDecisionLinker.java"],
visibility = ["//visibility:private"],
deps = [
"//bundle:cel",
"//common:cel_ast",
"//common:cel_source",
"//common:cel_var_decl",
"//common:mutable_ast",
"//common/ast",
"//common/ast:cel_block",
"//common/ast:mutable_expr",
"//common/navigation",
"//common/navigation:mutable_navigation",
"//common/types:type_providers",
"//extensions:bindings",
"//optimizer:ast_optimizer",
"//optimizer:mutable_ast",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.optimizer.CelAstOptimizer;
import java.util.List;
import java.util.Map;

/** Interface for building an instance of {@link CelPolicyCompiler} */
public interface CelPolicyCompilerBuilder {
Expand All @@ -44,6 +46,13 @@ public interface CelPolicyCompilerBuilder {
@CanIgnoreReturnValue
CelPolicyCompilerBuilder setOptimizers(List<CelAstOptimizer> optimizers);

/**
* Statically links separately compiled policies, keyed by the qualified identifier the policy
* references them by (e.g. {@code decisions.channel}).
*/
@CanIgnoreReturnValue
CelPolicyCompilerBuilder addDecisions(Map<String, CelAbstractSyntaxTree> decisions);

@CheckReturnValue
CelPolicyCompiler build();
}
69 changes: 66 additions & 3 deletions policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@

package dev.cel.policy;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.stream.Collectors.partitioningBy;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
Expand All @@ -39,6 +42,7 @@
import dev.cel.optimizer.CelOptimizerFactory;
import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer;
import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer.ConstantFoldingOptions;
import dev.cel.optimizer.optimizers.SelectOptimizer;
import dev.cel.optimizer.optimizers.SubexpressionOptimizer;
import dev.cel.optimizer.optimizers.SubexpressionOptimizer.SubexpressionOptimizerOptions;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch;
Expand All @@ -57,6 +61,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/** Package-private implementation for policy compiler. */
Expand All @@ -68,6 +73,7 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
private final int iterationLimit;
private final ImmutableList<CelAstOptimizer> optimizers;
private final Optional<CelAstValidator> astDepthValidator;
private final ImmutableMap<String, CelAbstractSyntaxTree> decisions;

@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
Expand Down Expand Up @@ -144,8 +150,15 @@ public CelAbstractSyntaxTree compose(CelPolicy policy, CelCompiledRule compiledR
throw new CelPolicyValidationException("Unexpected error while composing rules.", e);
}

// Linking runs in a fresh environment after CSE, as it renumbers the cel.@block indices that
// CSE has already declared. SelectOptimizer must observe the linked AST.
Map<Boolean, List<CelAstOptimizer>> isPostLinkOptimizer =
optimizers.stream()
.collect(partitioningBy(o -> !decisions.isEmpty() && o instanceof SelectOptimizer));
CelOptimizer astOptimizer =
CelOptimizerFactory.standardCelOptimizerBuilder(cel).addAstOptimizers(optimizers).build();
CelOptimizerFactory.standardCelOptimizerBuilder(cel)
.addAstOptimizers(isPostLinkOptimizer.get(false))
.build();
try {
// Optimize the composed graph using const fold and CSE
ast = astOptimizer.optimize(ast);
Expand All @@ -154,6 +167,20 @@ public CelAbstractSyntaxTree compose(CelPolicy policy, CelCompiledRule compiledR
"Failed to optimize the composed policy. Reason: " + e.getMessage(), e);
}

if (!decisions.isEmpty()) {
CelOptimizer linkingOptimizer =
CelOptimizerFactory.standardCelOptimizerBuilder(cel)
.addAstOptimizers(PolicyDecisionLinker.newInstance(decisions, iterationLimit))
.addAstOptimizers(isPostLinkOptimizer.get(true))
.build();
try {
ast = linkingOptimizer.optimize(ast);
} catch (CelOptimizationException e) {
throw new CelPolicyValidationException(
"Failed to link policy decisions. Reason: " + e.getMessage(), e);
}
}

assertAstDepthIsSafe(ast, cel);

return ast;
Expand Down Expand Up @@ -370,10 +397,12 @@ static final class Builder implements CelPolicyCompilerBuilder {
private int iterationLimit;
private ImmutableList<CelAstOptimizer> optimizers;
private Optional<CelAstValidator> astDepthLimitValidator;
private final ImmutableMap.Builder<String, CelAbstractSyntaxTree> decisions;

private Builder(Cel cel) {
this.cel = cel;
this.astDepthLimitValidator = Optional.of(AstDepthLimitValidator.DEFAULT);
this.decisions = ImmutableMap.builder();
}

@Override
Expand Down Expand Up @@ -402,15 +431,47 @@ public Builder setAstDepthLimit(int astDepthLimit) {
}

@Override
@CanIgnoreReturnValue
public Builder setOptimizers(List<CelAstOptimizer> optimizers) {
this.optimizers = ImmutableList.copyOf(optimizers);
return this;
}

@Override
@CanIgnoreReturnValue
public Builder addDecisions(Map<String, CelAbstractSyntaxTree> decisions) {
this.decisions.putAll(checkNotNull(decisions));
return this;
}

@Override
public CelPolicyCompiler build() {
ImmutableMap<String, CelAbstractSyntaxTree> decisions = this.decisions.buildOrThrow();
ImmutableList.Builder<CelVarDecl> decisionDecls = ImmutableList.builder();
for (Map.Entry<String, CelAbstractSyntaxTree> decision : decisions.entrySet()) {
String name = decision.getKey();
// Qualified names cannot be captured by comprehension variables, and '@' is reserved for
// optimizer generated identifiers (e.g. @index0).
checkArgument(
name.contains(".") && !name.startsWith("@") && !name.startsWith(variablesPrefix),
"Invalid decision name: '%s'. Decision names must be qualified (e.g. decisions.foo).",
name);
checkArgument(decision.getValue().isChecked(), "Decision '%s' must be type-checked.", name);
decisionDecls.add(CelVarDecl.newVarDeclaration(name, decision.getValue().getResultType()));
}
// Without namespaced declarations, qualified decision references remain unlinked selects.
checkArgument(
decisions.isEmpty() || cel.toCheckerBuilder().options().enableNamespacedDeclarations(),
"Linking decisions requires enableNamespacedDeclarations.");
return new CelPolicyCompilerImpl(
cel, this.variablesPrefix, this.iterationLimit, this.optimizers, astDepthLimitValidator);
decisions.isEmpty()
? cel
: cel.toCelBuilder().addVarDeclarations(decisionDecls.build()).build(),
this.variablesPrefix,
this.iterationLimit,
this.optimizers,
astDepthLimitValidator,
decisions);
}
}

Expand All @@ -436,11 +497,13 @@ private CelPolicyCompilerImpl(
String variablesPrefix,
int iterationLimit,
ImmutableList<CelAstOptimizer> optimizers,
Optional<CelAstValidator> astDepthValidator) {
Optional<CelAstValidator> astDepthValidator,
ImmutableMap<String, CelAbstractSyntaxTree> decisions) {
this.cel = checkNotNull(cel);
this.variablesPrefix = checkNotNull(variablesPrefix);
this.iterationLimit = iterationLimit;
this.optimizers = optimizers;
this.astDepthValidator = astDepthValidator;
this.decisions = decisions;
}
}
Loading
Loading