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 formatValue(value interface{}) string {
58	return fmt.Sprintf("%v", value)
59}
60
61func isPointerKind(kind reflect.Kind) bool {
62	return kind == reflect.Ptr
63}
64
65func isStructKind(kind reflect.Kind) bool {
66	return kind == reflect.Struct
67}
68
69func isValueKind(kind reflect.Kind) bool {
70	switch kind {
71	case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,
72		reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8,
73		reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32,
74		reflect.Float64, reflect.Complex64, reflect.Complex128:
75		return true
76	default:
77		return false
78	}
79}
80
81func zeroValue(value reflect.Value) bool {
82	return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface())
83}
84
85func customMarshalValue(value reflect.Value) (reflect.Value, bool) {
86	// Return unless we implement a custom query marshaler
87	if !value.CanInterface() {
88		return reflect.Value{}, false
89	}
90
91	marshaler, ok := value.Interface().(Marshaler)
92	if !ok {
93		if !isPointerKind(value.Kind()) && value.CanAddr() {
94			marshaler, ok = value.Addr().Interface().(Marshaler)
95			if !ok {
96				return reflect.Value{}, false
97			}
98		} else {
99			return reflect.Value{}, false
100		}
101	}
102
103	// Don't invoke functions on nil pointers
104	// If the type implements MarshalQueryParameter, AND the tag is not omitempty, AND the value is a nil pointer, "" seems like a reasonable response
105	if isPointerKind(value.Kind()) && zeroValue(value) {
106		return reflect.ValueOf(""), true
107	}
108
109	// Get the custom marshalled value
110	v, err := marshaler.MarshalQueryParameter()
111	if err != nil {
112		return reflect.Value{}, false
113	}
114	return reflect.ValueOf(v), true
115}
116
117func addParam(values url.Values, tag string, omitempty bool, value reflect.Value) {
118	if omitempty && zeroValue(value) {
119		return
120	}
121	val := ""
122	iValue := fmt.Sprintf("%v", value.Interface())
123
124	if iValue != "<nil>" {
125		val = iValue
126	}
127	values.Add(tag, val)
128}
129
130func addListOfParams(values url.Values, tag string, omitempty bool, list reflect.Value) {
131	for i := 0; i < list.Len(); i++ {
132		addParam(values, tag, omitempty, list.Index(i))
133	}
134}
135
136// Convert takes an object and converts it to a url.Values object using JSON tags as
137// parameter names. Only top-level simple values, arrays, and slices are serialized.
138// Embedded structs, maps, etc. will not be serialized.
139func Convert(obj interface{}) (url.Values, error) {
140	result := url.Values{}
141	if obj == nil {
142		return result, nil
143	}
144	var sv reflect.Value
145	switch reflect.TypeOf(obj).Kind() {
146	case reflect.Ptr, reflect.Interface:
147		sv = reflect.ValueOf(obj).Elem()
148	default:
149		return nil, fmt.Errorf("expecting a pointer or interface")
150	}
151	st := sv.Type()
152	if !isStructKind(st.Kind()) {
153		return nil, fmt.Errorf("expecting a pointer to a struct")
154	}
155
156	// Check all object fields
157	convertStruct(result, st, sv)
158
159	return result, nil
160}
161
162func convertStruct(result url.Values, st reflect.Type, sv reflect.Value) {
163	for i := 0; i < st.NumField(); i++ {
164		field := sv.Field(i)
165		tag, omitempty := jsonTag(st.Field(i))
166		if len(tag) == 0 {
167			continue
168		}
169		ft := field.Type()
170
171		kind := ft.Kind()
172		if isPointerKind(kind) {
173			ft = ft.Elem()
174			kind = ft.Kind()
175			if !field.IsNil() {
176				field = reflect.Indirect(field)
177				// If the field is non-nil, it should be added to params
178				// and the omitempty should be overwite to false
179				omitempty = false
180			}
181		}
182
183		switch {
184		case isValueKind(kind):
185			addParam(result, tag, omitempty, field)
186		case kind == reflect.Array || kind == reflect.Slice:
187			if isValueKind(ft.Elem().Kind()) {
188				addListOfParams(result, tag, omitempty, field)
189			}
190		case isStructKind(kind) && !(zeroValue(field) && omitempty):
191			if marshalValue, ok := customMarshalValue(field); ok {
192				addParam(result, tag, omitempty, marshalValue)
193			} else {
194				convertStruct(result, ft, field)
195			}
196		}
197	}
198}
199