Skip to content

Commit 3167f60

Browse files
l46kokcopybara-github
authored andcommitted
Support for typename import aliases in policy compiler
PiperOrigin-RevId: 789401005
1 parent ca9b3f5 commit 3167f60

14 files changed

Lines changed: 201 additions & 13 deletions

File tree

checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public interface CelCheckerBuilder {
4949
@CanIgnoreReturnValue
5050
CelCheckerBuilder setContainer(CelContainer container);
5151

52+
/** Retrieves the currently configured {@link CelContainer} in the builder. */
53+
CelContainer container();
54+
5255
/** Add variable and function {@code declarations} to the CEL environment. */
5356
@CanIgnoreReturnValue
5457
CelCheckerBuilder addDeclarations(Decl... declarations);

checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ public CelCheckerBuilder setContainer(CelContainer container) {
210210
return this;
211211
}
212212

213+
@Override
214+
public CelContainer container() {
215+
return this.container;
216+
}
217+
213218
@Override
214219
public CelCheckerBuilder addDeclarations(Decl... declarations) {
215220
checkNotNull(declarations);

common/src/main/java/dev/cel/common/CelContainer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public Builder addAbbreviations(String... qualifiedNames) {
108108
* <p>If there is ever a case where an identifier could be in both the container and as an
109109
* abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
110110
* preserved between compilations even as the container evolves.
111+
*
112+
* @throws IllegalArgumentException If qualifiedName is invalid per above specification.
111113
*/
112114
@CanIgnoreReturnValue
113115
public Builder addAbbreviations(ImmutableSet<String> qualifiedNames) {
@@ -250,6 +252,8 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
250252
return candidates.add(typeName).build();
251253
}
252254

255+
public abstract Builder toBuilder();
256+
253257
public static Builder newBuilder() {
254258
return new AutoValue_CelContainer.Builder().setName("");
255259
}

policy/src/main/java/dev/cel/policy/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ java_library(
206206
"//common:cel_ast",
207207
"//common:cel_source",
208208
"//common:compiler_common",
209+
"//common:container",
209210
"//common:source_location",
210211
"//common/ast",
211212
"//common/formats:value_string",

policy/src/main/java/dev/cel/policy/CelPolicy.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public abstract class CelPolicy {
4646

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

49+
public abstract ImmutableList<Import> imports();
50+
4951
/** Creates a new builder to construct a {@link CelPolicy} instance. */
5052
public static Builder newBuilder() {
5153
return new AutoValue_CelPolicy.Builder()
@@ -74,6 +76,16 @@ public abstract static class Builder {
7476

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

79+
abstract ImmutableList.Builder<Import> importsBuilder();
80+
81+
abstract Builder setImports(ImmutableList<Import> value);
82+
83+
@CanIgnoreReturnValue
84+
public Builder addImport(Import value) {
85+
importsBuilder().add(value);
86+
return this;
87+
}
88+
7789
@CanIgnoreReturnValue
7890
public Builder putMetadata(String key, Object value) {
7991
metadataBuilder().put(key, value);
@@ -278,4 +290,16 @@ public static Builder newBuilder() {
278290
return new AutoValue_CelPolicy_Variable.Builder();
279291
}
280292
}
293+
294+
/** Import represents an imported type name which is aliased within CEL expressions. */
295+
@AutoValue
296+
public abstract static class Import {
297+
public abstract long id();
298+
299+
public abstract ValueString name();
300+
301+
public static Import create(long id, ValueString name) {
302+
return new AutoValue_CelPolicy_Import(id, name);
303+
}
304+
}
281305
}

policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2222
import dev.cel.bundle.Cel;
2323
import dev.cel.common.CelAbstractSyntaxTree;
24+
import dev.cel.common.CelContainer;
2425
import dev.cel.common.CelIssue;
2526
import dev.cel.common.CelSource;
2627
import dev.cel.common.CelSourceLocation;
@@ -39,6 +40,7 @@
3940
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result;
4041
import dev.cel.policy.CelCompiledRule.CelCompiledMatch.Result.Kind;
4142
import dev.cel.policy.CelCompiledRule.CelCompiledVariable;
43+
import dev.cel.policy.CelPolicy.Import;
4244
import dev.cel.policy.CelPolicy.Match;
4345
import dev.cel.policy.CelPolicy.Variable;
4446
import dev.cel.policy.RuleComposer.RuleCompositionException;
@@ -63,7 +65,28 @@ final class CelPolicyCompilerImpl implements CelPolicyCompiler {
6365
@Override
6466
public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationException {
6567
CompilerContext compilerContext = new CompilerContext(policy.policySource());
66-
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), cel, compilerContext);
68+
69+
Cel extendedCel = this.cel;
70+
71+
if (!policy.imports().isEmpty()) {
72+
CelContainer.Builder containerBuilder =
73+
extendedCel.toCheckerBuilder().container().toBuilder();
74+
75+
for (Import imp : policy.imports()) {
76+
try {
77+
containerBuilder.addAbbreviations(imp.name().value());
78+
} catch (IllegalArgumentException e) {
79+
compilerContext.addIssue(
80+
imp.id(),
81+
CelIssue.formatError(
82+
1, 0, String.format("Error configuring import: %s", e.getMessage())));
83+
}
84+
}
85+
86+
extendedCel = extendedCel.toCelBuilder().setContainer(containerBuilder.build()).build();
87+
}
88+
89+
CelCompiledRule compiledRule = compileRuleImpl(policy.rule(), extendedCel, compilerContext);
6790
if (compilerContext.hasError()) {
6891
throw new CelPolicyValidationException(compilerContext.getIssueString());
6992
}

policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import dev.cel.common.formats.YamlHelper.YamlNodeType;
2828
import dev.cel.common.formats.YamlParserContextImpl;
2929
import dev.cel.common.internal.CelCodePointArray;
30+
import dev.cel.policy.CelPolicy.Import;
3031
import dev.cel.policy.CelPolicy.Match;
3132
import dev.cel.policy.CelPolicy.Match.Result;
3233
import dev.cel.policy.CelPolicy.Variable;
@@ -118,6 +119,9 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
118119
Node valueNode = nodeTuple.getValueNode();
119120
String fieldName = ((ScalarNode) keyNode).getValue();
120121
switch (fieldName) {
122+
case "imports":
123+
parseImports(policyBuilder, ctx, valueNode);
124+
break;
121125
case "name":
122126
policyBuilder.setName(ctx.newValueString(valueNode));
123127
break;
@@ -141,6 +145,51 @@ public CelPolicy parsePolicy(PolicyParserContext<Node> ctx, Node node) {
141145
.build();
142146
}
143147

148+
private void parseImports(
149+
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
150+
long id = ctx.collectMetadata(node);
151+
if (!assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
152+
return;
153+
}
154+
155+
SequenceNode importListNode = (SequenceNode) node;
156+
for (Node importNode : importListNode.getValue()) {
157+
parseImport(policyBuilder, ctx, importNode);
158+
}
159+
}
160+
161+
private void parseImport(
162+
CelPolicy.Builder policyBuilder, PolicyParserContext<Node> ctx, Node node) {
163+
long importId = ctx.collectMetadata(node);
164+
if (!assertYamlType(ctx, importId, node, YamlNodeType.MAP)) {
165+
return;
166+
}
167+
168+
MappingNode mappingNode = (MappingNode) node;
169+
for (NodeTuple nodeTuple : mappingNode.getValue()) {
170+
Node key = nodeTuple.getKeyNode();
171+
long keyId = ctx.collectMetadata(key);
172+
if (!assertYamlType(ctx, keyId, key, YamlNodeType.STRING, YamlNodeType.TEXT)) {
173+
continue;
174+
}
175+
176+
String fieldName = ((ScalarNode) key).getValue();
177+
if (!fieldName.equals("name")) {
178+
ctx.reportError(
179+
keyId, String.format("Invalid import key: %s, expected 'name'", fieldName));
180+
continue;
181+
}
182+
183+
Node value = nodeTuple.getValueNode();
184+
long valueId = ctx.collectMetadata(value);
185+
if (!assertYamlType(ctx, valueId, value, YamlNodeType.STRING, YamlNodeType.TEXT)) {
186+
continue;
187+
}
188+
189+
policyBuilder.addImport(Import.create(valueId, ctx.newValueString(value)));
190+
}
191+
}
192+
144193
@Override
145194
public CelPolicy.Rule parseRule(
146195
PolicyParserContext<Node> ctx, CelPolicy.Builder policyBuilder, Node node) {

policy/src/test/java/dev/cel/policy/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ java_library(
1919
"//common:options",
2020
"//common/formats:value_string",
2121
"//common/internal",
22+
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
2223
"//compiler",
2324
"//extensions:optional_library",
2425
"//parser:macro",

policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
4444
import dev.cel.runtime.CelFunctionBinding;
4545
import dev.cel.runtime.CelLateFunctionBindings;
46+
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
4647
import java.io.IOException;
4748
import java.util.Map;
4849
import java.util.Optional;
@@ -290,6 +291,7 @@ private static Cel newCel() {
290291
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
291292
.addCompilerLibraries(CelOptionalLibrary.INSTANCE)
292293
.addRuntimeLibraries(CelOptionalLibrary.INSTANCE)
294+
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
293295
.addMessageTypes(TestAllTypes.getDescriptor())
294296
.setOptions(CEL_OPTIONS)
295297
.addFunctionBindings(

policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.testing.junit.testparameterinjector.TestParameter;
2222
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
2323
import dev.cel.common.formats.ValueString;
24+
import dev.cel.policy.CelPolicy.Import;
2425
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
2526
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
2627
import org.junit.Test;
@@ -129,6 +130,24 @@ public void parseYamlPolicy_withExplanation() throws Exception {
129130
.hasValue(ValueString.of(11, "'custom explanation'"));
130131
}
131132

133+
@Test
134+
public void parseYamlPolicy_withImports() throws Exception {
135+
String policySource =
136+
"name: 'policy_with_imports'\n"
137+
+ "imports:\n"
138+
+ "- name: foo\n"
139+
+ "- name: >\n"
140+
+ " bar";
141+
142+
CelPolicy policy = POLICY_PARSER.parse(policySource);
143+
144+
assertThat(policy.imports())
145+
.containsExactly(
146+
Import.create(8L, ValueString.of(9L, "foo")),
147+
Import.create(12L, ValueString.of(13L, " bar")))
148+
.inOrder();
149+
}
150+
132151
@Test
133152
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
134153
CelPolicyValidationException e =
@@ -318,7 +337,32 @@ private enum PolicyParseErrorTestCase {
318337
+ " [tag:yaml.org,2002:str !txt]\n"
319338
+ " | description: 1\n"
320339
+ " | ...............^"),
321-
;
340+
ILLEGAL_YAML_TYPE_IMPORT_EXPECTED_LIST(
341+
"imports: foo",
342+
"ERROR: <input>:1:10: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
343+
+ " [tag:yaml.org,2002:seq]\n"
344+
+ " | imports: foo\n"
345+
+ " | .........^"),
346+
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_EXPECTED_MAP(
347+
"imports:\n" //
348+
+ "- foo",
349+
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
350+
+ " [tag:yaml.org,2002:map]\n"
351+
+ " | - foo\n"
352+
+ " | ..^"),
353+
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_KEY(
354+
"imports:\n" //
355+
+ "- 1: 2",
356+
"ERROR: <input>:2:3: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
357+
+ " [tag:yaml.org,2002:str !txt]\n"
358+
+ " | - 1: 2\n"
359+
+ " | ..^"),
360+
ILLEGAL_YAML_TYPE_IMPORT_ELEMENT_MAP_INVALID_VALUE_NAME(
361+
"imports:\n" //
362+
+ "- foo: bar",
363+
"ERROR: <input>:2:3: Invalid import key: foo, expected 'name'\n"
364+
+ " | - foo: bar\n"
365+
+ " | ..^");
322366

323367
private final String yamlPolicy;
324368
private final String expectedErrorMessage;

0 commit comments

Comments
 (0)