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