Skip to content

Commit 606a2b2

Browse files
l46kokcopybara-github
authored andcommitted
Add code for Exercise 8 Codelabs
PiperOrigin-RevId: 632199199
1 parent 447776f commit 606a2b2

File tree

8 files changed

+535
-0
lines changed

8 files changed

+535
-0
lines changed

codelab/src/main/codelab/BUILD.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,19 @@ java_library(
1616
"//common/types:type_providers", # unuseddeps: keep
1717
"//compiler", # unuseddeps: keep
1818
"//compiler:compiler_builder", # unuseddeps: keep
19+
"//optimizer", # unuseddeps: keep
20+
"//optimizer:optimization_exception", # unuseddeps: keep
21+
"//optimizer:optimizer_builder", # unuseddeps: keep
22+
"//optimizer/optimizers:common_subexpression_elimination", # unuseddeps: keep
23+
"//optimizer/optimizers:constant_folding", # unuseddeps: keep
1924
"//parser:macro", # unuseddeps: keep
2025
"//runtime", # unuseddeps: keep
26+
"//validator", # unuseddeps: keep
27+
"//validator:validator_builder", # unuseddeps: keep
28+
"//validator/validators:duration", # unuseddeps: keep
29+
"//validator/validators:homogeneous_literal", # unuseddeps: keep
30+
"//validator/validators:regex", # unuseddeps: keep
31+
"//validator/validators:timestamp", # unuseddeps: keep
2132
"@maven//:com_google_api_grpc_proto_google_common_protos", # unuseddeps: keep
2233
"@maven//:com_google_guava_guava", # unuseddeps: keep
2334
"@maven//:com_google_protobuf_protobuf_java", # unuseddeps: keep
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package codelab;
16+
17+
import com.google.rpc.context.AttributeContext;
18+
import dev.cel.common.CelAbstractSyntaxTree;
19+
import dev.cel.common.CelValidationException;
20+
import dev.cel.common.CelValidationResult;
21+
import dev.cel.common.types.SimpleType;
22+
import dev.cel.common.types.StructTypeReference;
23+
import dev.cel.compiler.CelCompiler;
24+
import dev.cel.compiler.CelCompilerFactory;
25+
import dev.cel.runtime.CelEvaluationException;
26+
import dev.cel.runtime.CelRuntime;
27+
import dev.cel.runtime.CelRuntimeFactory;
28+
import java.util.Map;
29+
30+
/**
31+
* Exercise8 demonstrates how to leverage canonical CEL validators to perform advanced validations
32+
* on an AST and CEL optimizers to improve evaluation efficiency.
33+
*/
34+
final class Exercise8 {
35+
private static final CelCompiler CEL_COMPILER =
36+
CelCompilerFactory.standardCelCompilerBuilder()
37+
.addVar("x", SimpleType.INT)
38+
.addVar(
39+
"request", StructTypeReference.create("google.rpc.context.AttributeContext.Request"))
40+
.addMessageTypes(AttributeContext.Request.getDescriptor())
41+
.build();
42+
private static final CelRuntime CEL_RUNTIME =
43+
CelRuntimeFactory.standardCelRuntimeBuilder()
44+
.addMessageTypes(AttributeContext.Request.getDescriptor())
45+
.build();
46+
47+
// Statically declare the validator and optimizer here.
48+
49+
/**
50+
* Compiles the input expression.
51+
*
52+
* @throws CelValidationException If the expression contains parsing or type-checking errors.
53+
*/
54+
CelAbstractSyntaxTree compile(String expression) throws CelValidationException {
55+
return CEL_COMPILER.compile(expression).getAst();
56+
}
57+
58+
/** Validates a type-checked AST. */
59+
@SuppressWarnings("DoNotCallSuggester")
60+
CelValidationResult validate(CelAbstractSyntaxTree checkedAst) {
61+
throw new UnsupportedOperationException("To be implemented");
62+
}
63+
64+
/** Optimizes a type-checked AST. */
65+
@SuppressWarnings("DoNotCallSuggester")
66+
CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree checkedAst) {
67+
throw new UnsupportedOperationException("To be implemented");
68+
}
69+
70+
/** Evaluates the compiled AST with the user provided parameter values. */
71+
Object eval(CelAbstractSyntaxTree ast, Map<String, ?> parameterValues)
72+
throws CelEvaluationException {
73+
CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
74+
return program.eval(parameterValues);
75+
}
76+
}

