Skip to content

Commit cd44aab

Browse files
l46kokcopybara-github
authored andcommitted
Add capability to accept a CEL environment in CelCompilerTool
PiperOrigin-RevId: 730675994
1 parent 11217ee commit cd44aab

14 files changed

Lines changed: 345 additions & 95 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ java_library(
6464
"//common:source",
6565
"//common/types",
6666
"//common/types:type_providers",
67+
"//compiler:compiler_builder",
6768
"//extensions",
6869
"//extensions:optional_library",
70+
"//runtime",
6971
"@maven//:com_google_errorprone_error_prone_annotations",
7072
"@maven//:com_google_guava_guava",
7173
],

bundle/src/main/java/dev/cel/bundle/CelEnvironment.java

Lines changed: 122 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import static com.google.common.collect.ImmutableList.toImmutableList;
2020

2121
import com.google.auto.value.AutoValue;
22+
import com.google.common.annotations.VisibleForTesting;
2223
import com.google.common.collect.ImmutableList;
24+
import com.google.common.collect.ImmutableMap;
2325
import com.google.common.collect.ImmutableSet;
2426
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2527
import com.google.errorprone.annotations.CheckReturnValue;
@@ -35,8 +37,11 @@
3537
import dev.cel.common.types.OptionalType;
3638
import dev.cel.common.types.SimpleType;
3739
import dev.cel.common.types.TypeParamType;
40+
import dev.cel.compiler.CelCompiler;
41+
import dev.cel.compiler.CelCompilerBuilder;
3842
import dev.cel.extensions.CelExtensions;
3943
import dev.cel.extensions.CelOptionalLibrary;
44+
import dev.cel.runtime.CelRuntimeBuilder;
4045
import java.util.Arrays;
4146
import java.util.Optional;
4247

@@ -47,6 +52,18 @@
4752
@AutoValue
4853
public abstract class CelEnvironment {
4954

55+
@VisibleForTesting
56+
static final ImmutableMap<String, CanonicalCelExtension> CEL_EXTENSION_CONFIG_MAP =
57+
ImmutableMap.of(
58+
"bindings", CanonicalCelExtension.BINDINGS,
59+
"encoders", CanonicalCelExtension.ENCODERS,
60+
"lists", CanonicalCelExtension.LISTS,
61+
"math", CanonicalCelExtension.MATH,
62+
"optional", CanonicalCelExtension.OPTIONAL,
63+
"protos", CanonicalCelExtension.PROTOS,
64+
"sets", CanonicalCelExtension.SETS,
65+
"strings", CanonicalCelExtension.STRINGS);
66+
5067
/** Environment source in textual format (ex: textproto, YAML). */
5168
public abstract Optional<Source> source();
5269

@@ -126,12 +143,14 @@ public static Builder newBuilder() {
126143
.setFunctions(ImmutableSet.of());
127144
}
128145

129-
/** Extends the provided {@code cel} environment with this configuration. */
130-
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
146+
/** Extends the provided {@link CelCompiler} environment with this configuration. */
147+
public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
148+
throws CelEnvironmentException {
131149
try {
132-
CelTypeProvider celTypeProvider = cel.getTypeProvider();
133-
CelBuilder celBuilder =
134-
cel.toCelBuilder()
150+
CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
151+
CelCompilerBuilder compilerBuilder =
152+
celCompiler
153+
.toCompilerBuilder()
135154
.setTypeProvider(celTypeProvider)
136155
.setContainer(container())
137156
.addVarDeclarations(
@@ -144,53 +163,58 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException
144163
.collect(toImmutableList()));
145164

146165
if (!container().isEmpty()) {
147-
celBuilder.setContainer(container());
166+
compilerBuilder.setContainer(container());
148167
}
149168

150-
addAllExtensions(celBuilder, celOptions);
169+
addAllCompilerExtensions(compilerBuilder, celOptions);
170+
171+
return compilerBuilder.build();
172+
} catch (RuntimeException e) {
173+
throw new CelEnvironmentException(e.getMessage(), e);
174+
}
175+
}
176+
177+
/** Extends the provided {@link Cel} environment with this configuration. */
178+
public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
179+
try {
180+
// Casting is necessary to only extend the compiler here
181+
CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
182+
183+
CelRuntimeBuilder celRuntimeBuilder = cel.toRuntimeBuilder();
184+
addAllRuntimeExtensions(celRuntimeBuilder, celOptions);
151185

152-
return celBuilder.build();
186+
return CelFactory.combine(celCompiler, celRuntimeBuilder.build());
153187
} catch (RuntimeException e) {
154188
throw new CelEnvironmentException(e.getMessage(), e);
155189
}
156190
}
157191

