1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package queryparams
18
19import (
20	"fmt"
21	"net/url"
22	"reflect"
23	"strings"
24)
25
26// Marshaler converts an object to a query parameter string representation
27type Marshaler interface {
28	MarshalQueryParameter() (string, error)
29}
30
31// Unmarshaler converts a string representation to an object
32type Unmarshaler interface {
33	UnmarshalQueryParameter(string) error
34}
35
36func jsonTag(field reflect.StructField) (string, bool) {
37	structTag := field.Tag.Get("json")
38	if len(structTag) == 0 {
39		return "", false
40	}
41	parts := strings.Split(structTag, ",")
42	tag := parts[0]
43	if tag == "-" {
44		tag = ""
45	}
46	omitempty := false
47	parts = parts[1:]
48	for _, part := range parts {
49		if part == "omitempty" {
50			omitempty = true
51			break
52		}
53	}
54	return tag, omitempty
55}
56
57func isPointerKind(kind reflect.Kind) bool {
58	return kind == reflect.Ptr
59}
60
61func isStructKind(kind reflect.Kind) bool {
62	return kind == reflect.Struct
63}
64
65func isValueKind(kind reflect.Kind) bool {
66	switch kind {
67	case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,
68		reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8,
69		reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32,
70		reflect.Float64, reflect.Complex64, reflect.Complex128:
71		return true
72	default:
73		return false
74	}
75}
76
77func zeroValue(value reflect.Value) bool {
78	return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface())
79}
80
81func customMarshalValue(value reflect.Value) (reflect.Value, bool) {
82	// Return unless we implement a custom query marshaler
83	if !value.CanInterface() {
84		return reflect.Value{}, false
85	}
86
87	marshaler, ok := value.Interface().(Marshaler)
88	if !ok {
89		if !isPointerKind(value.Kind()) && value.CanAddr() {
90			marshaler, ok = value.Addr().Interface().(Marshaler)
91			if !ok {
92				return reflect.Value{}, false
93			}
94		} else {
95			return reflect.Value{}, false
96		}
97	}
98
99	// Don't invoke functions on nil pointers
100	// If the type implements MarshalQueryParameter, AND the tag is not omitempty, AND the value is a nil pointer, "" seems like a reasonable response
101	if isPointerKind(value.Kind()) && zeroValue(value) {
102		return reflect.ValueOf(""), true
103	}
104
105	// Get the custom marshalled value
106	v, err := marshaler.MarshalQueryParameter()
107	if err != nil {
108		return reflect.Value{}, false
109	}
110	return reflect.ValueOf(v), true
111}
112
113func addParam(values url.Values, tag string, omitempty bool, value reflect.Value) {
114	if omitempty && zeroValue(value) {
115		return
116	}
117	val := ""
118	iValue := fmt.Sprintf("%v", value.Interface())
119
120	if iValue != "<nil>" {
121		val = iValue
122	}
123	values.Add(tag, val)
124}
125
126func addListOfParams(values url.Values, tag string, omitempty bool, list reflect.Value) {
127	for i := 0; i < list.Len(); i++ {
128		addParam(values, tag, omitempty, list.Index(i))
129	}
130}
131
132// Convert takes an object and converts it to a url.Values object using JSON tags as
133// parameter names. Only top-level simple values, arrays, and slices are serialized.
134// Embedded structs, maps, etc. will not be serialized.
135func Convert(obj interface{}) (url.Values, error) {
136	result := url.Values{}
137	if obj == nil {
138		return result, nil
139	}
140	var sv reflect.Value
141	switch reflect.TypeOf(obj).Kind() {
142	case reflect.Ptr, reflect.Interface:
143		sv = reflect.ValueOf(obj).Elem()
144	default:
145		return nil, fmt.Errorf("expecting a pointer or interface")
146	}
147	st := sv.Type()
148	if !isStructKind(st.Kind()) {
149		return nil, fmt.Errorf("expecting a pointer to a struct")
150	}
151
152	// Check all object fields
153	convertStruct(result, st, sv)
154
155	return result, nil
156}
157
158func convertStruct(result url.Values, st reflect.Type, sv reflect.Value) {
159	for i := 0; i < st.NumField(); i++ {
160		field := sv.Field(i)
161		tag, omitempty := jsonTag(st.Field(i))
162		if len(tag) == 0 {
163			continue
164		}
165		ft := field.Type()
166
167		kind := ft.Kind()
168		if isPointerKind(kind) {
169			ft = ft.Elem()
170			kind = ft.Kind()
171			if !field.IsNil() {
172				field = reflect.Indirect(field)
173				// If the field is non-nil, it should be added to params
174				// and the omitempty should be overwite to false
175				omitempty = false
176			}
177		}
178
179		switch {
180		case isValueKind(kind):
181			addParam(result, tag, omitempty, field)
182		case kind == reflect.Array || kind == reflect.Slice:
183			if isValueKind(ft.Elem().Kind()) {
184				addListOfParams(result, tag, omitempty, field)
185			}
186		case isStructKind(kind) && !(zeroValue(field) && omitempty):
187			if marshalValue, ok := customMarshalValue(field); ok {
188				addParam(result, tag, omitempty, marshalValue)
189			} else {
190				convertStruct(result, ft, field)
191			}
192		}
193	}
194}
195