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