Skip to content

Commit e2e495e

Browse files
committed
POC: JsonFieldName support
1 parent b8eafbf commit e2e495e

8 files changed

Lines changed: 171 additions & 33 deletions

File tree

bundle/src/test/java/dev/cel/bundle/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ java_library(
6767
"@maven//:com_google_truth_extensions_truth_proto_extension",
6868
"@maven//:junit_junit",
6969
"@maven//:org_jspecify_jspecify",
70+
"//testing/protos:single_file_java_proto",
7071
],
7172
)
7273

bundle/src/test/java/dev/cel/bundle/CelImplTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
import dev.cel.runtime.CelUnknownSet;
113113
import dev.cel.runtime.CelVariableResolver;
114114
import dev.cel.runtime.UnknownContext;
115+
import dev.cel.testing.testdata.SingleFileProto.SingleFile;
115116
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
116117
import java.time.Instant;
117118
import java.util.ArrayList;
@@ -2193,6 +2194,24 @@ public void toBuilder_isImmutable() {
21932194
assertThat(newRuntimeBuilder).isNotEqualTo(celImpl.toRuntimeBuilder());
21942195
}
21952196

2197+
@Test
2198+
public void eval_withJsonFieldName()
2199+
throws Exception {
2200+
Cel cel =
2201+
standardCelBuilderWithMacros()
2202+
.addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName()))
2203+
.addMessageTypes(SingleFile.getDescriptor())
2204+
.setOptions(CelOptions.current().enableJsonFieldNames(true).build())
2205+
.build();
2206+
CelAbstractSyntaxTree ast =
2207+
cel.compile("file.camelCased").getAst();
2208+
2209+
Object result = cel.createProgram(ast).eval(ImmutableMap.of("file", SingleFile.newBuilder().setName("foo").build()));
2210+
2211+
assertThat(result).isEqualTo("foo");
2212+
}
2213+
2214+
21962215
private static TypeProvider aliasingProvider(ImmutableMap<String, Type> typeAliases) {
21972216
return new TypeProvider() {
21982217
@Override

checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -456,9 +456,13 @@ public CelCheckerLegacyImpl build() {
456456
}
457457

458458
CelTypeProvider messageTypeProvider =
459-
new ProtoMessageTypeProvider(
460-
CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
461-
fileTypeSet, celOptions.resolveTypeDependencies()));
459+
ProtoMessageTypeProvider.newBuilder()
460+
.setAllowJsonFieldNames(celOptions.enableJsonFieldNames())
461+
.setCelDescriptors(
462+
CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
463+
fileTypeSet, celOptions.resolveTypeDependencies()))
464+
.build();
465+
462466
if (celTypeProvider != null && fileTypeSet.isEmpty()) {
463467
messageTypeProvider = celTypeProvider;
464468
} else if (celTypeProvider != null) {

common/src/main/java/dev/cel/common/CelOptions.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public enum ProtoUnsetFieldOptions {
8383

8484
public abstract boolean enableNamespacedDeclarations();
8585

86+
public abstract boolean enableJsonFieldNames();
87+
8688
// Evaluation related options
8789

8890
public abstract boolean disableCelStandardEquality();
@@ -150,6 +152,7 @@ public static Builder newBuilder() {
150152
.enableTimestampEpoch(false)
151153
.enableHeterogeneousNumericComparisons(false)
152154
.enableNamespacedDeclarations(true)
155+
.enableJsonFieldNames(false)
153156
// Evaluation options
154157
.disableCelStandardEquality(true)
155158
.evaluateCanonicalTypesToNativeValues(false)
@@ -170,7 +173,8 @@ public static Builder newBuilder() {
170173
.enableStringConcatenation(true)
171174
.enableListConcatenation(true)
172175
.enableComprehension(true)
173-
.maxRegexProgramSize(-1);
176+
.maxRegexProgramSize(-1)
177+
;
174178
}
175179

176180
/**
@@ -529,6 +533,12 @@ public abstract static class Builder {
529533
*/
530534
public abstract Builder maxRegexProgramSize(int value);
531535

536+
/**
537+
* TODO
538+
*/
539+
public abstract Builder enableJsonFieldNames(boolean value);
540+
541+
532542
public abstract CelOptions build();
533543
}
534544
}

common/src/main/java/dev/cel/common/types/ProtoMessageTypeProvider.java

Lines changed: 111 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@
1414

1515
package dev.cel.common.types;
1616

17-
import static com.google.common.collect.ImmutableList.toImmutableList;
1817
import static com.google.common.collect.ImmutableMap.toImmutableMap;
1918

2019
import com.google.common.collect.ImmutableCollection;
21-
import com.google.common.collect.ImmutableList;
2220
import com.google.common.collect.ImmutableMap;
2321
import com.google.common.collect.ImmutableMultimap;
2422
import com.google.common.collect.ImmutableSet;
@@ -34,11 +32,11 @@
3432
import dev.cel.common.CelDescriptorUtil;
3533
import dev.cel.common.CelDescriptors;
3634
import dev.cel.common.internal.FileDescriptorSetConverter;
35+
import java.util.Arrays;
3736
import java.util.Collection;
3837
import java.util.HashMap;
3938
import java.util.Map;
4039
import java.util.Optional;
41-
import java.util.function.Function;
4240

4341
/**
4442
* The {@code ProtoMessageTypeProvider} implements the {@link CelTypeProvider} interface to provide
@@ -68,35 +66,35 @@ public final class ProtoMessageTypeProvider implements CelTypeProvider {
6866
.buildOrThrow();
6967

7068
private final ImmutableMap<String, CelType> allTypes;
69+
private final boolean allowJsonFieldNames;
7170

71+
/** Returns a new builder for {@link ProtoMessageTypeProvider}. */
72+
public static Builder newBuilder() {
73+
return new Builder();
74+
}
75+
76+
@Deprecated
7277
public ProtoMessageTypeProvider() {
73-
this(CelDescriptors.builder().build());
78+
this(CelDescriptors.builder().build(), false);
7479
}
7580

81+
@Deprecated
7682
public ProtoMessageTypeProvider(FileDescriptorSet descriptorSet) {
7783
this(
7884
CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
79-
FileDescriptorSetConverter.convert(descriptorSet)));
85+
FileDescriptorSetConverter.convert(descriptorSet)), false);
8086
}
8187

