Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code for Exercise 8 Codelabs #343

Merged
1 commit merged into from
May 9, 2024
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
11 changes: 11 additions & 0 deletions codelab/src/main/codelab/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,19 @@ java_library(
"//common/types:type_providers", # unuseddeps: keep
"//compiler", # unuseddeps: keep
"//compiler:compiler_builder", # unuseddeps: keep
"//optimizer", # unuseddeps: keep
"//optimizer:optimization_exception", # unuseddeps: keep
"//optimizer:optimizer_builder", # unuseddeps: keep
"//optimizer/optimizers:common_subexpression_elimination", # unuseddeps: keep
"//optimizer/optimizers:constant_folding", # unuseddeps: keep
"//parser:macro", # unuseddeps: keep
"//runtime", # unuseddeps: keep
"//validator", # unuseddeps: keep
"//validator:validator_builder", # unuseddeps: keep
"//validator/validators:duration", # unuseddeps: keep
"//validator/validators:homogeneous_literal", # unuseddeps: keep
"//validator/validators:regex", # unuseddeps: keep
"//validator/validators:timestamp", # unuseddeps: keep
"@maven//:com_google_api_grpc_proto_google_common_protos", # unuseddeps: keep
"@maven//:com_google_guava_guava", # unuseddeps: keep
"@maven//:com_google_protobuf_protobuf_java", # unuseddeps: keep
Expand Down
76 changes: 76 additions & 0 deletions codelab/src/main/codelab/Exercise8.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package codelab;

import com.google.rpc.context.AttributeContext;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelValidationException;
import dev.cel.common.CelValidationResult;
import dev.cel.common.types.SimpleType;
import dev.cel.common.types.StructTypeReference;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerFactory;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.CelRuntimeFactory;
import java.util.Map;

/**
* Exercise8 demonstrates how to leverage canonical CEL validators to perform advanced validations
* on an AST and CEL optimizers to improve evaluation efficiency.
*/
final class Exercise8 {
private static final CelCompiler CEL_COMPILER =
CelCompilerFactory.standardCelCompilerBuilder()
.addVar("x", SimpleType.INT)
.addVar(
"request", StructTypeReference.create("google.rpc.context.AttributeContext.Request"))
.addMessageTypes(AttributeContext.Request.getDescriptor())
.build();
private static final CelRuntime CEL_RUNTIME =
CelRuntimeFactory.standardCelRuntimeBuilder()
.addMessageTypes(AttributeContext.Request.getDescriptor())
.build();

// Statically declare the validator and optimizer here.

/**
* Compiles the input expression.
*
* @throws CelValidationException If the expression contains parsing or type-checking errors.
*/
CelAbstractSyntaxTree compile(String expression) throws CelValidationException {
return CEL_COMPILER.compile(expression).getAst();
}

/** Validates a type-checked AST. */
@SuppressWarnings("DoNotCallSuggester")
CelValidationResult validate(CelAbstractSyntaxTree checkedAst) {
throw new UnsupportedOperationException("To be implemented");
}

/** Optimizes a type-checked AST. */
@SuppressWarnings("DoNotCallSuggester")
CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree checkedAst) {
throw new UnsupportedOperationException("To be implemented");
}

/** Evaluates the compiled AST with the user provided parameter values. */
Object eval(CelAbstractSyntaxTree ast, Map<String, ?> parameterValues)
throws CelEvaluationException {
CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
return program.eval(parameterValues);
}
}
11 changes: 11 additions & 0 deletions codelab/src/main/codelab/solutions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,19 @@ java_library(
"//common/types:type_providers",
"//compiler",
"//compiler:compiler_builder",
"//optimizer",
"//optimizer:optimization_exception",
"//optimizer:optimizer_builder",
"//optimizer/optimizers:common_subexpression_elimination",
"//optimizer/optimizers:constant_folding",
"//parser:macro",
"//runtime",
"//validator",
"//validator:validator_builder",
"//validator/validators:duration",
"//validator/validators:homogeneous_literal",
"//validator/validators:regex",
"//validator/validators:timestamp",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
Expand Down
106 changes: 106 additions & 0 deletions codelab/src/main/codelab/solutions/Exercise8.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package codelab.solutions;

import com.google.rpc.context.AttributeContext;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelValidationException;
import dev.cel.common.CelValidationResult;
import dev.cel.common.types.SimpleType;
import dev.cel.common.types.StructTypeReference;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerFactory;
import dev.cel.optimizer.CelOptimizationException;
import dev.cel.optimizer.CelOptimizer;
import dev.cel.optimizer.CelOptimizerFactory;
import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer;
import dev.cel.optimizer.optimizers.SubexpressionOptimizer;
import dev.cel.optimizer.optimizers.SubexpressionOptimizer.SubexpressionOptimizerOptions;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.CelRuntimeFactory;
import dev.cel.validator.CelValidator;
import dev.cel.validator.CelValidatorFactory;
import dev.cel.validator.validators.DurationLiteralValidator;
import dev.cel.validator.validators.HomogeneousLiteralValidator;
import dev.cel.validator.validators.RegexLiteralValidator;
import dev.cel.validator.validators.TimestampLiteralValidator;
import java.util.Map;

/**
* Exercise8 demonstrates how to leverage canonical CEL validators to perform advanced validations
* on an AST and CEL optimizers to improve evaluation efficiency.
*/
final class Exercise8 {
private static final CelCompiler CEL_COMPILER =
CelCompilerFactory.standardCelCompilerBuilder()
.addVar("x", SimpleType.INT)
.addVar(
"request", StructTypeReference.create("google.rpc.context.AttributeContext.Request"))
.addMessageTypes(AttributeContext.Request.getDescriptor())
.build();
private static final CelRuntime CEL_RUNTIME =
CelRuntimeFactory.standardCelRuntimeBuilder()
.addMessageTypes(AttributeContext.Request.getDescriptor())
.build();

// Just like the compiler and runtime, the validator and optimizer can be statically
// initialized as their instances are immutable.
private static final CelValidator CEL_VALIDATOR =
CelValidatorFactory.standardCelValidatorBuilder(CEL_COMPILER, CEL_RUNTIME)
.addAstValidators(
TimestampLiteralValidator.INSTANCE,
DurationLiteralValidator.INSTANCE,
RegexLiteralValidator.INSTANCE,
HomogeneousLiteralValidator.newInstance())
.build();
private static final CelOptimizer CEL_OPTIMIZER =
CelOptimizerFactory.standardCelOptimizerBuilder(CEL_COMPILER, CEL_RUNTIME)
.addAstOptimizers(
ConstantFoldingOptimizer.getInstance(),
SubexpressionOptimizer.newInstance(
SubexpressionOptimizerOptions.newBuilder().enableCelBlock(true).build()))
.build();

/**
* Compiles the input expression.
*
* @throws CelValidationException If the expression contains parsing or type-checking errors.
*/
CelAbstractSyntaxTree compile(String expression) throws CelValidationException {
return CEL_COMPILER.compile(expression).getAst();
}

/** Validates a type-checked AST. */
CelValidationResult validate(CelAbstractSyntaxTree checkedAst) {
return CEL_VALIDATOR.validate(checkedAst);
}

/**
* Optimizes a type-checked AST.
*
* @throws CelOptimizationException If the optimization fails.
*/
CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree checkedAst) throws CelOptimizationException {
return CEL_OPTIMIZER.optimize(checkedAst);
}

/** Evaluates the compiled AST with the user provided parameter values. */
Object eval(CelAbstractSyntaxTree ast, Map<String, ?> parameterValues)
throws CelEvaluationException {
CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
return program.eval(parameterValues);
}
}
19 changes: 19 additions & 0 deletions codelab/src/test/codelab/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ java_test(
],
)

