Skip to content

Commit 4b626ad

Browse files
committed
address comments and create registries
1 parent 7b50380 commit 4b626ad

15 files changed

Lines changed: 1761 additions & 1696 deletions
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
import com.github.xds.core.v3.TypedExtensionConfig;
20+
import dev.cel.common.CelAbstractSyntaxTree;
21+
import dev.cel.common.CelProtoAbstractSyntaxTree;
22+
import dev.cel.runtime.CelEvaluationException;
23+
24+
/**
25+
* Matcher for CEL expressions handling xDS CEL Matcher extension.
26+
*/
27+
final class CelStateMatcher implements Matcher {
28+
private final CelMatcher compiledEndpoint;
29+
30+
CelStateMatcher(CelMatcher compiledEndpoint) {
31+
this.compiledEndpoint = compiledEndpoint;
32+
}
33+
34+
@Override
35+
public boolean match(Object value) {
36+
try {
37+
return compiledEndpoint.match(value);
38+
} catch (CelEvaluationException e) {
39+
return false;
40+
}
41+
}
42+
43+
@Override
44+
public Class<?> inputType() {
45+
return GrpcCelEnvironment.class;
46+
}
47+
48+
static final class Provider implements MatcherProvider {
49+
@Override
50+
public Matcher getMatcher(TypedExtensionConfig config) {
51+
try {
52+
com.github.xds.type.matcher.v3.CelMatcher celProto = config.getTypedConfig()
53+
.unpack(com.github.xds.type.matcher.v3.CelMatcher.class);
54+
if (!celProto.hasExprMatch()) {
55+
throw new IllegalArgumentException("CelMatcher must have expr_match");
56+
}
57+
com.github.xds.type.v3.CelExpression expr = celProto.getExprMatch();
58+
if (!expr.hasCelExprChecked()) {
59+
throw new IllegalArgumentException("CelMatcher must have cel_expr_checked");
60+
}
61+
CelAbstractSyntaxTree ast =
62+
CelProtoAbstractSyntaxTree.fromCheckedExpr(
63+
expr.getCelExprChecked()).getAst();
64+
CelMatcher compiled = CelMatcher.compile(ast);
65+
66+
return new CelStateMatcher(compiled);
67+
} catch (Exception e) {
68+
throw new IllegalArgumentException("Invalid CelMatcher config", e);
69+
}
70+
}
71+
72+
@Override
73+
public String typeUrl() {
74+
return "type.googleapis.com/xds.type.matcher.v3.CelMatcher";
75+
}
76+
}
77+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import com.github.xds.core.v3.TypedExtensionConfig;
22+
import com.google.protobuf.InvalidProtocolBufferException;
23+
import io.envoyproxy.envoy.type.matcher.v3.HttpRequestHeaderMatchInput;
24+
import io.grpc.Metadata;
25+
import io.grpc.xds.internal.matcher.MatcherRunner.MatchContext;
26+
27+
/**
28+
* MatchInput for extracting HTTP headers.
29+
*/
30+
final class HeaderMatchInput implements MatchInput {
31+
private final String headerName;
32+
33+
HeaderMatchInput(String headerName) {
34+
this.headerName = checkNotNull(headerName, "headerName");
35+
if (headerName.isEmpty() || headerName.length() >= 16384) {
36+
throw new IllegalArgumentException(
37+
"Header name length must be in range [1, 16384): " + headerName.length());
38+
}
39+
if (!headerName.equals(headerName.toLowerCase(java.util.Locale.ROOT))) {
40+
throw new IllegalArgumentException("Header name must be lowercase: " + headerName);
41+
}
42+
try {
43+
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
44+
Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER);
45+
} else {
46+
Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
47+
}
48+
} catch (IllegalArgumentException e) {
49+
throw new IllegalArgumentException("Invalid header name: " + headerName, e);
50+
}
51+
}
52+
53+
@Override
54+
public Object apply(MatchContext context) {
55+
if ("te".equals(headerName)) {
56+
return null;
57+
}
58+
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
59+
Iterable<byte[]> values = context.getMetadata().getAll(
60+
Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER));
61+
if (values == null) {
62+
return null;
63+
}
64+
StringBuilder sb = new StringBuilder();
65+
boolean first = true;
66+
for (byte[] value : values) {
67+
if (!first) {
68+
sb.append(",");
69+
}
70+
first = false;
71+
sb.append(com.google.common.io.BaseEncoding.base64().encode(value));
72+
}
73+
return sb.toString();
74+
}
75+
Metadata metadata = context.getMetadata();
76+
Iterable<String> values = metadata.getAll(
77+
Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER));
78+
if (values == null) {
79+
return null;
80+
}
81+
return String.join(",", values);
82+
}
83+
84+
@Override
85+
public Class<?> outputType() {
86+
return String.class;
87+
}
88+
89+
static final class Provider implements MatchInputProvider {
90+
@Override
91+
public MatchInput getInput(TypedExtensionConfig config) {
92+
try {
93+
HttpRequestHeaderMatchInput proto = config.getTypedConfig()
94+
.unpack(HttpRequestHeaderMatchInput.class);
95+
return new HeaderMatchInput(proto.getHeaderName());
96+
} catch (InvalidProtocolBufferException e) {
97+
throw new IllegalArgumentException(
98+
"Invalid input config: " + config.getTypedConfig().getTypeUrl(), e);
99+
}
100+
}
101+
102+
@Override
103+
public String typeUrl() {
104+
return "type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput";
105+
}
106+
}
107+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
import com.github.xds.core.v3.TypedExtensionConfig;
20+
import io.grpc.xds.internal.matcher.MatcherRunner.MatchContext;
21+
22+
/**
23+
* MatchInput for extracting CEL environment from HTTP attributes.
24+
*/
25+
final class HttpAttributesCelMatchInput implements MatchInput {
26+
static final HttpAttributesCelMatchInput INSTANCE = new HttpAttributesCelMatchInput();
27+
28+
private HttpAttributesCelMatchInput() {}
29+
30+
@Override
31+
public Object apply(MatchContext context) {
32+
return new GrpcCelEnvironment(context);
33+
}
34+
35+
@Override
36+
public Class<?> outputType() {
37+
return GrpcCelEnvironment.class;
38+
}
39+
40+
static final class Provider implements MatchInputProvider {
41+
@Override
42+
public MatchInput getInput(TypedExtensionConfig config) {
43+
return INSTANCE;
44+
}
45+
46+
@Override
47+
public String typeUrl() {
48+
return "type.googleapis.com/xds.type.matcher.v3.HttpAttributesCelMatchInput";
49+
}
50+
}
51+
}

