github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/conversion/queryparams/convert.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     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 isPointerKind(kind reflect.Kind) bool {
    58  	return kind == reflect.Pointer
    59  }
    60  
    61  func isStructKind(kind reflect.Kind) bool {
    62  	return kind == reflect.Struct
    63  }
    64  
    65  func 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  
    77  func zeroValue(value reflect.Value) bool {
    78  	return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface())
    79  }
    80  
    81  func 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  
   113  func 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  
   126  func 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.
   135  func 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.Pointer, 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  
   158  func 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  }