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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
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
3 changes: 3 additions & 0 deletions checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setContainer(CelContainer container);

/** Retrieves the currently configured {@link CelContainer} in the builder. */
CelContainer container();

/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
CelCheckerBuilder addDeclarations(Decl... declarations);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
return this;
}

@Override
public CelContainer container() {
return this.container;
}

@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/CelContainer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
* <p>If there is ever a case where an identifier could be in both the container and as an
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
* preserved between compilations even as the container evolves.
*
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
*/
@CanIgnoreReturnValue
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
Expand DownExpand Up@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
return candidates.add(typeName).build();
}

public abstract Builder toBuilder();

public static Builder newBuilder() {
return new AutoValue_CelContainer.Builder().setName("");
}
Expand Down
1 change: 1 addition & 0 deletions policy/src/main/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ java_library(
"//common:cel_ast",
"//common:cel_source",
"//common:compiler_common",
"//common:container",
"//common:source_location",
"//common/ast",
"//common/formats:value_string",
Expand Down
24 changes: 24 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicy.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,8 @@ public abstract class CelPolicy {

public abstract ImmutableMap<String, Object> metadata();

public abstract ImmutableList<Import> imports();

/** Creates a new builder to construct a {@link CelPolicy} instance. */
public static Builder newBuilder() {
return new AutoValue_CelPolicy.Builder()
Expand DownExpand Up@@ -74,6 +76,16 @@ public abstract static class Builder {

public abstract Builder setMetadata(ImmutableMap<String, Object> value);

abstract ImmutableList.Builder<Import> importsBuilder();

abstract Builder setImports(ImmutableList<Import> value);

@CanIgnoreReturnValue
public Builder addImport(Import value) {
importsBuilder().add(value);
return this;
}

@CanIgnoreReturnValue
public Builder putMetadata(String key, Object value) {
metadataBuilder().put(key, value);
Expand DownExpand Up@@ -278,4 +290,16 @@ public static Builder newBuilder() {
return new AutoValue_CelPolicy_Variable.Builder();
}
}

/** Import represents an imported type name which is aliased within CEL expressions. */
@AutoValue
public abstract static class Import {
public abstract long id();

public abstract ValueString name();

public static Import create(long id, ValueString name) {
return new AutoValue_CelPolicy_Import(id, name);
}
}
}
25 changes: 24 additions & 1 deletion policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelIssue;
import dev.cel.common.CelSource;
import dev.cel.common.CelSourceLocation;
Expand All@@ -39,6 +40,7 @@
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Variable;
import dev.cel.policy.RuleComposer.RuleCompositionException;
Expand All@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
@Override
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
CompilerContext compilerContext = new CompilerContext(policy.policySource());
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);

Cel extendedCel = this.cel;

if (!policy.imports().isEmpty()) {
CelContainer.Builder containerBuilder =
extendedCel.toCheckerBuilder().container().toBuilder();

for (Import imp : policy.imports()) {
try {
containerBuilder.addAbbreviations(imp.name().value());
} catch (IllegalArgumentException e) {
compilerContext.addIssue(
imp.id(),
CelIssue.formatError(
1, 0, String.format("Error configuring import: %s", e.getMessage())));
}
}

extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
}

CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
if (compilerContext.hasError()) {
throw new CelPolicyValidationException(compilerContext.getIssueString());
}
Expand Down
49 changes: 49 additions & 0 deletions policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@
import dev.cel.common.formats.YamlHelper.YamlNodeType;
import dev.cel.common.formats.YamlParserContextImpl;
import dev.cel.common.internal.CelCodePointArray;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.CelPolicy.Match;
import dev.cel.policy.CelPolicy.Match.Result;
import dev.cel.policy.CelPolicy.Variable;
Expand DownExpand Up@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
Node valueNode = nodeTuple.getValueNode();
String fieldName = ((ScalarNode) keyNode).getValue();
switch (fieldName) {
case "imports":
parseImports(policyBuilder, ctx, valueNode);
break;
case "name":
policyBuilder.setName(ctx.newValueString(valueNode));
break;
Expand All@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
.build();
}

private void parseImports(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long id = ctx.collectMetadata(node);
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
return;
}

SequenceNode importListNode = (SequenceNode) node;
for (Node importNode : importListNode.getValue()) {
parseImport(policyBuilder, ctx, importNode);
}
}

private void parseImport(
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
long importId = ctx.collectMetadata(node);
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
return;
}

MappingNode mappingNode = (MappingNode) node;
for (NodeTuple nodeTuple : mappingNode.getValue()) {
Node key = nodeTuple.getKeyNode();
long keyId = ctx.collectMetadata(key);
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

String fieldName = ((ScalarNode) key).getValue();
if (!fieldName.equals("name")) {
ctx.reportError(
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
continue;
}

Node value = nodeTuple.getValueNode();
long valueId = ctx.collectMetadata(value);
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
continue;
}

policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
}
}

