Skip to content

Commit

Permalink
fix(regapic): Allow well-known-types as unquoted query string values
Browse files Browse the repository at this point in the history
  • Loading branch information
vchudnov-g committed Mar 24, 2023
1 parent 50ac75b commit 53daf62
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 18 deletions.
60 changes: 44 additions & 16 deletions util/genrest/resttools/populatefield.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,43 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
)

var wellKnownTypes = map[string]bool{
type valueTransform func(string) (string, error)

func ensureQuotedValue(pre string) (string, error) {
hasStartQuote := strings.HasPrefix(pre, `"`)
hasEndQuote := strings.HasSuffix(pre, `"`)
if hasStartQuote != hasEndQuote {
return "", fmt.Errorf("unbalanced quotes in value %q", pre)
}
if hasStartQuote {
return pre, nil
}
return fmt.Sprintf(`"%s"`, pre), nil
}

var wellKnownTypes = map[string]valueTransform{
// testing various types in https://go.dev/play/p/rNhsXY364qQ

// == The following are the only three common types used in this API ==
"google.protobuf.FieldMask": true,
"google.protobuf.Timestamp": true,
"google.protobuf.Duration": true,
"google.protobuf.FieldMask": ensureQuotedValue,
"google.protobuf.Timestamp": ensureQuotedValue,
"google.protobuf.Duration": ensureQuotedValue,
// == End utilized types ==
"google.protobuf.DoubleValue": true,
"google.protobuf.FloatValue": true,
"google.protobuf.Int64Value": true,
"google.protobuf.UInt64Value": true,
"google.protobuf.Int32Value": true,
"google.protobuf.UInt32Value": true,
"google.protobuf.BoolValue": true,
"google.protobuf.StringValue": true,
"google.protobuf.BytesValue": true,
// TODO: When the following start being used int he Showcase API, add tests for their use as
// (unquoted) query params. These types are defined in
// https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/wrappers.proto
"google.protobuf.DoubleValue": nil,
"google.protobuf.FloatValue": nil,
"google.protobuf.Int64Value": ensureQuotedValue,
"google.protobuf.UInt64Value": ensureQuotedValue,
"google.protobuf.Int32Value": nil,
"google.protobuf.UInt32Value": nil,
"google.protobuf.BoolValue": nil,
"google.protobuf.StringValue": ensureQuotedValue,
"google.protobuf.BytesValue": ensureQuotedValue,
// TODO: Determine if the following are even viable as query params.
"google.protobuf.Value": true,
"google.protobuf.ListValue": true,
"google.protobuf.Value": nil,
"google.protobuf.ListValue": nil,
}

// PopulateSingularFields sets the fields within protoMessage to the values provided in
Expand Down Expand Up @@ -249,10 +268,19 @@ func parseWellKnownType(message protoreflect.Message, fieldDescriptor protorefle
}
fieldMsg := messageFieldTypes.Message(fieldDescriptor.Index())
fullName := string(fieldMsg.Descriptor().FullName())
if !wellKnownTypes[fullName] {
transform, isWellKnown := wellKnownTypes[fullName]
if !isWellKnown {
return nil, nil
}

if transform != nil {
var err error
if value, err = transform(value); err != nil {
return nil, err
}

}

msgValue := fieldMsg.New()
err := protojson.Unmarshal([]byte(value), msgValue.Interface())
if err != nil {
Expand Down
59 changes: 57 additions & 2 deletions util/genrest/resttools/populatefield_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
)

func TestParseWellKnownType(t *testing.T) {
// TODO: Test the other well-known types as the Showcase API
// starts incorporating them.
for _, tst := range []struct {
name string
msg protoreflect.Message
Expand Down Expand Up @@ -62,10 +64,10 @@ func TestParseWellKnownType(t *testing.T) {

gotp, err := parseWellKnownType(tst.msg, fd, value)
if err != nil {
t.Fatal(err)
t.Fatalf("parsing %q led to error %s", value, err)
}
if gotp == nil {
t.Fatal("expected non-nil value from parsing")
t.Fatalf("expected non-nil value from parsing: %s", value)
}
got := gotp.Message().Interface()
if diff := cmp.Diff(got, tst.want, cmp.Comparer(proto.Equal)); diff != "" {
Expand All @@ -74,6 +76,59 @@ func TestParseWellKnownType(t *testing.T) {
}
}

func TestParseWellKnownTypeUnquoted(t *testing.T) {
// TODO: Test the other well-known types as the Showcase API
// starts incorporating them.
for _, tst := range []struct {
name string
msg protoreflect.Message
field protoreflect.Name
value string
want proto.Message
}{
{
"google.protobuf.FieldMask",
(&genprotopb.UpdateUserRequest{}).ProtoReflect(),
"update_mask",
`foo,bar,baz`,
&fieldmaskpb.FieldMask{Paths: []string{"foo", "bar", "baz"}},
},

{
"google.protobuf.Timestamp",
(&genprotopb.User{}).ProtoReflect(),
"create_time",
"2023-03-21T12:01:02.000000003Z",
timestamppb.New(time.Date(2023, 03, 21, 12, 1, 2, 3, time.UTC)),
},

{
"google.protobuf.Duration",
(&genprotopb.Sequence_Response{}).ProtoReflect(),
"delay",
"5s",
durationpb.New(5 * time.Second),
},
} {
fd := tst.msg.Descriptor().Fields().ByName(tst.field)

gotp, err := parseWellKnownType(tst.msg, fd, tst.value)
if err != nil {
t.Errorf("parsing %q led to error %s", tst.value, err)
continue
}
if gotp == nil {
t.Errorf("expected non-nil value from parsing: %s", tst.value)
continue
}
got := gotp.Message().Interface()
if diff := cmp.Diff(got, tst.want, cmp.Comparer(proto.Equal)); diff != "" {
t.Errorf("%s: got(-),want(+):\n%s", "FieldMask", diff)
continue
}
}
}

func TestPopulateOneFieldError(t *testing.T) {
for idx, testCase := range []struct {
field string
Expand Down

0 comments on commit 53daf62

Please sign in to comment.