blob: b3804aa42b26cec136c9f50fa56fca007074a649 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
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}