github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/Godeps/_workspace/src/k8s.io/kubernetes/pkg/conversion/queryparams/convert.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package queryparams
    18  
    19  import (
    20  	"fmt"
    21  	"net/url"
    22  	"reflect"
    23  	"strings"
    24  )
    25  
    26  // Marshaler converts an object to a query parameter string representation
    27  type Marshaler interface {
    28  	MarshalQueryParameter() (string, error)
    29  }
    30  
    31  // Unmarshaler converts a string representation to an object
    32  type Unmarshaler interface {
    33  	UnmarshalQueryParameter(string) error
    34  }
    35  
    36  func 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  
    57  func formatValue(value interface{}) string {
    58  	return fmt.Sprintf("%v", value)
    59  }
    60  
    61  func isPointerKind(kind reflect.Kind) bool {
    62  	return kind == reflect.Ptr
    63  }
    64  
    65  func isStructKind(kind reflect.Kind) bool {
    66  	return kind == reflect.Struct
    67  }
    68  
    69  func 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  
    81  func zeroValue(value reflect.Value) bool {
    82  	return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface())
    83  }
    84  
    85  func 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  		return reflect.Value{}, false
    94  	}
    95  
    96  	// Don't invoke functions on nil pointers
    97  	// If the type implements MarshalQueryParameter, AND the tag is not omitempty, AND the value is a nil pointer, "" seems like a reasonable response
    98  	if isPointerKind(value.Kind()) && zeroValue(value) {
    99  		return reflect.ValueOf(""), true
   100  	}
   101  
   102  	// Get the custom marshalled value
   103  	v, err := marshaler.MarshalQueryParameter()
   104  	if err != nil {
   105  		return reflect.Value{}, false
   106  	}
   107  	return reflect.ValueOf(v), true
   108  }
   109  
   110  func addParam(values url.Values, tag string, omitempty bool, value reflect.Value) {
   111  	if omitempty && zeroValue(value) {
   112  		return
   113  	}
   114  	val := ""
   115  	iValue := fmt.Sprintf("%v", value.Interface())
   116  
   117  	if iValue != "<nil>" {
   118  		val = iValue
   119  	}
   120  	values.Add(tag, val)
   121  }
   122  
   123  func addListOfParams(values url.Values, tag string, omitempty bool, list reflect.Value) {
   124  	for i := 0; i < list.Len(); i++ {
   125  		addParam(values, tag, omitempty, list.Index(i))
   126  	}
   127  }
   128  
   129  // Convert takes an object and converts it to a url.Values object using JSON tags as
   130  // parameter names. Only top-level simple values, arrays, and slices are serialized.
   131  // Embedded structs, maps, etc. will not be serialized.
   132  func Convert(obj interface{}) (url.Values, error) {
   133  	result := url.Values{}
   134  	if obj == nil {
   135  		return result, nil
   136  	}
   137  	var sv reflect.Value
   138  	switch reflect.TypeOf(obj).Kind() {
   139  	case reflect.Ptr, reflect.Interface:
   140  		sv = reflect.ValueOf(obj).Elem()
   141  	default:
   142  		return nil, fmt.Errorf("expecting a pointer or interface")
   143  	}
   144  	st := sv.Type()
   145  	if !isStructKind(st.Kind()) {
   146  		return nil, fmt.Errorf("expecting a pointer to a struct")
   147  	}
   148  
   149  	// Check all object fields
   150  	convertStruct(result, st, sv)
   151  
   152  	return result, nil
   153  }
   154  
   155  func convertStruct(result url.Values, st reflect.Type, sv reflect.Value) {
   156  	for i := 0; i < st.NumField(); i++ {
   157  		field := sv.Field(i)
   158  		tag, omitempty := jsonTag(st.Field(i))
   159  		if len(tag) == 0 {
   160  			continue
   161  		}
   162  		ft := field.Type()
   163  
   164  		kind := ft.Kind()
   165  		if isPointerKind(kind) {
   166  			ft = ft.Elem()
   167  			kind = ft.Kind()
   168  			if !field.IsNil() {
   169  				field = reflect.Indirect(field)
   170  			}
   171  		}
   172  
   173  		switch {
   174  		case isValueKind(kind):
   175  			addParam(result, tag, omitempty, field)
   176  		case kind == reflect.Array || kind == reflect.Slice:
   177  			if isValueKind(ft.Elem().Kind()) {
   178  				addListOfParams(result, tag, omitempty, field)
   179  			}
   180  		case isStructKind(kind) && !(zeroValue(field) && omitempty):
   181  			if marshalValue, ok := customMarshalValue(field); ok {
   182  				addParam(result, tag, omitempty, marshalValue)
   183  			} else {
   184  				convertStruct(result, ft, field)
   185  			}
   186  		}
   187  	}
   188  }