88+
@Deprecated
8289
public ProtoMessageTypeProvider(Iterable<Descriptor> descriptors) {
8390
this(
8491
CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
85-
ImmutableSet.copyOf(Iterables.transform(descriptors, Descriptor::getFile))));
92+
ImmutableSet.copyOf(Iterables.transform(descriptors, Descriptor::getFile))), false);
8693
}
8794

95+
@Deprecated
8896
public ProtoMessageTypeProvider(ImmutableSet<FileDescriptor> fileDescriptors) {
89-
this(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors));
90-
}
91-
92-
public ProtoMessageTypeProvider(CelDescriptors celDescriptors) {
93-
this.allTypes =
94-
ImmutableMap.<String, CelType>builder()
95-
.putAll(createEnumTypes(celDescriptors.enumDescriptors()))
96-
.putAll(
97-
createProtoMessageTypes(
98-
celDescriptors.messageTypeDescriptors(), celDescriptors.extensionDescriptors()))
99-
.buildOrThrow();
97+
this(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors), false);
10098
}
10199

102100
@Override
@@ -120,8 +118,14 @@ private ImmutableMap<String, CelType> createProtoMessageTypes(
120118
if (protoMessageTypes.containsKey(descriptor.getFullName())) {
121119
continue;
122120
}
123-
ImmutableList<String> fieldNames =
124-
descriptor.getFields().stream().map(FieldDescriptor::getName).collect(toImmutableList());
121+
122+
ImmutableSet.Builder<String> fieldNamesBuilder = ImmutableSet.builder() ;
123+
for (FieldDescriptor fd : descriptor.getFields()) {
124+
fieldNamesBuilder.add(fd.getName());
125+
if (allowJsonFieldNames) {
126+
fieldNamesBuilder.add(fd.getJsonName());
127+
}
128+
}
125129

126130
Map<String, FieldDescriptor> extensionFields = new HashMap<>();
127131
for (FieldDescriptor extension : extensionMap.get(descriptor.getFullName())) {
@@ -133,7 +137,7 @@ private ImmutableMap<String, CelType> createProtoMessageTypes(
133137
descriptor.getFullName(),
134138
ProtoMessageType.create(
135139
descriptor.getFullName(),
136-
ImmutableSet.copyOf(fieldNames),
140+
fieldNamesBuilder.build(),
137141
new FieldResolver(this, descriptor)::findField,
138142
new FieldResolver(this, extensions)::findField));
139143
}
@@ -158,19 +162,35 @@ private ImmutableMap<String, CelType> createEnumTypes(
158162
}
159163

160164
private static class FieldResolver {
161-
private final CelTypeProvider celTypeProvider;
165+
private final ProtoMessageTypeProvider protoMessageTypeProvider;
162166
private final ImmutableMap<String, FieldDescriptor> fields;
163167

164-
private FieldResolver(CelTypeProvider celTypeProvider, Descriptor descriptor) {
168+
private static ImmutableMap<String, FieldDescriptor> collectFieldDescriptorMap(ProtoMessageTypeProvider protoMessageTypeProvider, Descriptor descriptor) {
169+
ImmutableMap.Builder<String, FieldDescriptor> builder = ImmutableMap.builder();
170+
for (FieldDescriptor fd : descriptor.getFields()) {
171+
builder.put(fd.getName(), fd);
172+
if (protoMessageTypeProvider.allowJsonFieldNames) {
173+
builder.put(fd.getJsonName(), fd);
174+
}
175+
}
176+
177+
return builder.buildKeepingLast();
178+
}
179+
180+
private FieldResolver(
181+
ProtoMessageTypeProvider protoMessageTypeProvider,
182+
Descriptor descriptor
183+
) {
165184
this(
166-
celTypeProvider,
167-
descriptor.getFields().stream()
168-
.collect(toImmutableMap(FieldDescriptor::getName, Function.identity())));
185+
protoMessageTypeProvider,
186+
collectFieldDescriptorMap(protoMessageTypeProvider, descriptor)
187+
);
169188
}
170189

171190
private FieldResolver(
172-
CelTypeProvider celTypeProvider, ImmutableMap<String, FieldDescriptor> fields) {
173-
this.celTypeProvider = celTypeProvider;
191+
ProtoMessageTypeProvider protoMessageTypeProvider,
192+
ImmutableMap<String, FieldDescriptor> fields) {
193+
this.protoMessageTypeProvider = protoMessageTypeProvider;
174194
this.fields = fields;
175195
}
176196

@@ -203,11 +223,11 @@ private Optional<CelType> findFieldInternal(FieldDescriptor fieldDescriptor) {
203223
String messageName = descriptor.getFullName();
204224
fieldType =
205225
CelTypes.getWellKnownCelType(messageName)
206-
.orElse(celTypeProvider.findType(descriptor.getFullName()).orElse(null));
226+
.orElse(protoMessageTypeProvider.findType(descriptor.getFullName()).orElse(null));
207227
break;
208228
case ENUM:
209229
EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
210-
fieldType = celTypeProvider.findType(enumDescriptor.getFullName()).orElse(null);
230+
fieldType = protoMessageTypeProvider.findType(enumDescriptor.getFullName()).orElse(null);
211231
break;
212232
default:
213233
fieldType = PROTO_TYPE_TO_CEL_TYPE.get(fieldDescriptor.getType());
@@ -222,4 +242,66 @@ private Optional<CelType> findFieldInternal(FieldDescriptor fieldDescriptor) {
222242
return Optional.of(fieldType);
223243
}
224244
}
245+
246+
/** Builder for {@link ProtoMessageTypeProvider}. */
247+
public static final class Builder {
248+
private final ImmutableSet.Builder<FileDescriptor> fileDescriptors = ImmutableSet.builder();
249+
private boolean allowJsonFieldNames;
250+
private CelDescriptors celDescriptors;
251+
252+
/** Adds a {@link FileDescriptor} to the provider. */
253+
public Builder addFileDescriptors(FileDescriptor... fileDescriptors) {
254+
return addFileDescriptors(Arrays.asList(fileDescriptors));
255+
}
256+
257+
/** Adds a collection of {@link FileDescriptor}s to the provider. */
258+
public Builder addFileDescriptors(Iterable<FileDescriptor> fileDescriptors) {
259+
this.fileDescriptors.addAll(fileDescriptors);
260+
return this;
261+
}
262+
263+
/** Adds a collection of {@link Descriptor}s. The parent file of each descriptor is added. */
264+
public Builder addDescriptors(Iterable<Descriptor> descriptors) {
265+
this.fileDescriptors.addAll(Iterables.transform(descriptors, Descriptor::getFile));
266+
return this;
267+
}
268+
269+
/** Adds a {@link FileDescriptorSet} to the provider. */
270+
public Builder addFileDescriptorSet(FileDescriptorSet fileDescriptorSet) {
271+
this.fileDescriptors.addAll(FileDescriptorSetConverter.convert(fileDescriptorSet));
272+
return this;
273+
}
274+
275+
public Builder setAllowJsonFieldNames(boolean allowJsonFieldNames) {
276+
this.allowJsonFieldNames = allowJsonFieldNames;
277+
return this;
278+
}
279+
280+
public Builder setCelDescriptors(CelDescriptors celDescriptors) {
281+
this.celDescriptors = celDescriptors;
282+
return this;
283+
}
284+
285+
/** Builds the {@link ProtoMessageTypeProvider}. */
286+
public ProtoMessageTypeProvider build() {
287+
// CelDescriptors celDescriptors = CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors.build());
288+
return new ProtoMessageTypeProvider(celDescriptors, allowJsonFieldNames);
289+
}
290+
291+
private Builder() {}
292+
}
293+
294+
private ProtoMessageTypeProvider(
295+
CelDescriptors celDescriptors,
296+
boolean allowJsonFieldNames
297+
) {
298+
this.allowJsonFieldNames = allowJsonFieldNames;
299+
this.allTypes =
300+
ImmutableMap.<String, CelType>builder()
301+
.putAll(createEnumTypes(celDescriptors.enumDescriptors()))
302+
.putAll(
303+
createProtoMessageTypes(
304+
celDescriptors.messageTypeDescriptors(), celDescriptors.extensionDescriptors()))
305+
.buildOrThrow();
306+
}
225307
}

common/src/test/java/dev/cel/common/types/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ java_library(
1616
"//common/types:cel_types",
1717
"//common/types:message_type_provider",
1818
"//common/types:type_providers",
19+
"//testing/protos:single_file_java_proto",
1920
"@cel_spec//proto/cel/expr:checked_java_proto",
2021
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
2122
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",

common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import com.google.common.collect.ImmutableList;
2121
import com.google.common.collect.ImmutableSet;
2222
import dev.cel.common.types.CelTypeProvider.CombinedCelTypeProvider;
23+
import dev.cel.common.types.StructType.Field;
2324
import dev.cel.expr.conformance.proto2.TestAllTypes;
2425
import dev.cel.expr.conformance.proto2.TestAllTypesExtensions;
26+
import dev.cel.testing.testdata.SingleFileProto.SingleFile;
2527
import java.util.Optional;
2628
import org.junit.Test;
2729
import org.junit.runner.RunWith;
@@ -254,4 +256,22 @@ public void types_combinedDuplicateProviderIsSameAsFirst() {
254256
CombinedCelTypeProvider combined = new CombinedCelTypeProvider(proto3Provider, proto3Provider);
255257
assertThat(combined.types()).hasSize(proto3Provider.types().size());
256258
}
259+
260+
@Test
261+
public void findField_withJsonNameOption() {
262+
ProtoMessageTypeProvider typeProvider =
263+
ProtoMessageTypeProvider.newBuilder()
264+
.addFileDescriptors(SingleFile.getDescriptor().getFile())
265+
.setAllowJsonFieldNames(true)
266+
.build();
267+
268+
ProtoMessageType msgType = (ProtoMessageType) typeProvider.findType(SingleFile.getDescriptor().getFullName()).get();
269+
270+
// Note that these are the same fields, with json_name option set
271+
Optional<Field> snakeCasedField = msgType.findField("snake_cased");
272+
Optional<Field> jsonNameField = msgType.findField("camelCased");
273+
274+
assertThat(snakeCasedField).hasValue(Field.of("snake_cased", SimpleType.STRING));
275+
assertThat(jsonNameField).hasValue(Field.of("camelCased", SimpleType.STRING));
276+
}
257277
}

testing/src/test/resources/protos/single_file.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ message SingleFile {
2626

2727
string name = 1;
2828
Path path = 2;
29+
string snake_cased = 3 [json_name = "camelCased"];
2930
}

0 commit comments

Comments
 (0)