codelab/src/main/codelab/solutions/BUILD.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,19 @@ java_library(
1616
"//common/types:type_providers",
1717
"//compiler",
1818
"//compiler:compiler_builder",
19+
"//optimizer",
20+
"//optimizer:optimization_exception",
21+
"//optimizer:optimizer_builder",
22+
"//optimizer/optimizers:common_subexpression_elimination",
23+
"//optimizer/optimizers:constant_folding",
1924
"//parser:macro",
2025
"//runtime",
26+
"//validator",
27+
"//validator:validator_builder",
28+
"//validator/validators:duration",
29+
"//validator/validators:homogeneous_literal",
30+
"//validator/validators:regex",
31+
"//validator/validators:timestamp",
2132
"@maven//:com_google_api_grpc_proto_google_common_protos",
2233
"@maven//:com_google_guava_guava",
2334
"@maven//:com_google_protobuf_protobuf_java",
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package codelab.solutions;
16+
17+
import com.google.rpc.context.AttributeContext;
18+
import dev.cel.common.CelAbstractSyntaxTree;
19+
import dev.cel.common.CelValidationException;
20+
import dev.cel.common.CelValidationResult;
21+
import dev.cel.common.types.SimpleType;
22+
import dev.cel.common.types.StructTypeReference;
23+
import dev.cel.compiler.CelCompiler;
24+
import dev.cel.compiler.CelCompilerFactory;
25+
import dev.cel.optimizer.CelOptimizationException;
26+
import dev.cel.optimizer.CelOptimizer;
27+
import dev.cel.optimizer.CelOptimizerFactory;
28+
import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer;
29+
import dev.cel.optimizer.optimizers.SubexpressionOptimizer;
30+
import dev.cel.optimizer.optimizers.SubexpressionOptimizer.SubexpressionOptimizerOptions;
31+
import dev.cel.runtime.CelEvaluationException;
32+
import dev.cel.runtime.CelRuntime;
33+
import dev.cel.runtime.CelRuntimeFactory;
34+
import dev.cel.validator.CelValidator;
35+
import dev.cel.validator.CelValidatorFactory;
36+
import dev.cel.validator.validators.DurationLiteralValidator;
37+
import dev.cel.validator.validators.HomogeneousLiteralValidator;
38+
import dev.cel.validator.validators.RegexLiteralValidator;
39+
import dev.cel.validator.validators.TimestampLiteralValidator;
40+
import java.util.Map;
41+
42+
/**
43+
* Exercise8 demonstrates how to leverage canonical CEL validators to perform advanced validations
44+
* on an AST and CEL optimizers to improve evaluation efficiency.
45+
*/
46+
final class Exercise8 {
47+
private static final CelCompiler CEL_COMPILER =
48+
CelCompilerFactory.standardCelCompilerBuilder()
49+
.addVar("x", SimpleType.INT)
50+
.addVar(
51+
"request", StructTypeReference.create("google.rpc.context.AttributeContext.Request"))
52+
.addMessageTypes(AttributeContext.Request.getDescriptor())
53+
.build();
54+
private static final CelRuntime CEL_RUNTIME =
55+
CelRuntimeFactory.standardCelRuntimeBuilder()
56+
.addMessageTypes(AttributeContext.Request.getDescriptor())
57+
.build();
58+
59+
// Just like the compiler and runtime, the validator and optimizer can be statically
60+
// initialized as their instances are immutable.
61+
private static final CelValidator CEL_VALIDATOR =
62+
CelValidatorFactory.standardCelValidatorBuilder(CEL_COMPILER, CEL_RUNTIME)
63+
.addAstValidators(
64+
TimestampLiteralValidator.INSTANCE,
65+
DurationLiteralValidator.INSTANCE,
66+
RegexLiteralValidator.INSTANCE,
67+
HomogeneousLiteralValidator.newInstance())
68+
.build();
69+
private static final CelOptimizer CEL_OPTIMIZER =
70+
CelOptimizerFactory.standardCelOptimizerBuilder(CEL_COMPILER, CEL_RUNTIME)
71+
.addAstOptimizers(
72+
ConstantFoldingOptimizer.getInstance(),
73+
SubexpressionOptimizer.newInstance(
74+
SubexpressionOptimizerOptions.newBuilder().enableCelBlock(true).build()))
75+
.build();
76+
77+
/**
78+
* Compiles the input expression.
79+
*
80+
* @throws CelValidationException If the expression contains parsing or type-checking errors.
81+
*/
82+
CelAbstractSyntaxTree compile(String expression) throws CelValidationException {
83+
return CEL_COMPILER.compile(expression).getAst();
84+
}
85+
86+
/** Validates a type-checked AST. */
87+
CelValidationResult validate(CelAbstractSyntaxTree checkedAst) {
88+
return CEL_VALIDATOR.validate(checkedAst);
89+
}
90+
91+
/**
92+
* Optimizes a type-checked AST.
93+
*
94+
* @throws CelOptimizationException If the optimization fails.
95+
*/
96+
CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree checkedAst) throws CelOptimizationException {
97+
return CEL_OPTIMIZER.optimize(checkedAst);
98+
}
99+
100+
/** Evaluates the compiled AST with the user provided parameter values. */
101+
Object eval(CelAbstractSyntaxTree ast, Map<String, ?> parameterValues)
102+
throws CelEvaluationException {
103+
CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
104+
return program.eval(parameterValues);
105+
}
106+
}

