195 lines
4.7 KiB
Go
195 lines
4.7 KiB
Go
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
|
|
}
|