Files
micro-client-http/builder/query.go
pugnack 24801750a7
Some checks failed
coverage / build (push) Successful in 2m19s
test / test (push) Failing after 17m15s
integrate request builder into HTTP client for googleapis support (#157)
2025-09-23 13:30:15 +03:00

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
}