java_test(
name = "Exercise8Test",
srcs = ["Exercise8Test.java"],
tags = ["notap"],
test_class = "codelab.Exercise8Test",
deps = [
"//:java_truth",
"//codelab",
"//common",
"//common:compiler_common",
"//parser:unparser",
"//runtime",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_google_guava_guava",
"@maven//:com_google_testparameterinjector_test_parameter_injector",
"@maven//:junit_junit",
],
)

test_suite(
name = "exercise_test_suite",
tags = ["notap"],
Expand Down
147 changes: 147 additions & 0 deletions codelab/src/test/codelab/Exercise8Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package codelab;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.collect.ImmutableMap;
import com.google.rpc.context.AttributeContext;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelValidationException;
import dev.cel.common.CelValidationResult;
import dev.cel.parser.CelUnparser;
import dev.cel.parser.CelUnparserFactory;
import dev.cel.runtime.CelEvaluationException;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(TestParameterInjector.class)
public final class Exercise8Test {

private final Exercise8 exercise8 = new Exercise8();

@Test
public void validate_invalidTimestampLiteral_returnsError() throws Exception {
CelAbstractSyntaxTree ast = exercise8.compile("timestamp('bad')");

CelValidationResult validationResult = exercise8.validate(ast);

assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.isEqualTo(
"ERROR: <input>:1:11: timestamp validation failed. Reason: evaluation error: Failed to"
+ " parse timestamp: invalid timestamp \"bad\"\n"
+ " | timestamp('bad')\n"
+ " | ..........^");
}

@Test
public void validate_invalidDurationLiteral_returnsError() throws Exception {
CelAbstractSyntaxTree ast = exercise8.compile("duration('bad')");

CelValidationResult validationResult = exercise8.validate(ast);

assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.isEqualTo(
"ERROR: <input>:1:10: duration validation failed. Reason: evaluation error: invalid"
+ " duration format\n"
+ " | duration('bad')\n"
+ " | .........^");
}

@Test
public void validate_invalidRegexLiteral_returnsError() throws Exception {
CelAbstractSyntaxTree ast = exercise8.compile("'text'.matches('**')");

CelValidationResult validationResult = exercise8.validate(ast);

assertThat(validationResult.hasError()).isTrue();
assertThat(validationResult.getErrorString())
.isEqualTo(
"ERROR: <input>:1:16: Regex validation failed. Reason: Dangling meta character '*' near"
+ " index 0\n"
+ "**\n"
+ "^\n"
+ " | 'text'.matches('**')\n"
+ " | ...............^");
}

@Test
public void validate_listHasMixedLiterals_throws() throws Exception {
CelAbstractSyntaxTree ast = exercise8.compile("3 in [1, 2, '3']");

// Note that `CelValidationResult` is the same result class used for the compilation path. This
// means you could alternatively invoke `.getAst()` and handle `CelValidationException` as
// usual.
CelValidationResult validationResult = exercise8.validate(ast);

CelValidationException e = assertThrows(CelValidationException.class, validationResult::getAst);
assertThat(e)
.hasMessageThat()
.contains(
"ERROR: <input>:1:13: expected type 'int' but found 'string'\n"
+ " | 3 in [1, 2, '3']\n"
+ " | ............^");
}

@Test
public void optimize_constantFold_success() throws Exception {
CelUnparser celUnparser = CelUnparserFactory.newUnparser();
CelAbstractSyntaxTree ast = exercise8.compile("(1 + 2 + 3 == x) && (x in [1, 2, x])");

CelAbstractSyntaxTree optimizedAst = exercise8.optimize(ast);

assertThat(celUnparser.unparse(optimizedAst)).isEqualTo("6 == x");
}

@Test
public void optimize_constantFold_evaluateError() throws Exception {
CelAbstractSyntaxTree ast =
exercise8.compile("request.headers.referer == 'https://' + 'cel.dev'");
CelAbstractSyntaxTree optimizedAst = exercise8.optimize(ast);
ImmutableMap<String, AttributeContext.Request> runtimeParameters =
ImmutableMap.of("request", AttributeContext.Request.getDefaultInstance());

CelEvaluationException e1 =
assertThrows(CelEvaluationException.class, () -> exercise8.eval(ast, runtimeParameters));
CelEvaluationException e2 =
assertThrows(
CelEvaluationException.class, () -> exercise8.eval(optimizedAst, runtimeParameters));
// Note that the errors below differ by their source position.
assertThat(e1)
.hasMessageThat()
.contains("evaluation error at <input>:15: key 'referer' is not present in map.");
assertThat(e2)
.hasMessageThat()
.contains("evaluation error at <input>:0: key 'referer' is not present in map.");
}

@Test
public void optimize_commonSubexpressionElimination_success() throws Exception {
CelUnparser celUnparser = CelUnparserFactory.newUnparser();
CelAbstractSyntaxTree ast =
exercise8.compile(
"request.auth.claims.group == 'admin' || request.auth.claims.group == 'user'");

CelAbstractSyntaxTree optimizedAst = exercise8.optimize(ast);

assertThat(celUnparser.unparse(optimizedAst))
.isEqualTo(
"cel.@block([request.auth.claims.group], @index0 == \"admin\" || @index0 == \"user\")");
}
}
Loading
Loading