blob: 2f0dd0074af92603279a1344d03863902e81a723 [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
Zack Williamse940c7a2019-08-21 14:25:39 -070057func 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}