codelab/src/test/codelab/BUILD.bazel

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,25 @@ java_test(
115115
],
116116
)
117117

118+
java_test(
119+
name = "Exercise8Test",
120+
srcs = ["Exercise8Test.java"],
121+
tags = ["notap"],
122+
test_class = "codelab.Exercise8Test",
123+
deps = [
124+
"//:java_truth",
125+
"//codelab",
126+
"//common",
127+
"//common:compiler_common",
128+
"//parser:unparser",
129+
"//runtime",
130+
"@maven//:com_google_api_grpc_proto_google_common_protos",
131+
"@maven//:com_google_guava_guava",
132+
"@maven//:com_google_testparameterinjector_test_parameter_injector",
133+
"@maven//:junit_junit",
134+
],
135+
)
136+
118137
test_suite(
119138
name = "exercise_test_suite",
120139
tags = ["notap"],
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package codelab;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static org.junit.Assert.assertThrows;
19+
20+
import com.google.common.collect.ImmutableMap;
21+
import com.google.rpc.context.AttributeContext;
22+
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
23+
import dev.cel.common.CelAbstractSyntaxTree;
24+
import dev.cel.common.CelValidationException;
25+
import dev.cel.common.CelValidationResult;
26+
import dev.cel.parser.CelUnparser;
27+
import dev.cel.parser.CelUnparserFactory;
28+
import dev.cel.runtime.CelEvaluationException;
29+
import org.junit.Test;
30+
import org.junit.runner.RunWith;
31+
32+
@RunWith(TestParameterInjector.class)
33+
public final class Exercise8Test {
34+
35+
private final Exercise8 exercise8 = new Exercise8();
36+
37+
@Test
38+
public void validate_invalidTimestampLiteral_returnsError() throws Exception {
39+
CelAbstractSyntaxTree ast = exercise8.compile("timestamp('bad')");
40+
41+
CelValidationResult validationResult = exercise8.validate(ast);
42+
43+
assertThat(validationResult.hasError()).isTrue();
44+
assertThat(validationResult.getErrorString())
45+
.isEqualTo(
46+
"ERROR: <input>:1:11: timestamp validation failed. Reason: evaluation error: Failed to"
47+
+ " parse timestamp: invalid timestamp \"bad\"\n"
48+
+ " | timestamp('bad')\n"
49+
+ " | ..........^");
50+
}
51+
52+
@Test
53+
public void validate_invalidDurationLiteral_returnsError() throws Exception {
54+
CelAbstractSyntaxTree ast = exercise8.compile("duration('bad')");
55+
56+
CelValidationResult validationResult = exercise8.validate(ast);
57+
58+
assertThat(validationResult.hasError()).isTrue();
59+
assertThat(validationResult.getErrorString())
60+
.isEqualTo(
61+
"ERROR: <input>:1:10: duration validation failed. Reason: evaluation error: invalid"
62+
+ " duration format\n"
63+
+ " | duration('bad')\n"
64+
+ " | .........^");
65+
}
66+
67+
@Test
68+
public void validate_invalidRegexLiteral_returnsError() throws Exception {
69+
CelAbstractSyntaxTree ast = exercise8.compile("'text'.matches('**')");
70+
71+
CelValidationResult validationResult = exercise8.validate(ast);
72+
73+
assertThat(validationResult.hasError()).isTrue();
74+
assertThat(validationResult.getErrorString())
75+
.isEqualTo(
76+
"ERROR: <input>:1:16: Regex validation failed. Reason: Dangling meta character '*' near"
77+
+ " index 0\n"
78+
+ "**\n"
79+
+ "^\n"
80+
+ " | 'text'.matches('**')\n"
81+
+ " | ...............^");
82+
}
83+
84+
@Test
85+
public void validate_listHasMixedLiterals_throws() throws Exception {
86+
CelAbstractSyntaxTree ast = exercise8.compile("3 in [1, 2, '3']");
87+
88+
// Note that `CelValidationResult` is the same result class used for the compilation path. This
89+
// means you could alternatively invoke `.getAst()` and handle `CelValidationException` as
90+
// usual.
91+
CelValidationResult validationResult = exercise8.validate(ast);
92+
93+
CelValidationException e = assertThrows(CelValidationException.class, validationResult::getAst);
94+
assertThat(e)
95+
.hasMessageThat()
96+
.contains(
97+
"ERROR: <input>:1:13: expected type 'int' but found 'string'\n"
98+
+ " | 3 in [1, 2, '3']\n"
99+
+ " | ............^");
100+
}
101+
102+
@Test
103+
public void optimize_constantFold_success() throws Exception {
104+
CelUnparser celUnparser = CelUnparserFactory.newUnparser();
105+
CelAbstractSyntaxTree ast = exercise8.compile("(1 + 2 + 3 == x) && (x in [1, 2, x])");
106+
107+
CelAbstractSyntaxTree optimizedAst = exercise8.optimize(ast);
108+
109+
assertThat(celUnparser.unparse(optimizedAst)).isEqualTo("6 == x");
110+
}
111+
112+
@Test
113+
public void optimize_constantFold_evaluateError() throws Exception {
114+
CelAbstractSyntaxTree ast =
115+
exercise8.compile("request.headers.referer == 'https://' + 'cel.dev'");
116+
CelAbstractSyntaxTree optimizedAst = exercise8.optimize(ast);
117+
ImmutableMap<String, AttributeContext.Request> runtimeParameters =
118+
ImmutableMap.of("request", AttributeContext.Request.getDefaultInstance());
119+
120+
CelEvaluationException e1 =
121+
assertThrows(CelEvaluationException.class, () -> exercise8.eval(ast, runtimeParameters));
122+
CelEvaluationException e2 =
123+
assertThrows(
124+
CelEvaluationException.class, () -> exercise8.eval(optimizedAst, runtimeParameters));
125+
// Note that the errors below differ by their source position.
126+
assertThat(e1)
127+
.hasMessageThat()
128+
.contains("evaluation error at <input>:15: key 'referer' is not present in map.");
129+
assertThat(e2)
130+
.hasMessageThat()
131+
.contains("evaluation error at <input>:0: key 'referer' is not present in map.");
132+
}
133+
134+
@Test
135+
public void optimize_commonSubexpressionElimination_success() throws Exception {
136+
CelUnparser celUnparser = CelUnparserFactory.newUnparser();
137+
CelAbstractSyntaxTree ast =
138+
exercise8.compile(
139+
"request.auth.claims.group == 'admin' || request.auth.claims.group == 'user'");
140+
141+
CelAbstractSyntaxTree optimizedAst = exercise8.optimize(ast);
142+
143+
assertThat(celUnparser.unparse(optimizedAst))
144+
.isEqualTo(
145+
"cel.@block([request.auth.claims.group], @index0 == \"admin\" || @index0 == \"user\")");
146+
}
147+
}

0 commit comments

Comments
 (0)