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