158-
private void addAllExtensions(CelBuilder celBuilder, CelOptions celOptions) {
192+
private void addAllCompilerExtensions(
193+
CelCompilerBuilder celCompilerBuilder, CelOptions celOptions) {
159194
// TODO: Add capability to accept user defined exceptions
160195
for (ExtensionConfig extensionConfig : extensions()) {
161-
switch (extensionConfig.name()) {
162-
case "bindings":
163-
celBuilder.addCompilerLibraries(CelExtensions.bindings());
164-
break;
165-
case "encoders":
166-
celBuilder.addCompilerLibraries(CelExtensions.encoders());
167-
celBuilder.addRuntimeLibraries(CelExtensions.encoders());
168-
break;
169-
case "math":
170-
celBuilder.addCompilerLibraries(CelExtensions.math(celOptions));
171-
celBuilder.addRuntimeLibraries(CelExtensions.math(celOptions));
172-
break;
173-
case "optional":
174-
celBuilder.addCompilerLibraries(CelOptionalLibrary.INSTANCE);
175-
celBuilder.addRuntimeLibraries(CelOptionalLibrary.INSTANCE);
176-
break;
177-
case "protos":
178-
celBuilder.addCompilerLibraries(CelExtensions.protos());
179-
break;
180-
case "strings":
181-
celBuilder.addCompilerLibraries(CelExtensions.strings());
182-
celBuilder.addRuntimeLibraries(CelExtensions.strings());
183-
break;
184-
case "sets":
185-
celBuilder.addCompilerLibraries(CelExtensions.sets(celOptions));
186-
celBuilder.addRuntimeLibraries(CelExtensions.sets(celOptions));
187-
break;
188-
default:
189-
throw new IllegalArgumentException("Unrecognized extension: " + extensionConfig.name());
190-
}
196+
CanonicalCelExtension extension = getExtensionOrThrow(extensionConfig.name());
197+
extension.addCompilerExtension(celCompilerBuilder, celOptions);
198+
}
199+
}
200+
201+
private void addAllRuntimeExtensions(CelRuntimeBuilder celRuntimeBuilder, CelOptions celOptions) {
202+
// TODO: Add capability to accept user defined exceptions
203+
for (ExtensionConfig extensionConfig : extensions()) {
204+
CanonicalCelExtension extension = getExtensionOrThrow(extensionConfig.name());
205+
extension.addRuntimeExtension(celRuntimeBuilder, celOptions);
191206
}
192207
}
193208

209+
private static CanonicalCelExtension getExtensionOrThrow(String extensionName) {
210+
CanonicalCelExtension extension = CEL_EXTENSION_CONFIG_MAP.get(extensionName);
211+
if (extension == null) {
212+
throw new IllegalArgumentException("Unrecognized extension: " + extensionName);
213+
}
214+
215+
return extension;
216+
}
217+
194218
/** Represents a policy variable declaration. */
195219
@AutoValue
196220
public abstract static class VariableDecl {
@@ -529,4 +553,63 @@ public static ExtensionConfig of(String name, int version) {
529553
return newBuilder().setName(name).setVersion(version).build();
530554
}
531555
}
556+
557+
@VisibleForTesting
558+
enum CanonicalCelExtension {
559+
BINDINGS((compilerBuilder, options) -> compilerBuilder.addLibraries(CelExtensions.bindings())),
560+
PROTOS((compilerBuilder, options) -> compilerBuilder.addLibraries(CelExtensions.protos())),
561+
ENCODERS(
562+
(compilerBuilder, options) -> compilerBuilder.addLibraries(CelExtensions.encoders()),
563+
(runtimeBuilder, options) -> runtimeBuilder.addLibraries(CelExtensions.encoders())),
564+
MATH(
565+
(compilerBuilder, options) -> compilerBuilder.addLibraries(CelExtensions.math(options)),
566+
(runtimeBuilder, options) -> runtimeBuilder.addLibraries(CelExtensions.math(options))),
567+
OPTIONAL(
568+
(compilerBuilder, options) -> compilerBuilder.addLibraries(CelOptionalLibrary.INSTANCE),
569+
(runtimeBuilder, options) -> runtimeBuilder.addLibraries(CelOptionalLibrary.INSTANCE)),
570+
STRINGS(
571+
(compilerBuilder, options) -> compilerBuilder.addLibraries(CelExtensions.strings()),
572+
(runtimeBuilder, options) -> runtimeBuilder.addLibraries(CelExtensions.strings())),
573+
SETS(
574+
(compilerBuilder, options) -> compilerBuilder.addLibraries(CelExtensions.sets(options)),
575+
(runtimeBuilder, options) -> runtimeBuilder.addLibraries(CelExtensions.sets(options))),
576+
LISTS(
577+
(compilerBuilder, options) -> compilerBuilder.addLibraries(CelExtensions.lists()),
578+
(runtimeBuilder, options) -> runtimeBuilder.addLibraries(CelExtensions.lists()));
579+
580+
@SuppressWarnings("ImmutableEnumChecker")
581+
private final CompilerExtensionApplier compilerExtensionApplier;
582+
583+
@SuppressWarnings("ImmutableEnumChecker")
584+
private final RuntimeExtensionApplier runtimeExtensionApplier;
585+
586+
interface CompilerExtensionApplier {
587+
void apply(CelCompilerBuilder compilerBuilder, CelOptions options);
588+
}
589+
590+
interface RuntimeExtensionApplier {
591+
void apply(CelRuntimeBuilder runtimeBuilder, CelOptions options);
592+
}
593+
594+
void addCompilerExtension(CelCompilerBuilder compilerBuilder, CelOptions options) {
595+
compilerExtensionApplier.apply(compilerBuilder, options);
596+
}
597+
598+
void addRuntimeExtension(CelRuntimeBuilder runtimeBuilder, CelOptions options) {
599+
runtimeExtensionApplier.apply(runtimeBuilder, options);
600+
}
601+
602+
CanonicalCelExtension(CompilerExtensionApplier compilerExtensionApplier) {
603+
this(
604+
compilerExtensionApplier,
605+
(runtimeBuilder, options) -> {}); // no-op. Not all extensions augment the runtime.
606+
}
607+
608+
CanonicalCelExtension(
609+
CompilerExtensionApplier compilerExtensionApplier,
610+
RuntimeExtensionApplier runtimeExtensionApplier) {
611+
this.compilerExtensionApplier = compilerExtensionApplier;
612+
this.runtimeExtensionApplier = runtimeExtensionApplier;
613+
}
614+
}
532615
}

bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@
4747
import org.yaml.snakeyaml.nodes.SequenceNode;
4848
import org.yaml.snakeyaml.nodes.Tag;
4949

50-
final class CelEnvironmentYamlParser {
50+
/**
51+
* CelEnvironmentYamlParser intakes a YAML document that describes the structure of a CEL
52+
* environment, parses it then creates a {@link CelEnvironment}.
53+
*/
54+
public final class CelEnvironmentYamlParser {
5155
// Sentinel values to be returned for various declarations when parsing failure is encountered.
5256
private static final TypeDecl ERROR_TYPE_DECL = TypeDecl.create(ERROR);
5357
private static final VariableDecl ERROR_VARIABLE_DECL =

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ java_library(
1010
testonly = True,
1111
srcs = glob(["*Test.java"]),
1212
resources = [
13-
"//testing/environment:environment_yaml_files",
13+
"//testing/environment:extended_env",
1414
],
1515
deps = [
1616
"//:java_truth",

bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616

1717
import static com.google.common.truth.Truth.assertThat;
1818

19+
import com.google.common.collect.ImmutableSet;
1920
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
21+
import dev.cel.bundle.CelEnvironment.CanonicalCelExtension;
22+
import dev.cel.bundle.CelEnvironment.ExtensionConfig;
23+
import dev.cel.common.CelAbstractSyntaxTree;
24+
import dev.cel.common.CelOptions;
2025
import org.junit.Test;
2126
import org.junit.runner.RunWith;
2227

@@ -35,4 +40,32 @@ public void newBuilder_defaults() {
3540
assertThat(environment.variables()).isEmpty();
3641
assertThat(environment.functions()).isEmpty();
3742
}
43+
44+
@Test
45+
public void extend_allExtensions() throws Exception {
46+
ImmutableSet<ExtensionConfig> extensionConfigs =
47+
ImmutableSet.of(
48+
ExtensionConfig.of("bindings"),
49+
ExtensionConfig.of("encoders"),
50+
ExtensionConfig.of("lists"),
51+
ExtensionConfig.of("math"),
52+
ExtensionConfig.of("optional"),
53+
ExtensionConfig.of("protos"),
54+
ExtensionConfig.of("sets"),
55+
ExtensionConfig.of("strings"));
56+
CelEnvironment environment =
57+
CelEnvironment.newBuilder().setExtensions(extensionConfigs).build();
58+
59+
Cel cel = environment.extend(CelFactory.standardCelBuilder().build(), CelOptions.DEFAULT);
60+
CelAbstractSyntaxTree ast =
61+
cel.compile(
62+
"cel.bind(x, 10, math.greatest([1,x])) < int(' 11 '.trim()) &&"
63+
+ " optional.none().orValue(true) && [].flatten() == []")
64+
.getAst();
65+
boolean result = (boolean) cel.createProgram(ast).eval();
66+
67+
assertThat(extensionConfigs.size()).isEqualTo(CelEnvironment.CEL_EXTENSION_CONFIG_MAP.size());
68+
assertThat(extensionConfigs.size()).isEqualTo(CanonicalCelExtension.values().length);
69+
assertThat(result).isTrue();
70+
}
3871
}

bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public void environment_setExtensions() throws Exception {
7474
"extensions:\n"
7575
+ " - name: 'bindings'\n"
7676
+ " - name: 'encoders'\n"
77+
+ " - name: 'lists'\n"
7778
+ " - name: 'math'\n"
7879
+ " - name: 'optional'\n"
7980
+ " - name: 'protos'\n"
@@ -91,6 +92,7 @@ public void environment_setExtensions() throws Exception {
9192
ImmutableSet.of(
9293
ExtensionConfig.of("bindings"),
9394
ExtensionConfig.of("encoders"),
95+
ExtensionConfig.of("lists"),
9496
ExtensionConfig.of("math"),
9597
ExtensionConfig.of("optional"),
9698
ExtensionConfig.of("protos"),
@@ -307,6 +309,31 @@ public void environment_setMessageVariable() throws Exception {
307309
assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
308310
}
309311

312+
@Test
313+
public void environment_setContainer() throws Exception {
314+
String yamlConfig =
315+
"container: google.rpc.context\n"
316+
+ "variables:\n"
317+
+ "- name: 'request'\n"
318+
+ " type:\n"
319+
+ " type_name: 'google.rpc.context.AttributeContext.Request'";
320+
321+
CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
322+
323+
assertThat(environment)
324+
.isEqualTo(
325+
CelEnvironment.newBuilder()
326+
.setContainer("google.rpc.context")
327+
.setSource(environment.source().get())
328+
.setVariables(
329+
ImmutableSet.of(
330+
VariableDecl.create(
331+
"request",
332+
TypeDecl.create("google.rpc.context.AttributeContext.Request"))))
333+
.build());
334+
assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
335+
}
336+
310337
@Test
311338
public void environment_withInlinedVariableDecl() throws Exception {
312339
String yamlConfig =
@@ -675,8 +702,7 @@ private enum EnvironmentYamlResourceTestCase {
675702
private final String yamlFileContent;
676703
private final CelEnvironment expectedEnvironment;
677704

678-
EnvironmentYamlResourceTestCase(String yamlResourcePath, CelEnvironment expectedEnvironment)
679-
throws RuntimeException {
705+
EnvironmentYamlResourceTestCase(String yamlResourcePath, CelEnvironment expectedEnvironment) {
680706
try {
681707
this.yamlFileContent = readFile(yamlResourcePath);
682708
} catch (IOException e) {

compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ java_binary(
1515
main_class = "dev.cel.compiler.tools.CelCompilerTool",
1616
neverlink = 1,
1717
deps = [
18+
"//bundle:environment",
19+
"//bundle:environment_exception",
20+
"//bundle:environment_yaml_parser",
1821
"//common",
19-
"//common:compiler_common",
2022
"//common:options",
2123
"//common:proto_ast",
2224
"//compiler",
2325
"//compiler:compiler_builder",
24-
"//extensions",
25-
"//extensions:optional_library",
2626
"//parser:macro",
2727
"@cel_spec//proto/cel/expr:checked_java_proto",
2828
"@maven//:com_google_guava_guava",

0 commit comments

Comments
 (0)