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
 | 
						|
}
 |