fix FieldMask are converted to/from lower-camel naming conventions. (#1724)

pull/1726/head
Tony Chen 3 years ago committed by GitHub
parent b6b95089c4
commit 11a6120a2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      encoding/form/form.go
  2. 28
      encoding/form/proto_decode.go
  3. 30
      encoding/form/proto_encode.go
  4. 2
      transport/http/binding/encode.go

@ -34,7 +34,7 @@ func (c codec) Marshal(v interface{}) ([]byte, error) {
var vs url.Values var vs url.Values
var err error var err error
if m, ok := v.(proto.Message); ok { if m, ok := v.(proto.Message); ok {
vs, err = EncodeMap(m) vs, err = EncodeValues(m)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -66,9 +66,9 @@ func (c codec) Unmarshal(data []byte, v interface{}) error {
rv = rv.Elem() rv = rv.Elem()
} }
if m, ok := v.(proto.Message); ok { if m, ok := v.(proto.Message); ok {
return MapProto(m, vs) return DecodeValues(m, vs)
} else if m, ok := reflect.Indirect(reflect.ValueOf(v)).Interface().(proto.Message); ok { } else if m, ok := reflect.Indirect(reflect.ValueOf(v)).Interface().(proto.Message); ok {
return MapProto(m, vs) return DecodeValues(m, vs)
} }
return c.decoder.Decode(v, vs) return c.decoder.Decode(v, vs)

@ -4,6 +4,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net/url"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -19,7 +20,8 @@ import (
"google.golang.org/protobuf/types/known/wrapperspb" "google.golang.org/protobuf/types/known/wrapperspb"
) )
func MapProto(msg proto.Message, values map[string][]string) error { // DecodeValues decode url value into proto message.
func DecodeValues(msg proto.Message, values url.Values) error {
for key, values := range values { for key, values := range values {
if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), values); err != nil { if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), values); err != nil {
return err return err
@ -285,7 +287,9 @@ func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect
msg = wrapperspb.Bytes(v) msg = wrapperspb.Bytes(v)
case "google.protobuf.FieldMask": case "google.protobuf.FieldMask":
fm := &field_mask.FieldMask{} fm := &field_mask.FieldMask{}
fm.Paths = append(fm.Paths, strings.Split(value, ",")...) for _, fv := range strings.Split(value, ",") {
fm.Paths = append(fm.Paths, jsonSnakeCase(fv))
}
msg = fm msg = fm
case "google.protobuf.Value": case "google.protobuf.Value":
fm, err := structpb.NewValue(value) fm, err := structpb.NewValue(value)
@ -298,3 +302,23 @@ func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect
} }
return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil
} }
// jsonSnakeCase converts a camelCase identifier to a snake_case identifier,
// according to the protobuf JSON specification.
// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L864
func jsonSnakeCase(s string) string {
var b []byte
for i := 0; i < len(s); i++ { // proto identifiers are always ASCII
c := s[i]
if isASCIIUpper(c) {
b = append(b, '_')
c += 'a' - 'A' // convert to lowercase
}
b = append(b, c)
}
return string(b)
}
func isASCIIUpper(c byte) bool {
return 'A' <= c && c <= 'Z'
}

@ -13,8 +13,8 @@ import (
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
) )
// EncodeMap encode proto message to url query. // EncodeValues encode a message into url values.
func EncodeMap(msg proto.Message) (url.Values, error) { func EncodeValues(msg proto.Message) (url.Values, error) {
if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) { if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) {
return url.Values{}, nil return url.Values{}, nil
} }
@ -165,8 +165,34 @@ func encodeMessage(msgDescriptor protoreflect.MessageDescriptor, value protorefl
if !ok { if !ok {
return "", nil return "", nil
} }
for i, v := range m.Paths {
m.Paths[i] = jsonCamelCase(v)
}
return strings.Join(m.Paths, ","), nil return strings.Join(m.Paths, ","), nil
default: default:
return "", fmt.Errorf("unsupported message type: %q", string(msgDescriptor.FullName())) return "", fmt.Errorf("unsupported message type: %q", string(msgDescriptor.FullName()))
} }
} }
// JSONCamelCase converts a snake_case identifier to a camelCase identifier,
// according to the protobuf JSON specification.
// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L842
func jsonCamelCase(s string) string {
var b []byte
var wasUnderscore bool
for i := 0; i < len(s); i++ { // proto identifiers are always ASCII
c := s[i]
if c != '_' {
if wasUnderscore && isASCIILower(c) {
c -= 'a' - 'A' // convert to uppercase
}
b = append(b, c)
}
wasUnderscore = c == '_'
}
return string(b)
}
func isASCIILower(c byte) bool {
return 'a' <= c && c <= 'z'
}

@ -35,7 +35,7 @@ func EncodeURL(pathTemplate string, msg proto.Message, needQuery bool) string {
return in return in
}) })
if needQuery { if needQuery {
u, err := form.EncodeMap(msg) u, err := form.EncodeValues(msg)
if err == nil && len(u) > 0 { if err == nil && len(u) > 0 {
for key := range pathParams { for key := range pathParams {
delete(u, key) delete(u, key)

Loading…
Cancel
Save