@Override
public CelPolicy.Rule parseRule(
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {
Expand Down
1 change: 1 addition & 0 deletions policy/src/test/java/dev/cel/policy/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ java_library(
"//common:options",
"//common/formats:value_string",
"//common/internal",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
"//compiler",
"//extensions:optional_library",
"//parser:macro",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
Expand DownExpand Up@@ -290,6 +291,7 @@ private static Cel newCel() {
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.addMessageTypes(TestAllTypes.getDescriptor())
.setOptions(CEL_OPTIONS)
.addFunctionBindings(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.formats.ValueString;
import dev.cel.policy.CelPolicy.Import;
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
import org.junit.Test;
Expand DownExpand Up@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
.hasValue(ValueString.of(11, "'custom explanation'"));
}

@Test
public void parseYamlPolicy_withImports() throws Exception {
String policySource =
"name: 'policy_with_imports'\n"
+ "imports:\n"
+ "- name: foo\n"
+ "- name: >\n"
+ " bar";

CelPolicy policy = POLICY_PARSER.parse(policySource);

assertThat(policy.imports())
.containsExactly(
Import.create(8L, ValueString.of(9L, "foo")),
Import.create(12L, ValueString.of(13L, " bar")))
.inOrder();
}

@Test
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
CelPolicyValidationException e =
Expand DownExpand Up@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | description: 1\n"
+ " | ...............^"),
;
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
"imports: foo",
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:seq]\n"
+ " | imports: foo\n"
+ " | .........^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
"imports:\n" //
+ "- foo",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ " [tag:yaml.org,2002:map]\n"
+ " | - foo\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
"imports:\n" //
+ "- 1: 2",
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ " [tag:yaml.org,2002:str !txt]\n"
+ " | - 1: 2\n"
+ " | ..^"),
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
"imports:\n" //
+ "- foo: bar",
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
+ " | - foo: bar\n"
+ " | ..^");

private final String yamlPolicy;
private final String expectedErrorMessage;
Expand Down
9 changes: 7 additions & 2 deletions policy/src/test/java/dev/cel/policy/PolicyTestHelper.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,8 +106,13 @@ enum TestYamlPolicy {
PB(
"pb",
true,
"(spec.single_int32 > 10) ? optional.of(\"invalid spec, got single_int32=\" +"
+ " string(spec.single_int32) + \", wanted <= 10\") : optional.none()"),
"(spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64) ? optional.of(\"invalid"
+ " spec, got single_int32=\" + string(spec.single_int32) + \", wanted <= 10\") :"
+ " ((spec.standalone_enum == cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR"
+ " || dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR =="
+ " dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO) ? optional.of(\"invalid"
+ " spec, neither nested nor imported enums may refer to BAR\") :"
+ " optional.none())"),
LIMITS(
"limits",
true,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
ERROR: compile_errors/policy.yaml:19:19: undeclared reference to 'spec' (in container '')
ERROR: compile_errors/policy.yaml:19:5: Error configuring import: invalid qualified name: punc.Import!, wanted name of the form 'qualified.name'
| punc.Import!
| ....^
ERROR: compile_errors/policy.yaml:20:10: Error configuring import: invalid qualified name: bad import, wanted name of the form 'qualified.name'
| - name: "bad import"
| .........^
ERROR: compile_errors/policy.yaml:24:19: undeclared reference to 'spec' (in container '')
| expression: spec.labels
| ..................^
ERROR: compile_errors/policy.yaml:21:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
ERROR: compile_errors/policy.yaml:26:50: mismatched input 'resource' expecting {'==', '!=', 'in', '<', '<=', '>=', '>', '&&', '||', '[', ')', '.', '-', '?', '+', '*', '/', '%%'}
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................^
ERROR: compile_errors/policy.yaml:21:66: extraneous input ')' expecting <EOF>
ERROR: compile_errors/policy.yaml:26:66: extraneous input ')' expecting <EOF>
| expression: variables.want.filter(l, !(lin resource.labels))
| .................................................................^
ERROR: compile_errors/policy.yaml:23:27: mismatched input '2' expecting {'}', ','}
ERROR: compile_errors/policy.yaml:28:27: mismatched input '2' expecting {'}', ','}
| expression: "{1:305 2:569}"
| ..........................^
ERROR: compile_errors/policy.yaml:31:75: extraneous input ']' expecting ')'
ERROR: compile_errors/policy.yaml:36:75: extraneous input ']' expecting ')'
| "missing one or more required labels: %s".format(variables.missing])
| ..........................................................................^
ERROR: compile_errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
ERROR: compile_errors/policy.yaml:39:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^
ERROR: compile_errors/policy.yaml:35:19: condition must produce a boolean output.
ERROR: compile_errors/policy.yaml:40:19: condition must produce a boolean output.
| - condition: '1'
| ..................^
ERROR: compile_errors/policy.yaml:38:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
ERROR: compile_errors/policy.yaml:43:24: found no matching overload for '_==_' applied to '(bool, string)' (candidates: (%A0, %A0))
| - condition: false == "0"
| .......................^
5 changes: 5 additions & 0 deletions testing/src/test/resources/policy/compile_errors/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,11 @@
# limitations under the License.

name: "errors"
imports:
- name: " untrimmed.Import1 "
- name: >
punc.Import!
- name: "bad import"
rule:
variables:
- name: want
Expand Down
18 changes: 17 additions & 1 deletion testing/src/test/resources/policy/pb/policy.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,24 @@
# limitations under the License.

name: "pb"

imports:
- name: cel.expr.conformance.proto3.TestAllTypes
- name: cel.expr.conformance.proto3.TestAllTypes.NestedEnum
# Note: Following enum is CEL-Java only.
- name: |
dev.cel.testing.testdata.proto3.StandaloneGlobalEnum

rule:
match:
- condition: spec.single_int32 > 10
- condition: >
spec.single_int32 > TestAllTypes{single_int64: 10}.single_int64
output: |
"invalid spec, got single_int32=" + string(spec.single_int32) + ", wanted <= 10"
# TODO: replace when string.format is available
# "invalid spec, got single_int32=%d, wanted <= 10".format([spec.single_int32])
- condition: >
spec.standalone_enum == NestedEnum.BAR ||
StandaloneGlobalEnum.SGAR == StandaloneGlobalEnum.SGOO
output: |
"invalid spec, neither nested nor imported enums may refer to BAR"
Loading