xds/src/main/java/io/grpc/xds/internal/matcher/MatchInput.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,25 @@
1616

1717
package io.grpc.xds.internal.matcher;
1818

19+
import io.grpc.xds.internal.matcher.MatcherRunner.MatchContext;
1920
import javax.annotation.Nullable;
2021

2122
/**
2223
* Interface for extracting values from a match context (e.g. HTTP headers).
2324
*/
24-
public interface MatchInput<T> {
25+
public interface MatchInput {
2526
/**
2627
* Extracts the value from the context.
2728
* @param context the context (e.g. Metadata, Attributes)
2829
* @return the extracted value, or null if not found.
2930
*/
3031
@Nullable
31-
Object apply(T context);
32+
Object apply(MatchContext context);
33+
34+
/**
35+
* Returns the type of value extracted by this input.
36+
*/
37+
default Class<?> outputType() {
38+
return Object.class;
39+
}
3240
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
import com.github.xds.core.v3.TypedExtensionConfig;
20+
21+
/**
22+
* Provider interface for creating {@link MatchInput} instances.
23+
*/
24+
public interface MatchInputProvider {
25+
/**
26+
* Returns the corresponding {@link MatchInput} for the given config.
27+
*/
28+
MatchInput getInput(TypedExtensionConfig config);
29+
30+
/**
31+
* Returns the type URL supported by this provider.
32+
*/
33+
String typeUrl();
34+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
import com.google.common.annotations.VisibleForTesting;
20+
import java.util.Map;
21+
import java.util.concurrent.ConcurrentHashMap;
22+
import javax.annotation.Nullable;
23+
24+
/**
25+
* Registry for {@link MatchInputProvider}s.
26+
*/
27+
public final class MatchInputRegistry {
28+
private static final MatchInputRegistry DEFAULT_INSTANCE = new MatchInputRegistry();
29+
30+
private final Map<String, MatchInputProvider> providers = new ConcurrentHashMap<>();
31+
32+
public static MatchInputRegistry getDefaultRegistry() {
33+
return DEFAULT_INSTANCE;
34+
}
35+
36+
@VisibleForTesting
37+
public MatchInputRegistry() {
38+
register(new HeaderMatchInput.Provider());
39+
register(new HttpAttributesCelMatchInput.Provider());
40+
}
41+
42+
public void register(MatchInputProvider provider) {
43+
providers.put(provider.typeUrl(), provider);
44+
}
45+
46+
@Nullable
47+
public MatchInputProvider getProvider(String typeUrl) {
48+
return providers.get(typeUrl);
49+
}
50+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
/**
20+
* Interface that defines a matcher that can match a given value.
21+
*/
22+
public interface Matcher {
23+
/**
24+
* Returns true if the value matches the matcher.
25+
*/
26+
boolean match(Object value);
27+
28+
/**
29+
* Returns the type of value accepted by this matcher.
30+
*/
31+
default Class<?> inputType() {
32+
return Object.class;
33+
}
34+
}

0 commit comments

Comments
 (0)