-
Notifications
You must be signed in to change notification settings - Fork 4k
xds: Implementation of Unified Matcher and CEL Integration #12640
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
base: master
Are you sure you want to change the base?
Changes from 15 commits
f472c63
99aeef1
db4d885
33eff21
e28c8f5
78895db
19989eb
118d7af
77b01b9
310677d
49031b4
5a31706
c62d193
0c2d771
91d6bea
22f65ca
5c04ac3
82d9a8b
7b50380
4b626ad
c5bf128
c0b2b1d
e07d612
020f9ff
f20c5f0
0bd57e9
3b6129f
7cad139
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /* | ||
| * Copyright 2026 The gRPC Authors | ||
| * | ||
| * 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 | ||
| * | ||
| * http://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 io.grpc.xds.internal.matcher; | ||
|
|
||
| import dev.cel.common.CelAbstractSyntaxTree; | ||
| import dev.cel.common.CelOptions; | ||
| import dev.cel.common.types.SimpleType; | ||
| import dev.cel.compiler.CelCompiler; | ||
| import dev.cel.compiler.CelCompilerFactory; | ||
| import dev.cel.runtime.CelRuntime; | ||
| import dev.cel.runtime.CelRuntimeFactory; | ||
|
|
||
| /** | ||
| * Shared utilities for CEL-based matchers and extractors. | ||
| */ | ||
| final class CelCommon { | ||
| private static final CelOptions CEL_OPTIONS = CelOptions.newBuilder() | ||
| .enableComprehension(false) | ||
| .maxRegexProgramSize(100) | ||
| .build(); | ||
|
|
||
| private static final dev.cel.checker.CelStandardDeclarations DECLARATIONS = | ||
| dev.cel.checker.CelStandardDeclarations.newBuilder() | ||
| .filterFunctions((func, over) -> { | ||
| if (func == dev.cel.checker.CelStandardDeclarations.StandardFunction.STRING) { | ||
| return false; | ||
| } | ||
| if (func == dev.cel.checker.CelStandardDeclarations.StandardFunction.ADD) { | ||
| String id = over.celOverloadDecl().overloadId(); | ||
| return !id.equals("add_string") && !id.equals("add_list"); | ||
| } | ||
| return true; | ||
| }) | ||
| .build(); | ||
|
|
||
| private static final dev.cel.runtime.CelStandardFunctions FUNCTIONS = | ||
| dev.cel.runtime.CelStandardFunctions.newBuilder() | ||
| .filterFunctions((func, over) -> { | ||
| if (func == dev.cel.runtime.CelStandardFunctions.StandardFunction.STRING) { | ||
| return false; | ||
| } | ||
| if (func == dev.cel.runtime.CelStandardFunctions.StandardFunction.ADD) { | ||
| return !over.equals( | ||
| (Object) dev.cel.runtime.standard.AddOperator.AddOverload.ADD_STRING) | ||
|
||
| && !over.equals( | ||
| (Object) dev.cel.runtime.standard.AddOperator.AddOverload.ADD_LIST); | ||
| } | ||
| return true; | ||
|
Comment on lines
+42
to
+49
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about using
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just 2 cases! It's fine to use either. I would have considered if there were more cases. |
||
| }) | ||
| .build(); | ||
|
|
||
| static final CelCompiler COMPILER = CelCompilerFactory.standardCelCompilerBuilder() | ||
| .setStandardEnvironmentEnabled(false) | ||
| .setStandardDeclarations(DECLARATIONS) | ||
| .addVar("request", SimpleType.DYN) | ||
| .setOptions(CEL_OPTIONS) | ||
| .build(); | ||
|
|
||
| static final CelRuntime RUNTIME = CelRuntimeFactory.standardCelRuntimeBuilder() | ||
| .setStandardEnvironmentEnabled(false) | ||
| .setStandardFunctions(FUNCTIONS) | ||
| .setOptions(CEL_OPTIONS) | ||
| .build(); | ||
|
|
||
| private CelCommon() {} | ||
|
|
||
| static void checkAllowedVariables(CelAbstractSyntaxTree ast) { | ||
| for (java.util.Map.Entry<Long, dev.cel.common.ast.CelReference> entry : | ||
| ast.getReferenceMap().entrySet()) { | ||
| dev.cel.common.ast.CelReference ref = entry.getValue(); | ||
| // If overload_id is empty, it's a variable reference or type name. | ||
| // We only support "request". | ||
| if (!ref.value().isPresent() && ref.overloadIds().isEmpty()) { | ||
| if (!"request".equals(ref.name())) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we put Follow up - if we have more constants, does it make sense to create a public final class UnifiedMatcherConstants {
// Avoid init
private UnifiedMatcherConstants() {}
public static final String CEL_ALLOWED_VARIABLES = "request";
}
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now I will put "request" in a final string. A new file just for constants, we can see if there are that many constants used throughout the directory otherwise its better to keep constants local if they are not used anywhere else. |
||
| throw new IllegalArgumentException( | ||
| "CEL expression references unknown variable: " + ref.name()); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /* | ||
| * Copyright 2026 The gRPC Authors | ||
| * | ||
| * 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 | ||
| * | ||
| * http://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 io.grpc.xds.internal.matcher; | ||
|
|
||
| import dev.cel.common.CelAbstractSyntaxTree; | ||
| import dev.cel.common.CelValidationException; | ||
| import dev.cel.common.types.SimpleType; | ||
| import dev.cel.runtime.CelEvaluationException; | ||
| import dev.cel.runtime.CelRuntime; | ||
|
|
||
| /** | ||
| * Executes compiled CEL expressions. | ||
| */ | ||
| public final class CelMatcher { | ||
| private final CelRuntime.Program program; | ||
|
|
||
| private CelMatcher(CelRuntime.Program program) { | ||
| this.program = program; | ||
| } | ||
|
|
||
| /** | ||
| * Compiles the AST into a CelMatcher. | ||
| */ | ||
| public static CelMatcher compile(CelAbstractSyntaxTree ast) | ||
| throws CelValidationException, CelEvaluationException { | ||
| if (ast.getResultType() != SimpleType.BOOL) { | ||
| throw new IllegalArgumentException( | ||
| "CEL expression must evaluate to boolean, got: " + ast.getResultType()); | ||
| } | ||
| CelCommon.checkAllowedVariables(ast); | ||
| CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast); | ||
| return new CelMatcher(program); | ||
| } | ||
|
|
||
| /** | ||
| * Evaluates the CEL expression against the input activation. | ||
| */ | ||
| public boolean match(Object input) throws CelEvaluationException { | ||
| Object result; | ||
| if (input instanceof dev.cel.runtime.CelVariableResolver) { | ||
| result = program.eval((dev.cel.runtime.CelVariableResolver) input); | ||
| } else if (input instanceof java.util.Map) { | ||
| @SuppressWarnings("unchecked") | ||
| java.util.Map<String, ?> mapInput = (java.util.Map<String, ?>) input; | ||
| result = program.eval(mapInput); | ||
| } else { | ||
| throw new CelEvaluationException( | ||
| "Unsupported input type for CEL evaluation: " + input.getClass().getName()); | ||
| } | ||
|
|
||
| if (result instanceof Boolean) { | ||
| return (Boolean) result; | ||
| } | ||
| throw new CelEvaluationException( | ||
| "CEL expression must evaluate to boolean, got: " + result.getClass().getName()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /* | ||
| * Copyright 2026 The gRPC Authors | ||
| * | ||
| * 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 | ||
| * | ||
| * http://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 io.grpc.xds.internal.matcher; | ||
|
|
||
| import dev.cel.common.CelAbstractSyntaxTree; | ||
| import dev.cel.common.CelValidationException; | ||
| import dev.cel.common.types.SimpleType; | ||
| import dev.cel.runtime.CelEvaluationException; | ||
| import dev.cel.runtime.CelRuntime; | ||
|
|
||
| /** | ||
| * Executes compiled CEL expressions that extract a string. | ||
| */ | ||
| public final class CelStringExtractor { | ||
| private final CelRuntime.Program program; | ||
|
|
||
| private CelStringExtractor(CelRuntime.Program program) { | ||
| this.program = program; | ||
| } | ||
|
|
||
| /** | ||
| * Compiles the AST into a CelStringExtractor. | ||
| */ | ||
| public static CelStringExtractor compile(CelAbstractSyntaxTree ast) | ||
| throws CelValidationException, CelEvaluationException { | ||
| if (ast.getResultType() != SimpleType.STRING && ast.getResultType() != SimpleType.DYN) { | ||
| throw new IllegalArgumentException( | ||
| "CEL expression must evaluate to string, got: " + ast.getResultType()); | ||
| } | ||
| CelCommon.checkAllowedVariables(ast); | ||
| CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast); | ||
| return new CelStringExtractor(program); | ||
| } | ||
|
|
||
| /** | ||
| * Compiles the CEL expression string into a CelStringExtractor. | ||
| */ | ||
| public static CelStringExtractor compile(String expression) | ||
| throws CelValidationException, CelEvaluationException { | ||
| CelAbstractSyntaxTree ast = CelCommon.COMPILER.compile(expression).getAst(); | ||
| return compile(ast); | ||
| } | ||
|
|
||
| /** | ||
| * Evaluates the CEL expression against the input activation and returns the string result. | ||
| * Returns null if the result is not a string. | ||
| */ | ||
| public String extract(Object input) throws CelEvaluationException { | ||
| Object result; | ||
| if (input instanceof dev.cel.runtime.CelVariableResolver) { | ||
| result = program.eval((dev.cel.runtime.CelVariableResolver) input); | ||
| } else if (input instanceof java.util.Map) { | ||
| @SuppressWarnings("unchecked") | ||
| java.util.Map<String, ?> mapInput = (java.util.Map<String, ?>) input; | ||
| result = program.eval(mapInput); | ||
| } else { | ||
| throw new CelEvaluationException( | ||
| "Unsupported input type for CEL evaluation: " + input.getClass().getName()); | ||
| } | ||
|
|
||
| if (result instanceof String) { | ||
| return (String) result; | ||
| } | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For educational purpose - Why is this
100, should we extract it out in constant?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not entirely sure but by any chance should this be configurable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is coming from grfc:
Per is go/java-practices/constants we should use inlined literal.