Skip to content

Commit fbf7c27

Browse files
l46kokcopybara-github
authored andcommitted
Add code for Exercise 9 Codelabs
PiperOrigin-RevId: 632202746
1 parent 606a2b2 commit fbf7c27

File tree

8 files changed

+501
-0
lines changed

8 files changed

+501
-0
lines changed

codelab/src/main/codelab/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ java_library(
99
name = "codelab",
1010
srcs = glob(["*.java"]),
1111
deps = [
12+
"//bundle:cel", # unuseddeps: keep
1213
"//common", # unuseddeps: keep
1314
"//common:compiler_common", # unuseddeps: keep
1415
"//common:proto_json_adapter", # unuseddeps: keep
16+
"//common/ast", # unuseddeps: keep
17+
"//common/navigation", # unuseddeps: keep
1518
"//common/types", # unuseddeps: keep
1619
"//common/types:type_providers", # unuseddeps: keep
1720
"//compiler", # unuseddeps: keep
@@ -24,6 +27,7 @@ java_library(
2427
"//parser:macro", # unuseddeps: keep
2528
"//runtime", # unuseddeps: keep
2629
"//validator", # unuseddeps: keep
30+
"//validator:ast_validator", # unuseddeps: keep
2731
"//validator:validator_builder", # unuseddeps: keep
2832
"//validator/validators:duration", # unuseddeps: keep
2933
"//validator/validators:homogeneous_literal", # unuseddeps: keep
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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.CelFunctionDecl;
20+
import dev.cel.common.CelOverloadDecl;
21+
import dev.cel.common.CelValidationException;
22+
import dev.cel.common.CelValidationResult;
23+
import dev.cel.common.types.SimpleType;
24+
import dev.cel.compiler.CelCompiler;
25+
import dev.cel.compiler.CelCompilerFactory;
26+
import dev.cel.parser.CelStandardMacro;
27+
import dev.cel.runtime.CelEvaluationException;
28+
import dev.cel.runtime.CelRuntime;
29+
import dev.cel.runtime.CelRuntimeFactory;
30+
import dev.cel.validator.CelValidator;
31+
import dev.cel.validator.CelValidatorFactory;
32+
33+
/**
34+
* Exercise9 demonstrates how to author a custom AST validator to perform domain specific
35+
* validations.
36+
*
37+
* <p>Given a `google.rpc.context.AttributeContext.Request` message, validate that its fields follow
38+
* the expected HTTP specification.
39+
*
40+
* <p>Given an expression containing an expensive function call, validate that it is not nested
41+
* within a macro.
42+
*/
43+
final class Exercise9 {
44+
private static final CelCompiler CEL_COMPILER =
45+
CelCompilerFactory.standardCelCompilerBuilder()
46+
.setStandardMacros(CelStandardMacro.ALL)
47+
.addFunctionDeclarations(
48+
CelFunctionDecl.newFunctionDeclaration(
49+
"is_prime_number",
50+
CelOverloadDecl.newGlobalOverload(
51+
"is_prime_number_int",
52+
"Invokes an expensive RPC call to check if the value is a prime number.",
53+
SimpleType.BOOL,
54+
SimpleType.INT)))
55+
.addMessageTypes(AttributeContext.Request.getDescriptor())
56+
.build();
57+
private static final CelRuntime CEL_RUNTIME =
58+
CelRuntimeFactory.standardCelRuntimeBuilder()
59+
.addMessageTypes(AttributeContext.Request.getDescriptor())
60+
.build();
61+
private static final CelValidator CEL_VALIDATOR =
62+
CelValidatorFactory.standardCelValidatorBuilder(CEL_COMPILER, CEL_RUNTIME)
63+
// Add your custom AST validators here
64+
.build();
65+
66+
/**
67+
* Compiles the input expression.
68+
*
69+
* @throws CelValidationException If the expression contains parsing or type-checking errors.
70+
*/
71+
CelAbstractSyntaxTree compile(String expression) throws CelValidationException {
72+
return CEL_COMPILER.compile(expression).getAst();
73+
}
74+
75+
/** Validates a type-checked AST. */
76+
CelValidationResult validate(CelAbstractSyntaxTree checkedAst) {
77+
return CEL_VALIDATOR.validate(checkedAst);
78+
}
79+
80+
/** Evaluates the compiled AST. */
81+
Object eval(CelAbstractSyntaxTree ast) throws CelEvaluationException {
82+
return CEL_RUNTIME.createProgram(ast).eval();
83+
}
84+
85+
/**
86+
* Performs general validation on AttributeContext.Request message. The validator raises errors if
87+
* the HTTP request is malformed and semantically invalid (e.g: contains disallowed HTTP methods).
88+
* Warnings are presented if there's potential problems with the contents of the request (e.g:
89+
* using "http" instead of "https" for scheme).
90+
*/
91+
static final class AttributeContextRequestValidator {
92+
// Implement validate method here
93+
}
94+
95+
/** Prevents nesting an expensive function call within a macro. */
96+
static final class ComprehensionSafetyValidator {
97+
// Implement validate method here
98+
}
99+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ java_library(
99
name = "solutions",
1010
srcs = glob(["*.java"]),
1111
deps = [
12+
"//bundle:cel",
1213
"//common",
1314
"//common:compiler_common",
1415
"//common:proto_json_adapter",
16+
"//common/ast",
17+
"//common/navigation",
1518
"//common/types",
1619
"//common/types:type_providers",
1720
"//compiler",
@@ -24,6 +27,7 @@ java_library(
2427
"//parser:macro",
2528
"//runtime",
2629
"//validator",
30+
"//validator:ast_validator",
2731
"//validator:validator_builder",
2832
"//validator/validators:duration",
2933
"//validator/validators:homogeneous_literal",
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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.common.collect.ImmutableSet;
18+
import com.google.rpc.context.AttributeContext;
19+
import dev.cel.bundle.Cel;
20+
import dev.cel.common.CelAbstractSyntaxTree;
21+
import dev.cel.common.CelFunctionDecl;
22+
import dev.cel.common.CelOverloadDecl;
23+
import dev.cel.common.CelValidationException;
24+
import dev.cel.common.CelValidationResult;
25+
import dev.cel.common.ast.CelExpr;
26+
import dev.cel.common.ast.CelExpr.CelStruct;
27+
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
28+
import dev.cel.common.navigation.CelNavigableAst;
29+
import dev.cel.common.types.SimpleType;
30+
import dev.cel.compiler.CelCompiler;
31+
import dev.cel.compiler.CelCompilerFactory;
32+
import dev.cel.parser.CelStandardMacro;
33+
import dev.cel.runtime.CelEvaluationException;
34+
import dev.cel.runtime.CelRuntime;
35+
import dev.cel.runtime.CelRuntimeFactory;
36+
import dev.cel.validator.CelAstValidator;
37+
import dev.cel.validator.CelValidator;
38+
import dev.cel.validator.CelValidatorFactory;
39+
40+
/**
41+
* Exercise9 demonstrates how to author a custom AST validator to perform domain specific
42+
* validations.
43+
*
44+
* <p>Given a `google.rpc.context.AttributeContext.Request` message, validate that its fields follow
45+
* the expected HTTP specification.
46+
*
47+
* <p>Given an expression containing an expensive function call, validate that it is not nested
48+
* within a macro.
49+
*/
50+
final class Exercise9 {
51+
private static final CelCompiler CEL_COMPILER =
52+
CelCompilerFactory.standardCelCompilerBuilder()
53+
.setStandardMacros(CelStandardMacro.ALL)
54+
.addFunctionDeclarations(
55+
CelFunctionDecl.newFunctionDeclaration(
56+
"is_prime_number",
57+
CelOverloadDecl.newGlobalOverload(
58+
"is_prime_number_int",
59+
"Invokes an expensive RPC call to check if the value is a prime number.",
60+
SimpleType.BOOL,
61+
SimpleType.INT)))
62+
.addMessageTypes(AttributeContext.Request.getDescriptor())
63+
.build();
64+
private static final CelRuntime CEL_RUNTIME =
65+
CelRuntimeFactory.standardCelRuntimeBuilder()
66+
.addMessageTypes(AttributeContext.Request.getDescriptor())
67+
.build();
68+
private static final CelValidator CEL_VALIDATOR =
69+
CelValidatorFactory.standardCelValidatorBuilder(CEL_COMPILER, CEL_RUNTIME)
70+
.addAstValidators(
71+
new AttributeContextRequestValidator(), //
72+
new ComprehensionSafetyValidator())
73+
.build();
74+
75+
/**
76+
* Compiles the input expression.
77+
*
78+
* @throws CelValidationException If the expression contains parsing or type-checking errors.
79+
*/
80+
CelAbstractSyntaxTree compile(String expression) throws CelValidationException {
81+
return CEL_COMPILER.compile(expression).getAst();
82+
}
83+
84+
/** Validates a type-checked AST. */
85+
CelValidationResult validate(CelAbstractSyntaxTree checkedAst) {
86+
return CEL_VALIDATOR.validate(checkedAst);
87+
}
88+
89+
/** Evaluates the compiled AST. */
90+
Object eval(CelAbstractSyntaxTree ast) throws CelEvaluationException {
91+
return CEL_RUNTIME.createProgram(ast).eval();
92+
}
93+
94+
/**
95+
* Performs general validation on AttributeContext.Request message. The validator raises errors if
96+
* the HTTP request is malformed and semantically invalid (e.g: contains disallowed HTTP methods).
97+
* Warnings are presented if there's potential problems with the contents of the request (e.g:
98+
* using "http" instead of "https" for scheme).
99+
*/
100+
static final class AttributeContextRequestValidator implements CelAstValidator {
101+
private static final ImmutableSet<String> ALLOWED_HTTP_METHODS =
102+
ImmutableSet.of("GET", "POST", "PUT", "DELETE");
103+
104+
@Override
105+
public void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issuesFactory) {
106+
navigableAst
107+
.getRoot()
108+
.allNodes()
109+
.filter(node -> node.getKind().equals(Kind.STRUCT))
110+
.map(node -> node.expr().struct())
111+
.filter(
112+
struct -> struct.messageName().equals("google.rpc.context.AttributeContext.Request"))
113+
.forEach(
114+
struct -> {
115+
for (CelStruct.Entry entry : struct.entries()) {
116+
String fieldKey = entry.fieldKey();
117+
if (fieldKey.equals("method")) {
118+
String entryStringValue = getStringValue(entry.value());
119+
if (!ALLOWED_HTTP_METHODS.contains(entryStringValue)) {
120+
issuesFactory.addError(
121+
entry.value().id(), entryStringValue + " is not an allowed HTTP method.");
122+
}
123+
} else if (fieldKey.equals("scheme")) {
124+
String entryStringValue = getStringValue(entry.value());
125+
if (!entryStringValue.equals("https")) {
126+
issuesFactory.addWarning(
127+
entry.value().id(), "Prefer using https for safety.");
128+
}
129+
}
130+
}
131+
});
132+
}
133+
134+
/**
135+
* Reads the underlying string value from the expression.
136+
*
137+
* @throws UnsupportedOperationException if the expression is not a constant string value.
138+
*/
139+
private static String getStringValue(CelExpr celExpr) {
140+
return celExpr.constant().stringValue();
141+
}
142+
}
143+
144+
/** Prevents nesting an expensive function call within a macro. */
145+
static final class ComprehensionSafetyValidator implements CelAstValidator {
146+
private static final String EXPENSIVE_FUNCTION_NAME = "is_prime_number";
147+
148+
@Override
149+
public void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issuesFactory) {
150+
navigableAst
151+
.getRoot()
152+
.allNodes()
153+
.filter(node -> node.getKind().equals(Kind.COMPREHENSION))
154+
.forEach(
155+
comprehensionNode -> {
156+
boolean isFunctionWithinMacro =
157+
comprehensionNode
158+
.descendants()
159+
.anyMatch(
160+
node ->
161+
node.expr()
162+
.callOrDefault()
163+
.function()
164+
.equals(EXPENSIVE_FUNCTION_NAME));
165+
if (isFunctionWithinMacro) {
166+
issuesFactory.addError(
167+
comprehensionNode.id(),
168+
EXPENSIVE_FUNCTION_NAME + " function cannot be used within CEL macros.");
169+
}
170+
});
171+
}
172+
}
173+
}

codelab/src/test/codelab/BUILD.bazel

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ java_test(
134134
],
135135
)
136136

137+
java_test(
138+
name = "Exercise9Test",
139+
srcs = ["Exercise9Test.java"],
140+
tags = ["notap"],
141+
test_class = "codelab.Exercise9Test",
142+
deps = [
143+
"//:java_truth",
144+
"//codelab",
145+
"//common",
146+
"//common:compiler_common",
147+
"@maven//:com_google_api_grpc_proto_google_common_protos",
148+
"@maven//:com_google_testparameterinjector_test_parameter_injector",
149+
"@maven//:junit_junit",
150+
],
151+
)
152+
137153
test_suite(
138154
name = "exercise_test_suite",
139155
tags = ["notap"],

0 commit comments

Comments
 (0)