diff --git a/MODULE.bazel b/MODULE.bazel index e563857d8fe..0ad9acb28aa 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -20,9 +20,6 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.re2j:re2j:1.8", "com.google.s2a.proto.v2:s2a-proto:0.1.3", "com.google.truth:truth:1.4.5", - "dev.cel:runtime:0.12.0", - "dev.cel:protobuf:0.12.0", - "dev.cel:common:0.12.0", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day "io.netty:netty-buffer:4.1.133.Final", diff --git a/build.gradle b/build.gradle index 2fba44452d6..e65261b0cc4 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,6 @@ subprojects { ignoreGradleMetadataRedirection() } } - maven { url 'https://central.sonatype.com/repository/maven-snapshots/' } } tasks.withType(JavaCompile).configureEach { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 165f114dd45..a951303d209 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,9 +37,6 @@ checkstyle = "com.puppycrawl.tools:checkstyle:10.26.1" # checkstyle 10.0+ requires Java 11+ # See https://checkstyle.sourceforge.io/releasenotes_old_8-35_10-26.html#Release_10.0 # checkForUpdates: checkstylejava8:9.+ -cel-runtime = "dev.cel:runtime:0.12.0" -cel-protobuf = "dev.cel:protobuf:0.12.0" -cel-compiler = "dev.cel:compiler:0.12.0" checkstylejava8 = "com.puppycrawl.tools:checkstyle:9.3" commons-math3 = "org.apache.commons:commons-math3:3.6.1" conscrypt = "org.conscrypt:conscrypt-openjdk-uber:2.5.2" diff --git a/repositories.bzl b/repositories.bzl index 5a2688dbb25..9691a12f286 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -25,9 +25,6 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.re2j:re2j:1.8", "com.google.s2a.proto.v2:s2a-proto:0.1.3", "com.google.truth:truth:1.4.5", - "dev.cel:runtime:0.12.0", - "dev.cel:protobuf:0.12.0", - "dev.cel:common:0.12.0", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day "io.netty:netty-buffer:4.1.133.Final", diff --git a/xds/BUILD.bazel b/xds/BUILD.bazel index e36bd37b228..9a650485c6c 100644 --- a/xds/BUILD.bazel +++ b/xds/BUILD.bazel @@ -41,9 +41,6 @@ java_library( artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), artifact("com.google.re2j:re2j"), - artifact("dev.cel:runtime"), - artifact("dev.cel:protobuf"), - artifact("dev.cel:common"), artifact("io.netty:netty-buffer"), artifact("io.netty:netty-codec"), artifact("io.netty:netty-common"), @@ -100,8 +97,6 @@ JAR_JAR_RULES = [ "rule com.google.api.expr.** io.grpc.xds.shaded.com.google.api.expr.@1", "rule com.google.security.** io.grpc.xds.shaded.com.google.security.@1", "rule dev.cel.expr.** io.grpc.xds.shaded.dev.cel.expr.@1", - "rule dev.cel.** io.grpc.xds.shaded.dev.cel.@1", - "rule cel.** io.grpc.xds.shaded.cel.@1", "rule envoy.annotations.** io.grpc.xds.shaded.envoy.annotations.@1", "rule io.envoyproxy.** io.grpc.xds.shaded.io.envoyproxy.@1", "rule udpa.annotations.** io.grpc.xds.shaded.udpa.annotations.@1", diff --git a/xds/build.gradle b/xds/build.gradle index 8036f8691ec..8394fe12f6b 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -56,18 +56,11 @@ dependencies { libraries.re2j, libraries.auto.value.annotations, libraries.protobuf.java.util - implementation(libraries.cel.runtime) { - exclude group: 'com.google.protobuf', module: 'protobuf-java' - } - implementation(libraries.cel.protobuf) { - exclude group: 'com.google.protobuf', module: 'protobuf-java' - } def nettyDependency = implementation project(':grpc-netty') testImplementation project(':grpc-api') testImplementation project(':grpc-rls') testImplementation project(':grpc-inprocess') - testImplementation libraries.cel.compiler testImplementation testFixtures(project(':grpc-core')), testFixtures(project(':grpc-api')), testFixtures(project(':grpc-util')) @@ -182,7 +175,6 @@ tasks.named("javadoc").configure { exclude 'io/grpc/xds/XdsNameResolverProvider.java' exclude 'io/grpc/xds/internal/**' exclude 'io/grpc/xds/Internal*' - exclude 'dev/cel/**' } def prefixName = 'io.grpc.xds' @@ -190,7 +182,6 @@ tasks.named("shadowJar").configure { archiveClassifier = null dependencies { include(project(':grpc-xds')) - include(dependency('dev.cel:.*')) } // Relocated packages commonly need exclusions in jacocoTestReport and javadoc // Keep in sync with BUILD.bazel's JAR_JAR_RULES @@ -207,8 +198,6 @@ tasks.named("shadowJar").configure { // TODO: missing java_package option in .proto relocate 'udpa.annotations', "${prefixName}.shaded.udpa.annotations" relocate 'xds.annotations', "${prefixName}.shaded.xds.annotations" - relocate 'dev.cel', "${prefixName}.shaded.dev.cel" - relocate 'cel', "${prefixName}.shaded.cel" exclude "**/*.proto" } diff --git a/xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java b/xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java deleted file mode 100644 index 60884af14c5..00000000000 --- a/xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 com.google.common.collect.ImmutableSet; -import dev.cel.common.CelAbstractSyntaxTree; -import dev.cel.common.CelOptions; -import dev.cel.common.ast.CelReference; -import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; -import dev.cel.runtime.CelStandardFunctions; -import dev.cel.runtime.CelStandardFunctions.StandardFunction; -import dev.cel.runtime.standard.AddOperator.AddOverload; -import java.util.Map; -import java.util.regex.Pattern; - -/** - * 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 String REQUEST_VARIABLE = "request"; - private static final CelStandardFunctions FUNCTIONS = - CelStandardFunctions.newBuilder() - .filterFunctions((func, over) -> { - if (func == StandardFunction.STRING) { - return false; - } - if (func == StandardFunction.ADD) { - return !over.equals(AddOverload.ADD_STRING) - && !over.equals(AddOverload.ADD_LIST); - } - return true; - }) - .build(); - - - - private static final ImmutableSet ALLOWED_EXACT_OVERLOAD_IDS = ImmutableSet.of( - "equals", "not_equals", "logical_and", "logical_or", "logical_not"); - - /** - * Regular expression pattern to validate internal CEL overload IDs. - * - *

Standard CEL operators and conversion functions often have empty names in the - * AST and are identified solely by their overload IDs (e.g., {@code equals} for - * {@code ==}, {@code divide_int64} for {@code /}). - * - *

This pattern matches allowed overload IDs by their prefixes (e.g., - * {@code divide}, {@code size}), optionally followed by numeric types - * (e.g., {@code int64}) and type-specific suffixes (e.g., {@code _string}, - * {@code _int64}). - */ - private static final Pattern ALLOWED_OVERLOAD_ID_PREFIX_PATTERN = Pattern.compile( - "^(size|matches|contains|startsWith|endsWith|starts_with|ends_with|" - + "timestamp|duration|in|index|has|int|uint|double|string|bytes|bool|" - + "less|less_equals|greater|greater_equals|" - + "add|subtract|multiply|divide|modulo|negate)" - + "[0-9]*(_.*)?$"); - - static final CelRuntime RUNTIME = CelRuntimeFactory.standardCelRuntimeBuilder() - .setStandardEnvironmentEnabled(false) - .setStandardFunctions(FUNCTIONS) - .setOptions(CEL_OPTIONS) - .build(); - - private CelCommon() {} - - /** - * Validates that the AST only references the allowed variable ("request") - * and supported functions as defined in gRFC A106. - */ - static void checkAllowedReferences(CelAbstractSyntaxTree ast) { - for (Map.Entry entry : ast.getReferenceMap().entrySet()) { - CelReference ref = entry.getValue(); - - // Check for variables (where overloadIds is empty) - if (!ref.value().isPresent() && ref.overloadIds().isEmpty()) { - if (!REQUEST_VARIABLE.equals(ref.name())) { - throw new IllegalArgumentException( - "CEL expression references unknown variable: " + ref.name()); - } - } else if (!ref.overloadIds().isEmpty()) { - String name = ref.name(); - if (name.isEmpty()) { - boolean allowed = false; - for (String id : ref.overloadIds()) { - if (id.equals("add_string") || id.equals("add_list") || id.endsWith("_to_string")) { - allowed = false; - break; - } - if (ALLOWED_EXACT_OVERLOAD_IDS.contains(id) - || ALLOWED_OVERLOAD_ID_PREFIX_PATTERN.matcher(id).matches()) { - allowed = true; - break; - } - } - if (!allowed) { - throw new IllegalArgumentException( - "CEL expression references unknown function with overload IDs: " - + ref.overloadIds()); - } - } else { - // Standard conversion functions (like string(x)) are named in the AST. - // We must explicitly reject 'string' here since it's disabled in the environment. - if (name.equals("string")) { - throw new IllegalArgumentException( - "CEL expression references unknown function with overload IDs: " - + ref.overloadIds()); - } - throw new IllegalArgumentException( - "CEL expression references unsupported named function: " + name); - } - } - } - } -} diff --git a/xds/src/main/java/io/grpc/xds/internal/matcher/CelStringExtractor.java b/xds/src/main/java/io/grpc/xds/internal/matcher/CelStringExtractor.java deleted file mode 100644 index 04d686ee47c..00000000000 --- a/xds/src/main/java/io/grpc/xds/internal/matcher/CelStringExtractor.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.types.SimpleType; -import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelVariableResolver; -import javax.annotation.Nullable; - -/** - * Executes compiled CEL expressions that extract a string. - */ -public final class CelStringExtractor { - private final CelRuntime.Program program; - @Nullable - private final String defaultValue; - - private CelStringExtractor(CelRuntime.Program program, @Nullable String defaultValue) { - this.program = program; - this.defaultValue = defaultValue; - } - - /** - * Compiles the AST into a CelStringExtractor with an optional default value. - * Throws an Exception if evaluation fails during compilation setup. - */ - public static CelStringExtractor compile(CelAbstractSyntaxTree ast, @Nullable String defaultValue) - throws CelEvaluationException { - if (ast.getResultType() != SimpleType.STRING && ast.getResultType() != SimpleType.DYN) { - throw new IllegalArgumentException( - "CEL expression must evaluate to string, got: " + ast.getResultType()); - } - CelCommon.checkAllowedReferences(ast); - CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast); - return new CelStringExtractor(program, defaultValue); - } - - /** - * Compiles the AST into a CelStringExtractor with no default value. - * Throws an Exception if evaluation fails during compilation setup. - */ - public static CelStringExtractor compile(CelAbstractSyntaxTree ast) - throws CelEvaluationException { - return compile(ast, null); - } - - /** - * Evaluates the CEL expression and returns the string result. - * Returns the default value if the result is not a string or if evaluation - * fails. - */ - public String extract(Object input) throws CelEvaluationException { - if (input instanceof CelVariableResolver) { - try { - Object result = program.eval((CelVariableResolver) input); - - if (result instanceof String) { - return (String) result; - } - } catch (CelEvaluationException e) { - if (defaultValue == null) { - throw e; - } - } - } else if (defaultValue == null) { - throw new CelEvaluationException( - "Unsupported input type for CEL evaluation: " - + (input == null ? "null" : input.getClass().getName())); - } - return defaultValue; - } -} diff --git a/xds/src/main/java/io/grpc/xds/internal/matcher/GrpcCelEnvironment.java b/xds/src/main/java/io/grpc/xds/internal/matcher/GrpcCelEnvironment.java deleted file mode 100644 index 2d90d86d955..00000000000 --- a/xds/src/main/java/io/grpc/xds/internal/matcher/GrpcCelEnvironment.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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 com.google.common.collect.ImmutableSet; -import dev.cel.runtime.CelVariableResolver; -import io.grpc.Metadata; -import java.util.AbstractMap; -import java.util.Optional; -import java.util.Set; -import javax.annotation.Nullable; - -/** - * CEL Environment for gRPC xDS matching. - */ -final class GrpcCelEnvironment implements CelVariableResolver { - private final MatchContext context; - - GrpcCelEnvironment(MatchContext context) { - this.context = context; - } - - @Override - public Optional find(String name) { - if (name.equals("request")) { - return Optional.of(new LazyRequestMap(this)); - } - return Optional.empty(); - } - - @Nullable - private Object getRequestField(String requestField) { - switch (requestField) { - case "headers": return new HeadersWrapper(context); - case "host": return orEmpty(context.getHost()); - case "id": return getHeader("x-request-id"); - case "method": return or(context.getMethod(), "POST"); - case "path": - case "url_path": - return orEmpty(context.getPath()); - case "query": return ""; - case "scheme": return ""; - case "protocol": return ""; - case "time": return null; - case "referer": return getHeader("referer"); - case "useragent": return getHeader("user-agent"); - - default: - return null; - } - } - - private String getHeader(String key) { - Metadata metadata = context.getMetadata(); - Iterable values = metadata.getAll( - Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); - if (values == null) { - return ""; - } - return String.join(",", values); - } - - private static String orEmpty(@Nullable String s) { - return s == null ? "" : s; - } - - private static String or(@Nullable String s, String def) { - return s == null ? def : s; - } - - private static final class LazyRequestMap extends AbstractMap { - private static final Set KNOWN_KEYS = ImmutableSet.of( - "headers", "host", "id", "method", "path", "url_path", "query", "scheme", "protocol", - "referer", "useragent", "time"); - private final GrpcCelEnvironment resolver; - - LazyRequestMap(GrpcCelEnvironment resolver) { - this.resolver = resolver; - } - - @Override - public Object get(Object key) { - if (key instanceof String) { - Object val = resolver.getRequestField((String) key); - if (val == null) { - return null; - } - return val; - } - return null; - } - - @Override - public boolean containsKey(Object key) { - boolean contains = key instanceof String && KNOWN_KEYS.contains(key); - return contains; - } - - @Override - public Set keySet() { - return KNOWN_KEYS; - } - - @Override - public int size() { - return KNOWN_KEYS.size(); - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Set> entrySet() { - throw new UnsupportedOperationException("LazyRequestMap does not support entrySet"); - } - } -} diff --git a/xds/src/main/java/io/grpc/xds/internal/matcher/HeadersWrapper.java b/xds/src/main/java/io/grpc/xds/internal/matcher/HeadersWrapper.java deleted file mode 100644 index 8c0f4e31392..00000000000 --- a/xds/src/main/java/io/grpc/xds/internal/matcher/HeadersWrapper.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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 com.google.common.collect.ImmutableSet; -import com.google.common.io.BaseEncoding; -import com.google.errorprone.annotations.DoNotCall; -import io.grpc.Metadata; -import java.util.AbstractMap; -import java.util.Set; -import javax.annotation.Nullable; - -/** - * A Map view over Metadata and MatchContext for CEL attribute resolution. - * Supports efficient lookup of headers and pseudo-headers without unnecessary copying. - */ -final class HeadersWrapper extends AbstractMap { - private static final ImmutableSet PSEUDO_HEADERS = - ImmutableSet.of(":method", ":authority", ":path"); - private final MatchContext context; - - HeadersWrapper(MatchContext context) { - this.context = context; - } - - @Override - @Nullable - public String get(Object key) { - if (!(key instanceof String)) { - return null; - } - String headerName = ((String) key).toLowerCase(java.util.Locale.ROOT); - // The "te" header is a hop-by-hop header used for protocol signaling (trailers). - // Per gRFC A41, it must be treated as not present to prevent matching logic - // from depending on transport-level semantics. - if (headerName.equals("te")) { - return null; - } - switch (headerName) { - case ":method": return context.getMethod(); - case ":authority": return context.getHost(); - case "host": return context.getHost(); - case ":path": return context.getPath(); - default: return getHeader(headerName); - } - } - - @Nullable - private String getHeader(String headerName) { - try { - if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { - Iterable values = context.getMetadata().getAll( - Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER)); - if (values == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (byte[] value : values) { - if (!first) { - sb.append(","); - } - first = false; - sb.append(BaseEncoding.base64().omitPadding().encode(value)); - } - return sb.toString(); - } - Metadata metadata = context.getMetadata(); - Iterable values = metadata.getAll( - Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER)); - if (values == null) { - return null; - } - return String.join(",", values); - } catch (IllegalArgumentException e) { - // Catching IllegalArgumentException and returning null matches Envoy's behavior - // (returning an empty optional for invalid header names). - return null; - } - } - - @Override - public boolean containsKey(Object key) { - if (!(key instanceof String)) { - return false; - } - String headerName = ((String) key).toLowerCase(java.util.Locale.ROOT); - if (headerName.equals("te")) { - return false; - } - if (PSEUDO_HEADERS.contains(headerName) || headerName.equals("host")) { - return true; - } - try { - if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { - return context.getMetadata().containsKey( - Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER)); - } - return context.getMetadata().containsKey( - Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER)); - } catch (IllegalArgumentException e) { - return false; - } - } - - private boolean isMetadataKeyResolvable(String headerName) { - try { - if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { - Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER); - } else { - Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER); - } - return true; - } catch (IllegalArgumentException e) { - return false; - } - } - - @Override - public Set keySet() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (String key : context.getMetadata().keys()) { - String lowerKey = key.toLowerCase(java.util.Locale.ROOT); - // Filter out any keys we provide specialized aliases/values for. - if (!lowerKey.equals("te") - && !lowerKey.equals("host") - && !PSEUDO_HEADERS.contains(lowerKey) - && isMetadataKeyResolvable(lowerKey)) { - builder.add(key); - } - } - builder.addAll(PSEUDO_HEADERS); - builder.add("host"); - return builder.build(); - } - - @Override - public int size() { - int count = 0; - for (String key : context.getMetadata().keys()) { - String lowerKey = key.toLowerCase(java.util.Locale.ROOT); - if (!lowerKey.equals("te") - && !lowerKey.equals("host") - && !PSEUDO_HEADERS.contains(lowerKey) - && isMetadataKeyResolvable(lowerKey)) { - count++; - } - } - return count + PSEUDO_HEADERS.size() + 1; // +1 for "host" alias - } - - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public Set> entrySet() { - throw new UnsupportedOperationException( - "Should not be called to prevent resolving all header values."); - } -} diff --git a/xds/src/main/java/io/grpc/xds/internal/matcher/MatchContext.java b/xds/src/main/java/io/grpc/xds/internal/matcher/MatchContext.java deleted file mode 100644 index a1f79bf5658..00000000000 --- a/xds/src/main/java/io/grpc/xds/internal/matcher/MatchContext.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 com.google.common.base.Preconditions; -import io.grpc.Metadata; - -public final class MatchContext { - private final Metadata metadata; - private final String path; - private final String host; - private final String method; - - public MatchContext(Metadata metadata, String path, - String host, String method) { - this.metadata = Preconditions.checkNotNull(metadata, "metadata"); - this.path = Preconditions.checkNotNull(path, "path"); - this.host = Preconditions.checkNotNull(host, "host"); - this.method = Preconditions.checkNotNull(method, "method"); - } - - public Metadata getMetadata() { - return metadata; - } - - public String getPath() { - return path; - } - - public String getHost() { - return host; - } - - public String getMethod() { - return method; - } - - public static Builder newBuilder() { - return new Builder(); - } - - public static final class Builder { - private Metadata metadata = new Metadata(); - private String path; - private String host; - private String method; - - public Builder setMetadata(Metadata metadata) { - this.metadata = metadata; - return this; - } - - public Builder setPath(String path) { - this.path = path; - return this; - } - - public Builder setHost(String host) { - this.host = host; - return this; - } - - public Builder setMethod(String method) { - this.method = method; - return this; - } - - public MatchContext build() { - return new MatchContext(metadata, path, host, method); - } - } -} diff --git a/xds/src/test/java/io/grpc/xds/internal/matcher/CelCommonTest.java b/xds/src/test/java/io/grpc/xds/internal/matcher/CelCommonTest.java deleted file mode 100644 index fd865144d9b..00000000000 --- a/xds/src/test/java/io/grpc/xds/internal/matcher/CelCommonTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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 static org.junit.Assert.fail; - -import dev.cel.common.CelAbstractSyntaxTree; -import dev.cel.common.CelFunctionDecl; -import dev.cel.common.CelOverloadDecl; -import dev.cel.common.types.SimpleType; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class CelCommonTest { - private static CelCompiler COMPILER; - - @BeforeClass - public static void setupCompiler() { - COMPILER = CelCompilerFactory.standardCelCompilerBuilder() - .addVar("request", SimpleType.DYN) - .addVar("unknown_var", SimpleType.STRING) - .addFunctionDeclarations( - CelFunctionDecl.newFunctionDeclaration( - "my_custom_func", - CelOverloadDecl.newGlobalOverload( - "my_custom_func_overload", SimpleType.BOOL, SimpleType.STRING))) - .build(); - } - - private void assertAllowed(String expression) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelCommon.checkAllowedReferences(ast); - } - - private void assertDisallowed(String expression) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - try { - CelCommon.checkAllowedReferences(ast); - fail("Should have thrown IllegalArgumentException for expression: " + expression); - } catch (IllegalArgumentException e) { - // Expected - } - } - - @Test - public void checkAllowedReferences_variables() throws Exception { - assertAllowed("request == 'foo'"); - assertDisallowed("unknown_var == 'foo'"); - } - - @Test - public void checkAllowedReferences_operators() throws Exception { - assertAllowed("request == 'foo'"); - assertAllowed("request != 'foo'"); - assertAllowed("1 < 2"); - assertAllowed("1 <= 2"); - assertAllowed("1 > 2"); - assertAllowed("1 >= 2"); - assertAllowed("1 + 2 == 3"); - assertAllowed("1 - 2 == -1"); - assertAllowed("1 * 2 == 2"); - assertAllowed("1 / 2 == 0"); - assertAllowed("1 % 2 == 1"); - assertAllowed("true && false == false"); - assertAllowed("true || false == true"); - assertAllowed("!true == false"); - } - - @Test - public void checkAllowedReferences_indexing() throws Exception { - assertAllowed("request['key'] == 'val'"); - } - - @Test - public void checkAllowedReferences_functions() throws Exception { - assertAllowed("size('foo') == 3"); - assertAllowed("'foo'.matches('.*')"); - assertAllowed("'foo'.contains('o')"); - assertAllowed("'foo'.startsWith('f')"); - assertAllowed("'foo'.endsWith('o')"); - assertAllowed("int(1) == 1"); - assertAllowed("uint(1) == 1u"); - assertAllowed("double(1) == 1.0"); - - // Disallowed functions / overloads - assertDisallowed("string(1) == '1'"); - assertDisallowed("'a' + 'b' == 'ab'"); - assertDisallowed("[1] + [2] == [1, 2]"); - assertDisallowed("my_custom_func('foo')"); - } - - @Test - public void checkAllowedReferences_additionalFunctions() throws Exception { - assertAllowed("timestamp('2026-04-20T00:00:00Z') != timestamp('2026-04-21T00:00:00Z')"); - assertAllowed("duration('1s') != duration('2s')"); - assertAllowed("'foo' in ['foo', 'bar']"); - assertAllowed("bytes('foo') == b'foo'"); - assertAllowed("bool('true') == true"); - } - - @Test - public void checkAllowedReferences_negation() throws Exception { - assertAllowed("-(1) == -1"); - assertAllowed("-(1.0) == -1.0"); - } -} diff --git a/xds/src/test/java/io/grpc/xds/internal/matcher/CelEnvironmentTest.java b/xds/src/test/java/io/grpc/xds/internal/matcher/CelEnvironmentTest.java deleted file mode 100644 index f3bd97bb878..00000000000 --- a/xds/src/test/java/io/grpc/xds/internal/matcher/CelEnvironmentTest.java +++ /dev/null @@ -1,551 +0,0 @@ -/* - * 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 static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; - -import com.google.common.io.BaseEncoding; -import dev.cel.common.CelAbstractSyntaxTree; -import dev.cel.common.CelValidationException; -import dev.cel.common.types.SimpleType; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; -import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelRuntime; -import io.grpc.Metadata; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class CelEnvironmentTest { - - private static final CelCompiler LENIENT_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .addVar("request", SimpleType.DYN) - .build(); - - private static CelAbstractSyntaxTree compileLenientAst(String expression) - throws CelValidationException { - return LENIENT_COMPILER.compile(expression).getAst(); - } - - @Test - public void headersWrapper_resolvesPseudoHeaders() { - MatchContext context = MatchContext.newBuilder() - .setPath("/path") - .setMethod("POST") - .setHost("example.com") - .build(); - - Map headers = new HeadersWrapper(context); - - assertThat(headers.get(":path")).isEqualTo("/path"); - assertThat(headers.get(":method")).isEqualTo("POST"); - assertThat(headers.get(":authority")).isEqualTo("example.com"); - } - - @Test - public void headersWrapper_resolvesStandardHeaders() { - Metadata metadata = new Metadata(); - metadata.put(Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER), "custom-val"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - Map headers = new HeadersWrapper(context); - - assertThat(headers.get("custom-key")).isEqualTo("custom-val"); - assertThat(headers.containsKey("custom-key")).isTrue(); - } - - @Test - @SuppressWarnings("DoNotCall") - public void headersWrapper_entrySet_unsupported() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - Map headers = new HeadersWrapper(context); - - try { - headers.entrySet(); - fail("Should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - assertThat(e).hasMessageThat().contains("Should not be called"); - } - } - - @Test - public void celEnvironment_resolvesRequestField() { - MatchContext context = MatchContext.newBuilder() - .setPath("/foo") - .setHost("example.com") - .setMethod("POST") - .build(); - - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isInstanceOf(Map.class); - - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - assertThat(requestMap.get("path")).isEqualTo("/foo"); - } - - @Test - public void headers_caseInsensitive() { - Metadata metadata = new Metadata(); - metadata.put(Metadata.Key.of("User-Agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-java"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - Map headers = new HeadersWrapper(context); - - // CEL lookup with different casing - assertThat(headers.get("user-agent")).isEqualTo("grpc-java"); - assertThat(headers.get("USER-AGENT")).isEqualTo("grpc-java"); - assertThat(headers.containsKey("User-Agent")).isTrue(); - assertThat(headers.containsKey("user-agent")).isTrue(); - } - - @Test - public void headers_ignoreTe() { - Metadata metadata = new Metadata(); - metadata.put(Metadata.Key.of("te", Metadata.ASCII_STRING_MARSHALLER), "trailers"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - Map headers = new HeadersWrapper(context); - - // "te" should be hidden - assertThat(headers.get("te")).isNull(); - assertThat(headers.containsKey("te")).isFalse(); - // Case insensitive check for "TE" logic too - assertThat(headers.get("TE")).isNull(); - assertThat(headers.containsKey("TE")).isFalse(); - - // Ensure "te" is also excluded from keySet() and size() - assertThat(headers.keySet()).doesNotContain("te"); - assertThat(headers.keySet()).doesNotContain("TE"); - assertThat(headers.size()).isEqualTo(4); // Pseudo (:method, :path, :authority) + "host" = 4 - } - - @Test - public void headers_hostAliasing() { - MatchContext context = MatchContext.newBuilder() - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - Map headers = new HeadersWrapper(context); - - assertThat(headers.get("host")).isEqualTo("example.com"); - assertThat(headers.get("HOST")).isEqualTo("example.com"); - assertThat(headers.get(":authority")).isEqualTo("example.com"); - - // Verify containsKey and keySet contract works correctly for host alias - assertThat(headers.containsKey("host")).isTrue(); - assertThat(headers.containsKey("HOST")).isTrue(); - assertThat(headers.keySet()).contains("host"); - } - - @Test - public void headers_binaryHeader() { - Metadata metadata = new Metadata(); - byte[] bytes = new byte[] { 0, 1, 2, 3 }; - metadata.put(Metadata.Key.of("test-bin", Metadata.BINARY_BYTE_MARSHALLER), bytes); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - Map headers = new HeadersWrapper(context); - // Expect Base64 encoded string without padding - String expected = BaseEncoding.base64().omitPadding().encode(bytes); - assertThat(headers.get("test-bin")).isEqualTo(expected); - assertThat(headers.containsKey("test-bin")).isTrue(); - } - - @Test - public void headersWrapper_invalidHeaderNames_returnsNullAndFalse() { - MatchContext context = MatchContext.newBuilder() - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - Map headers = new HeadersWrapper(context); - - // Invalid characters (spaces, special symbols) that gRPC Metadata.Key validations reject. - assertThat(headers.get("invalid header name")).isNull(); - assertThat(headers.get("bad@key")).isNull(); - assertThat(headers.containsKey("invalid header name")).isFalse(); - assertThat(headers.containsKey("bad@key")).isFalse(); - assertThat(headers.get("bad@key-bin")).isNull(); - assertThat(headers.containsKey("bad@key-bin")).isFalse(); - } - - @Test - public void celEnvironment_disabledFeatures_throwsValidationException() { - // String concatenation - try { - CelMatcherTestHelper.compileAst("'a' + 'b'"); - Assert.fail("String concatenation should be disabled"); - } catch (CelValidationException e) { - assertThat(e).hasMessageThat().contains("found no matching overload for '_+_'"); - } - - // List concatenation - try { - CelMatcherTestHelper.compileAst("[1] + [2]"); - Assert.fail("List concatenation should be disabled"); - } catch (CelValidationException e) { - assertThat(e).hasMessageThat().contains("found no matching overload for '_+_'"); - } - - // String conversion - try { - CelMatcherTestHelper.compileAst("string(1)"); - Assert.fail("String conversion should be disabled"); - } catch (CelValidationException e) { - assertThat(e).hasMessageThat().contains("undeclared reference to 'string'"); - } - - // Comprehensions - try { - CelMatcherTestHelper.compileAst("[1, 2, 3].all(x, x > 0)"); - Assert.fail("Comprehensions should be disabled"); - } catch (CelValidationException e) { - assertThat(e).hasMessageThat().contains("undeclared reference to 'all'"); - } - } - - @Test - public void celEnvironment_runtime_disabledFeatures_throwsException() throws Exception { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - GrpcCelEnvironment resolver = new GrpcCelEnvironment(context); - - // String concatenation fails at runtime evaluation (missing overload) - CelAbstractSyntaxTree stringConcatAst = compileLenientAst("'a' + 'b'"); - CelRuntime.Program stringConcatProgram = CelCommon.RUNTIME.createProgram(stringConcatAst); - Assert.assertThrows( - "String concatenation evaluation should fail", - CelEvaluationException.class, - () -> stringConcatProgram.eval(resolver)); - - // List concatenation fails at runtime evaluation - CelAbstractSyntaxTree listConcatAst = compileLenientAst("[1] + [2]"); - CelRuntime.Program listConcatProgram = CelCommon.RUNTIME.createProgram(listConcatAst); - Assert.assertThrows( - "List concatenation evaluation should fail", - CelEvaluationException.class, - () -> listConcatProgram.eval(resolver)); - - // String conversion fails at runtime evaluation - CelAbstractSyntaxTree stringConvAst = compileLenientAst("string(1)"); - CelRuntime.Program stringConvProgram = CelCommon.RUNTIME.createProgram(stringConvAst); - Assert.assertThrows( - "String conversion evaluation should fail", - CelEvaluationException.class, - () -> stringConvProgram.eval(resolver)); - } - - @Test - public void celEnvironment_resolvesLazyRequestMap() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isInstanceOf(Map.class); - - Map map = (Map) result.get(); - assertThat(map.containsKey("path")).isTrue(); - assertThat(map.size()).isAtLeast(1); - - try { - map.entrySet(); - fail("Should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // Expected - } - } - - @Test - public void celEnvironment_timeField_supportedButNull() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - assertThat(requestMap.containsKey("time")).isTrue(); - assertThat(requestMap.get("time")).isNull(); - } - - - @Test - public void headersWrapper_size() { - Metadata metadata = new Metadata(); - metadata.put(Metadata.Key.of("k1", Metadata.ASCII_STRING_MARSHALLER), "v1"); - metadata.put(Metadata.Key.of("k2", Metadata.ASCII_STRING_MARSHALLER), "v2"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - HeadersWrapper headers = new HeadersWrapper(context); - - // 2 custom headers + 3 pseudo headers (:method, :authority, :path) + "host" alias = 6 - assertThat(headers.size()).isEqualTo(6); - } - - @Test - public void celEnvironment_accessAllFields() { - Metadata metadata = new Metadata(); - metadata.put(Metadata.Key.of("x-request-id", Metadata.ASCII_STRING_MARSHALLER), "id-value"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("host") - .setMethod("GET") - .build(); - - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - - assertThat(requestMap.get("host")).isEqualTo("host"); - assertThat(requestMap.get("id")).isEqualTo("id-value"); - assertThat(requestMap.get("method")).isEqualTo("GET"); - assertThat(requestMap.get("scheme")).isEqualTo(""); - assertThat(requestMap.get("protocol")).isEqualTo(""); - assertThat(requestMap.get("query")).isEqualTo(""); - assertThat(requestMap.get("headers")).isInstanceOf(HeadersWrapper.class); - } - - @Test - public void celEnvironment_find_unknownField() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - assertThat(requestMap.get("unknown")).isNull(); - - assertThat(env.find("other").isPresent()).isFalse(); - } - - @Test - public void lazyRequestMap_unknownKey_returnsNull() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - Map map = (Map) env.find("request").get(); - - assertThat(map.get("unknown")).isNull(); - assertThat(map.get(new Object())).isNull(); - assertThat(map.containsKey(new Object())).isFalse(); - } - - @Test - public void headersWrapper_get_nonStringKey_returnsNull() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - Map headers = new HeadersWrapper(context); - assertThat(headers.get(new Object())).isNull(); - } - - @Test - public void headersWrapper_getHeader_binary_multipleValues() { - Metadata metadata = new Metadata(); - byte[] val1 = new byte[] { 1, 2, 3 }; - byte[] val2 = new byte[] { 4, 5, 6 }; - Metadata.Key key = Metadata.Key.of("bin-header-bin", Metadata.BINARY_BYTE_MARSHALLER); - metadata.put(key, val1); - metadata.put(key, val2); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - Map headers = new HeadersWrapper(context); - String expected = com.google.common.io.BaseEncoding.base64().omitPadding().encode(val1) + "," - + com.google.common.io.BaseEncoding.base64().omitPadding().encode(val2); - assertThat(headers.get("bin-header-bin")).isEqualTo(expected); - } - - @Test - public void headersWrapper_containsKey_nonStringKey_returnsFalse() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - Map headers = new HeadersWrapper(context); - assertThat(headers.containsKey(new Object())).isFalse(); - } - - @Test - public void headersWrapper_containsKey_pseudoHeader_returnsTrue() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - Map headers = new HeadersWrapper(context); - assertThat(headers.containsKey(":method")).isTrue(); - assertThat(headers.containsKey(":path")).isTrue(); - assertThat(headers.containsKey(":authority")).isTrue(); - } - - @Test - public void headersWrapper_keySet_containsExpectedKeys() { - Metadata metadata = new Metadata(); - metadata.put(Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER), "val"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - - Map headers = new HeadersWrapper(context); - Set keys = headers.keySet(); - - assertThat(keys).containsAtLeast("custom-key", ":method", ":path", ":authority"); - } - - @Test - public void headersWrapper_getHeader_missingBinaryHeader_returnsNull() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - Map headers = new HeadersWrapper(context); - assertThat(headers.get("missing-bin")).isNull(); - } - - @Test - public void celEnvironment_resolvesRefererAndUserAgent() { - Metadata metadata = new Metadata(); - metadata.put(Metadata.Key.of("referer", Metadata.ASCII_STRING_MARSHALLER), "http://example.com"); - metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-test"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - assertThat(requestMap.get("referer")).isEqualTo("http://example.com"); - assertThat(requestMap.get("useragent")).isEqualTo("grpc-test"); - } - - @Test - public void celEnvironment_joinsMultipleHeaderValues() { - Metadata metadata = new Metadata(); - Metadata.Key key = Metadata.Key.of("referer", Metadata.ASCII_STRING_MARSHALLER); - metadata.put(key, "v1"); - metadata.put(key, "v2"); - MatchContext context = MatchContext.newBuilder() - .setMetadata(metadata) - .setPath("/") - .setHost("example.com") - .setMethod("POST") - .build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - assertThat(requestMap.get("referer")).isEqualTo("v1,v2"); - } - - @Test - public void celEnvironment_find_invalidFormat() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - assertThat(env.find("other.path").isPresent()).isFalse(); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - assertThat(requestMap.get("a")).isNull(); - } - - @Test - public void lazyRequestMap_additionalMethods() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - Map map = (Map) env.find("request").get(); - - assertThat(map.isEmpty()).isFalse(); - assertThat(map.get("time")).isNull(); - } - - @Test - public void celEnvironment_missingHeader_returnsEmptyString() { - MatchContext context = MatchContext.newBuilder() - .setPath("/").setHost("example.com").setMethod("POST").build(); - GrpcCelEnvironment env = new GrpcCelEnvironment(context); - - Optional result = env.find("request"); - assertThat(result.isPresent()).isTrue(); - @SuppressWarnings("unchecked") - Map requestMap = (Map) result.get(); - assertThat(requestMap.get("referer")).isEqualTo(""); - } - - - -} diff --git a/xds/src/test/java/io/grpc/xds/internal/matcher/CelMatcherTestHelper.java b/xds/src/test/java/io/grpc/xds/internal/matcher/CelMatcherTestHelper.java deleted file mode 100644 index 0e5ef84f7d2..00000000000 --- a/xds/src/test/java/io/grpc/xds/internal/matcher/CelMatcherTestHelper.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.CelValidationException; -import dev.cel.common.types.SimpleType; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; - -public final class CelMatcherTestHelper { - 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 CelCompiler COMPILER = CelCompilerFactory.standardCelCompilerBuilder() - .setStandardEnvironmentEnabled(false) - .setStandardDeclarations(DECLARATIONS) - .addVar("request", SimpleType.DYN) - .setOptions(CelOptions.newBuilder() - .enableComprehension(false) - .build()) - .build(); - - private CelMatcherTestHelper() {} - - public static CelAbstractSyntaxTree compileAst(String expression) - throws CelValidationException { - return COMPILER.compile(expression).getAst(); - } - - public static CelStringExtractor compileStringExtractor(String expression) - throws dev.cel.common.CelException { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - return CelStringExtractor.compile(ast); - } -} diff --git a/xds/src/test/java/io/grpc/xds/internal/matcher/CelStringExtractorTest.java b/xds/src/test/java/io/grpc/xds/internal/matcher/CelStringExtractorTest.java deleted file mode 100644 index 9b8e5753e10..00000000000 --- a/xds/src/test/java/io/grpc/xds/internal/matcher/CelStringExtractorTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; - -import dev.cel.common.CelAbstractSyntaxTree; -import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelVariableResolver; -import java.util.Collections; -import java.util.Optional; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class CelStringExtractorTest { - - @Test - public void extract_simpleString() throws Exception { - CelStringExtractor extractor = CelMatcherTestHelper.compileStringExtractor("'foo'"); - CelVariableResolver resolver = name -> Optional.empty(); - String result = extractor.extract(resolver); - assertThat(result).isEqualTo("foo"); - } - - @Test - public void extract_resolvesVariable() throws Exception { - CelStringExtractor extractor = CelMatcherTestHelper.compileStringExtractor("request['key']"); - CelVariableResolver resolver = name -> { - if ("request".equals(name)) { - return Optional.of(Collections.singletonMap("key", "value")); - } - return Optional.empty(); - }; - - String result = extractor.extract(resolver); - assertThat(result).isEqualTo("value"); - } - - @Test - public void extract_nonStringResult_returnsNull() throws Exception { - CelStringExtractor extractor = CelMatcherTestHelper.compileStringExtractor("request"); - CelVariableResolver resolver = name -> { - if ("request".equals(name)) { - return Optional.of(123); - } - return Optional.empty(); - }; - - String result = extractor.extract(resolver); - assertThat(result).isNull(); - } - - @Test - public void extract_evaluationError_throws() throws Exception { - CelStringExtractor extractor = CelMatcherTestHelper.compileStringExtractor("request.bad"); - CelVariableResolver resolver = name -> { - if ("request".equals(name)) { - return Optional.of("foo"); - } - return Optional.empty(); - }; - - try { - extractor.extract(resolver); - fail("Should throw CelEvaluationException"); - } catch (CelEvaluationException e) { - // Expected - } - } - - @Test - public void extract_nonStringResult_returnsDefaultValue() throws Exception { - CelAbstractSyntaxTree ast = CelMatcherTestHelper.compileAst("request"); - CelStringExtractor extractor = CelStringExtractor.compile(ast, "default_val"); - CelVariableResolver resolver = name -> { - if ("request".equals(name)) { - return Optional.of(123); - } - return java.util.Optional.empty(); - }; - - String result = extractor.extract(resolver); - assertThat(result).isEqualTo("default_val"); - } - - @Test - public void extract_evaluationError_returnsDefaultValue() throws Exception { - CelAbstractSyntaxTree ast = CelMatcherTestHelper.compileAst("request.bad"); - CelStringExtractor extractor = CelStringExtractor.compile(ast, "default_val"); - CelVariableResolver resolver = name -> { - if ("request".equals(name)) { - return Optional.of("foo"); - } - return java.util.Optional.empty(); - }; - - String result = extractor.extract(resolver); - assertThat(result).isEqualTo("default_val"); - } - - @Test - public void extract_withCelVariableResolver_resolvesVariable() throws Exception { - CelAbstractSyntaxTree ast = CelMatcherTestHelper.compileAst("request['key']"); - CelStringExtractor extractor = CelStringExtractor.compile(ast, "default_val"); - - CelVariableResolver resolver = name -> { - if ("request".equals(name)) { - return Optional.of(Collections.singletonMap("key", "value")); - } - return java.util.Optional.empty(); - }; - - String result = extractor.extract(resolver); - assertThat(result).isEqualTo("value"); - } - - @Test - public void extract_withCelVariableResolver_evalError_returnsDefaultValue() throws Exception { - CelAbstractSyntaxTree ast = CelMatcherTestHelper.compileAst("request.bad"); - CelStringExtractor extractor = CelStringExtractor.compile(ast, "default_val"); - - CelVariableResolver resolver = name -> { - if ("request".equals(name)) { - return Optional.of("foo"); - } - return java.util.Optional.empty(); - }; - - String result = extractor.extract(resolver); - assertThat(result).isEqualTo("default_val"); - } - - @Test - public void compile_invalidSyntax_throws() { - try { - CelMatcherTestHelper.compileStringExtractor("invalid syntax ???"); - fail("Should throw CelValidationException"); - } catch (dev.cel.common.CelValidationException e) { - assertThat(e).hasMessageThat().isNotEmpty(); - } catch (Exception e) { - fail("Threw wrong exception type: " + e.getClass().getName()); - } - } - - @Test - public void extract_withCelVariableResolver() throws Exception { - CelStringExtractor extractor = CelMatcherTestHelper.compileStringExtractor("'val'"); - CelVariableResolver resolver = name -> Optional.empty(); - - assertThat(extractor.extract(resolver)).isEqualTo("val"); - } - - @Test - public void extract_unsupportedInputType_throws() throws Exception { - CelStringExtractor extractor = CelMatcherTestHelper.compileStringExtractor("'foo'"); - try { - extractor.extract("not-a-map"); - fail("Should have thrown CelEvaluationException"); - } catch (CelEvaluationException e) { - assertThat(e).hasMessageThat().contains("Unsupported input type"); - } - } -}