integrate request builder into HTTP client for googleapis support (#157)
This commit is contained in:
194
builder/query.go
Normal file
194
builder/query.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
func buildQuery(msg proto.Message, usedFieldsPath *usedFields, usedFieldBody string) (url.Values, error) {
|
||||
var (
|
||||
query = url.Values{}
|
||||
msgReflect = msg.ProtoReflect()
|
||||
)
|
||||
|
||||
fields := msgReflect.Descriptor().Fields()
|
||||
for i := 0; i < fields.Len(); i++ {
|
||||
var (
|
||||
fd = fields.Get(i)
|
||||
fieldName = fd.JSONName()
|
||||
)
|
||||
|
||||
if usedFieldsPath.hasFullKey(fieldName) {
|
||||
continue
|
||||
}
|
||||
if fieldName == usedFieldBody {
|
||||
continue
|
||||
}
|
||||
|
||||
val := msgReflect.Get(fd)
|
||||
if isZeroValue(val, fd) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Note: order of the cases is important!
|
||||
switch {
|
||||
case fd.IsList():
|
||||
if fd.Kind() == protoreflect.MessageKind {
|
||||
return nil, fmt.Errorf("repeated message field %s cannot be mapped to URL query parameters", fieldName)
|
||||
}
|
||||
list := val.List()
|
||||
for j := 0; j < list.Len(); j++ {
|
||||
strVal, err := stringifyValue(list.Get(j), fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stringify value for query %s: %w", fieldName, err)
|
||||
}
|
||||
query.Add(fieldName, strVal)
|
||||
}
|
||||
|
||||
case fd.IsMap():
|
||||
var (
|
||||
m = val.Map()
|
||||
rangeErr error
|
||||
)
|
||||
m.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
|
||||
key := fmt.Sprintf("%s.%s", fieldName, k.String())
|
||||
|
||||
if fd.MapValue().Kind() == protoreflect.MessageKind {
|
||||
flattened, err := flattenMsgForQuery(key, v.Message())
|
||||
if err != nil {
|
||||
rangeErr = fmt.Errorf("flatten msg for query %s: %w", fieldName, err)
|
||||
return false
|
||||
}
|
||||
for _, item := range flattened {
|
||||
if item.val == "" {
|
||||
continue
|
||||
}
|
||||
if usedFieldsPath.hasFullKey(item.key) {
|
||||
continue
|
||||
}
|
||||
query.Add(item.key, item.val)
|
||||
}
|
||||
} else {
|
||||
strVal, err := stringifyValue(v, fd.MapValue())
|
||||
if err != nil {
|
||||
rangeErr = fmt.Errorf("stringify value for map %s: %w", fieldName, err)
|
||||
return false
|
||||
}
|
||||
query.Add(key, strVal)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if rangeErr != nil {
|
||||
return nil, fmt.Errorf("map range error: %w", rangeErr)
|
||||
}
|
||||
|
||||
case fd.Kind() == protoreflect.MessageKind:
|
||||
flattened, err := flattenMsgForQuery(fieldName, val.Message())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("flatten msg for query %s: %w", fieldName, err)
|
||||
}
|
||||
for _, item := range flattened {
|
||||
if item.val == "" {
|
||||
continue
|
||||
}
|
||||
if usedFieldsPath.hasFullKey(item.key) {
|
||||
continue
|
||||
}
|
||||
query.Add(item.key, item.val)
|
||||
}
|
||||
|
||||
default:
|
||||
strVal, err := stringifyValue(val, fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stringify value for primitive %s: %w", fieldName, err)
|
||||
}
|
||||
query.Add(fieldName, strVal)
|
||||
}
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
type flattenItem struct {
|
||||
key string
|
||||
val string
|
||||
}
|
||||
|
||||
// flattenMsgForQuery flattens a non-repeated message value under a given prefix.
|
||||
func flattenMsgForQuery(prefix string, msg protoreflect.Message) ([]flattenItem, error) {
|
||||
var out []flattenItem
|
||||
|
||||
fields := msg.Descriptor().Fields()
|
||||
for i := 0; i < fields.Len(); i++ {
|
||||
var (
|
||||
fd = fields.Get(i)
|
||||
val = msg.Get(fd)
|
||||
)
|
||||
|
||||
if isZeroValue(val, fd) {
|
||||
continue
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s.%s", prefix, fd.JSONName())
|
||||
|
||||
switch {
|
||||
case fd.IsList():
|
||||
if fd.Kind() == protoreflect.MessageKind {
|
||||
return nil, fmt.Errorf("repeated message field %s cannot be flattened for query", key)
|
||||
}
|
||||
list := val.List()
|
||||
for j := 0; j < list.Len(); j++ {
|
||||
strVal, err := stringifyValue(list.Get(j), fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stringify query %s: %w", key, err)
|
||||
}
|
||||
out = append(out, flattenItem{key: key, val: strVal})
|
||||
}
|
||||
|
||||
case fd.Kind() == protoreflect.MessageKind:
|
||||
nested, err := flattenMsgForQuery(key, val.Message())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("flatten msg for query %s: %w", key, err)
|
||||
}
|
||||
out = append(out, nested...)
|
||||
|
||||
case fd.IsMap():
|
||||
var mapErr error
|
||||
val.Map().Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
|
||||
keyStr := k.String()
|
||||
|
||||
if fd.MapValue().Kind() == protoreflect.MessageKind {
|
||||
child, err := flattenMsgForQuery(keyStr, v.Message())
|
||||
if err != nil {
|
||||
mapErr = fmt.Errorf("flatten map value %s: %w", key, err)
|
||||
return false
|
||||
}
|
||||
out = append(out, child...)
|
||||
} else {
|
||||
strVal, err := stringifyValue(v, fd.MapValue())
|
||||
if err != nil {
|
||||
mapErr = fmt.Errorf("stringify query %s: %w", keyStr, err)
|
||||
return false
|
||||
}
|
||||
out = append(out, flattenItem{key: keyStr, val: strVal})
|
||||
}
|
||||
return true
|
||||
})
|
||||
if mapErr != nil {
|
||||
return nil, mapErr
|
||||
}
|
||||
|
||||
default:
|
||||
strVal, err := stringifyValue(val, fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stringify query %s: %w", key, err)
|
||||
}
|
||||
out = append(out, flattenItem{key: key, val: strVal})
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
Reference in New Issue
Block a user