Skip to content

Commit 68bdd8c

Browse files
authored
REPL -- cel-spec pb2 and json name support (#1294)
* Include pb2 extensions for cel-spec in repl * Update value formatting for extension fields * Add REPL support for JSON names. - Fix bug for messages with extensions using json types. * Add test cases for name collisions * Fix type list test
1 parent d19e782 commit 68bdd8c

14 files changed

Lines changed: 452 additions & 154 deletions

cel/cel_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3702,21 +3702,55 @@ func TestJSONFieldNames(t *testing.T) {
37023702
expr: `dyn(msg).single_int32 == dyn(msg).singleInt32`,
37033703
jsonFieldNames: true,
37043704
},
3705+
{
3706+
name: "proto with extensions",
3707+
expr: `google.expr.proto2.test.ExampleType{fooBar: 'value'}.fooBar == 'value'`,
3708+
jsonFieldNames: true,
3709+
},
3710+
{
3711+
name: "json opt fields",
3712+
expr: "jsonOptMsg.int32_snake_case_json_name == 1 && " +
3713+
"jsonOptMsg.int64CamelCaseJsonName == 2 && " +
3714+
"jsonOptMsg.uint32DefaultJsonName == 3u && " +
3715+
"jsonOptMsg.`uint64-custom-json-name` == 4u && " +
3716+
"jsonOptMsg.single_string == 'shadows' && " +
3717+
"jsonOptMsg.singleString == 'shadowed'",
3718+
jsonFieldNames: true,
3719+
},
3720+
{
3721+
name: "json opt fields fallback",
3722+
expr: "dyn(jsonOptMsg).int32_snake_case_json_name == 1 && " +
3723+
"dyn(jsonOptMsg).`uint64-custom-json-name` == 4u && " +
3724+
"dyn(jsonOptMsg).single_string == 'shadows' && " +
3725+
"dyn(jsonOptMsg).string_json_name_shadows == 'shadows' && " +
3726+
"dyn(jsonOptMsg).singleString == 'shadowed'",
3727+
jsonFieldNames: true,
3728+
},
37053729
}
37063730
msg := &proto3pb.TestAllTypes{
37073731
SingleInt32: 1,
37083732
MapStringString: map[string]string{
37093733
"key": "value",
37103734
},
37113735
}
3736+
jsonOptMsg := &proto3pb.TestJsonNames{
3737+
Int32SnakeCaseJsonName: 1,
3738+
Int64CamelCaseJsonName: 2,
3739+
Uint32DefaultJsonName: 3,
3740+
Uint64CustomJsonName: 4,
3741+
StringJsonNameShadows: "shadows",
3742+
SingleString: "shadowed",
3743+
}
37123744
for _, tst := range tests {
37133745
tc := tst
37143746
t.Run(tc.name, func(t *testing.T) {
37153747
env, err := NewEnv(
3748+
EnableIdentifierEscapeSyntax(),
37163749
JSONFieldNames(tc.jsonFieldNames),
3717-
Types(msg),
3750+
Types(msg, &proto2pb.ExternalMessageType{}, jsonOptMsg),
37183751
Container(string(msg.ProtoReflect().Descriptor().ParentFile().Package())),
37193752
Variable("msg", ObjectType(string(msg.ProtoReflect().Descriptor().FullName()))),
3753+
Variable("jsonOptMsg", ObjectType(string(jsonOptMsg.ProtoReflect().Descriptor().FullName()))),
37203754
)
37213755
if err != nil {
37223756
t.Fatalf("NewEnv() failed: %v", err)
@@ -3729,7 +3763,7 @@ func TestJSONFieldNames(t *testing.T) {
37293763
if err != nil {
37303764
t.Fatalf("env.Program() failed: %v", err)
37313765
}
3732-
out, _, err := prg.Eval(map[string]any{"msg": msg})
3766+
out, _, err := prg.Eval(map[string]any{"msg": msg, "jsonOptMsg": jsonOptMsg})
37333767
if err != nil {
37343768
t.Fatalf("prg.Eval() failed: %v", err)
37353769
}

common/types/object.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,14 @@ func (o *protoObj) format(sb *strings.Builder) {
187187
if i > 0 {
188188
sb.WriteString(", ")
189189
}
190-
sb.WriteString(fmt.Sprintf("%s: ", field.Name()))
191-
formatTo(sb, o.Get(String(field.Name())))
190+
name := String(field.Name())
191+
if field.IsExtension() {
192+
name = String(field.FullName())
193+
fmt.Fprintf(sb, "`%s`: ", name)
194+
} else {
195+
fmt.Fprintf(sb, "%s: ", name)
196+
}
197+
formatTo(sb, o.Get(name))
192198
}
193199
sb.WriteString("}")
194200
}

common/types/pb/file_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func TestFileDescriptionGetTypes(t *testing.T) {
100100
"google.expr.proto3.test.TestAllTypes.NestedMessage",
101101
"google.expr.proto3.test.TestAllTypes.MapStringStringEntry",
102102
"google.expr.proto3.test.TestAllTypes.MapInt64NestedTypeEntry",
103+
"google.expr.proto3.test.TestJsonNames",
103104
"google.expr.proto3.test.NestedTestAllTypes"}
104105
if len(fd.GetTypeNames()) != len(expected) {
105106
t.Errorf("got '%v', wanted '%v'", fd.GetTypeNames(), expected)

common/types/pb/type.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,24 @@ func (td *TypeDescription) FieldMap() map[string]*FieldDescription {
107107

108108
// FieldByName returns (FieldDescription, true) if the field name is declared within the type.
109109
func (td *TypeDescription) FieldByName(name string) (*FieldDescription, bool) {
110+
if td.jsonFieldNames {
111+
fd, found := td.jsonFieldMap[name]
112+
if found {
113+
return fd, true
114+
}
115+
}
116+
110117
fd, found := td.fieldMap[name]
111118
if found {
112119
return fd, true
113120
}
121+
114122
extFieldMap, found := td.extensions[td.typeName]
115123
if found {
116124
fd, found = extFieldMap[name]
117125
return fd, found
118126
}
119-
if td.jsonFieldNames {
120-
fd, found = td.jsonFieldMap[name]
121-
return fd, found
122-
}
127+
123128
return nil, false
124129
}
125130

repl/evaluator.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,10 @@ func (o *typeOption) String() string {
994994
return fmt.Sprintf("%%load_descriptors %s '%s'", flags, o.path)
995995
}
996996

997+
func (o *typeOption) Option() cel.EnvOption {
998+
return cel.TypeDescs(o.fds)
999+
}
1000+
9971001
type backtickOpt struct {
9981002
enabled bool
9991003
}
@@ -1009,8 +1013,17 @@ func (o *backtickOpt) String() string {
10091013
return "%option --enable_escaped_fields"
10101014
}
10111015

1012-
func (o *typeOption) Option() cel.EnvOption {
1013-
return cel.TypeDescs(o.fds)
1016+
type jsonOpt struct {
1017+
enabled bool
1018+
}
1019+
1020+
func (o *jsonOpt) Option() cel.EnvOption {
1021+
1022+
return cel.JSONFieldNames(o.enabled)
1023+
}
1024+
1025+
func (o *jsonOpt) String() string {
1026+
return "%option --enable_escaped_fields"
10141027
}
10151028

10161029
type containerOption struct {
@@ -1083,6 +1096,11 @@ func (e *Evaluator) setOption(args []string) error {
10831096
if err != nil {
10841097
issues = append(issues, fmt.Sprintf("enable_escaped_fields: %v", err))
10851098
}
1099+
case "--enable_json_field_names":
1100+
err := e.AddSerializableOption(&jsonOpt{enabled: true})
1101+
if err != nil {
1102+
issues = append(issues, fmt.Sprintf("enable_json_field_names: %v", err))
1103+
}
10861104
case "--enable_partial_eval":
10871105
err := e.EnablePartialEval()
10881106
if err != nil {
@@ -1190,14 +1208,17 @@ func deps(d protoreflect.FileDescriptor) []*descpb.FileDescriptorProto {
11901208
func (e *Evaluator) loadDescriptorFromPackage(pkg string) error {
11911209
switch pkg {
11921210
case "cel-spec-test-types":
1193-
fdp := (&test2pb.TestAllTypes{}).ProtoReflect().Type().Descriptor().ParentFile()
1194-
fdp2 := (&test3pb.TestAllTypes{}).ProtoReflect().Type().Descriptor().ParentFile()
1211+
fdp2 := (&test2pb.TestAllTypes{}).ProtoReflect().Type().Descriptor().ParentFile()
1212+
fdp2ext := (&test2pb.Proto2ExtensionScopedMessage{}).ProtoReflect().Type().Descriptor().ParentFile()
1213+
fdp3 := (&test3pb.TestAllTypes{}).ProtoReflect().Type().Descriptor().ParentFile()
11951214

1196-
descriptorProtos := deps(fdp)
1215+
// We only depend on WKTs.
1216+
descriptorProtos := deps(fdp2)
11971217

11981218
descriptorProtos = append(descriptorProtos,
1199-
protodesc.ToFileDescriptorProto(fdp),
1200-
protodesc.ToFileDescriptorProto(fdp2))
1219+
protodesc.ToFileDescriptorProto(fdp2),
1220+
protodesc.ToFileDescriptorProto(fdp2ext),
1221+
protodesc.ToFileDescriptorProto(fdp3))
12011222

12021223
fds := descpb.FileDescriptorSet{
12031224
File: descriptorProtos,

repl/evaluator_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,37 @@ func TestProcess(t *testing.T) {
903903
wantExit: false,
904904
wantError: false,
905905
},
906+
{
907+
name: "LoadDescriptorsPackageSpecExtensions",
908+
commands: []Cmder{
909+
&simpleCmd{
910+
cmd: "load_descriptors",
911+
args: []string{
912+
"--pkg",
913+
"cel-spec-test-types",
914+
},
915+
},
916+
&simpleCmd{
917+
cmd: "option",
918+
args: []string{
919+
"--container",
920+
"cel.expr.conformance",
921+
},
922+
},
923+
&simpleCmd{
924+
cmd: "option",
925+
args: []string{
926+
"--enable_escaped_fields",
927+
},
928+
},
929+
&evalCmd{
930+
expr: "proto2.TestAllTypes{`cel.expr.conformance.proto2.int32_ext`: 42}.`cel.expr.conformance.proto2.int32_ext` == 42",
931+
},
932+
},
933+
wantText: `true : bool`,
934+
wantExit: false,
935+
wantError: false,
936+
},
906937
{
907938
name: "LoadDescriptorsPackageRpc",
908939
commands: []Cmder{

repl/main/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ package main
4343
import (
4444
"fmt"
4545
"os"
46+
"path/filepath"
4647

4748
"github.com/google/cel-go/repl"
4849

@@ -52,6 +53,7 @@ import (
5253
func main() {
5354
var c readline.Config
5455
c.Prompt = "cel-repl> "
56+
c.HistoryFile = filepath.Join(os.Getenv("HOME"), ".cel-repl.history")
5557

5658
err := c.Init()
5759
if err != nil {

0 commit comments

Comments
 (0)