...

Source file src/github.com/google/go-querystring/query/encode.go

Documentation: github.com/google/go-querystring/query

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package query implements encoding of structs into URL query parameters.
     6  //
     7  // As a simple example:
     8  //
     9  // 	type Options struct {
    10  // 		Query   string `url:"q"`
    11  // 		ShowAll bool   `url:"all"`
    12  // 		Page    int    `url:"page"`
    13  // 	}
    14  //
    15  // 	opt := Options{ "foo", true, 2 }
    16  // 	v, _ := query.Values(opt)
    17  // 	fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
    18  //
    19  // The exact mapping between Go values and url.Values is described in the
    20  // documentation for the Values() function.
    21  package query
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"net/url"
    27  	"reflect"
    28  	"strconv"
    29  	"strings"
    30  	"time"
    31  )
    32  
    33  var timeType = reflect.TypeOf(time.Time{})
    34  
    35  var encoderType = reflect.TypeOf(new(Encoder)).Elem()
    36  
    37  // Encoder is an interface implemented by any type that wishes to encode
    38  // itself into URL values in a non-standard way.
    39  type Encoder interface {
    40  	EncodeValues(key string, v *url.Values) error
    41  }
    42  
    43  // Values returns the url.Values encoding of v.
    44  //
    45  // Values expects to be passed a struct, and traverses it recursively using the
    46  // following encoding rules.
    47  //
    48  // Each exported struct field is encoded as a URL parameter unless
    49  //
    50  //	- the field's tag is "-", or
    51  //	- the field is empty and its tag specifies the "omitempty" option
    52  //
    53  // The empty values are false, 0, any nil pointer or interface value, any array
    54  // slice, map, or string of length zero, and any type (such as time.Time) that
    55  // returns true for IsZero().
    56  //
    57  // The URL parameter name defaults to the struct field name but can be
    58  // specified in the struct field's tag value.  The "url" key in the struct
    59  // field's tag value is the key name, followed by an optional comma and
    60  // options.  For example:
    61  //
    62  // 	// Field is ignored by this package.
    63  // 	Field int `url:"-"`
    64  //
    65  // 	// Field appears as URL parameter "myName".
    66  // 	Field int `url:"myName"`
    67  //
    68  // 	// Field appears as URL parameter "myName" and the field is omitted if
    69  // 	// its value is empty
    70  // 	Field int `url:"myName,omitempty"`
    71  //
    72  // 	// Field appears as URL parameter "Field" (the default), but the field
    73  // 	// is skipped if empty.  Note the leading comma.
    74  // 	Field int `url:",omitempty"`
    75  //
    76  // For encoding individual field values, the following type-dependent rules
    77  // apply:
    78  //
    79  // Boolean values default to encoding as the strings "true" or "false".
    80  // Including the "int" option signals that the field should be encoded as the
    81  // strings "1" or "0".
    82  //
    83  // time.Time values default to encoding as RFC3339 timestamps.  Including the
    84  // "unix" option signals that the field should be encoded as a Unix time (see
    85  // time.Unix()).  The "unixmilli" and "unixnano" options will encode the number
    86  // of milliseconds and nanoseconds, respectively, since January 1, 1970 (see
    87  // time.UnixNano()).  Including the "layout" struct tag (separate from the
    88  // "url" tag) will use the value of the "layout" tag as a layout passed to
    89  // time.Format.  For example:
    90  //
    91  // 	// Encode a time.Time as YYYY-MM-DD
    92  // 	Field time.Time `layout:"2006-01-02"`
    93  //
    94  // Slice and Array values default to encoding as multiple URL values of the
    95  // same name.  Including the "comma" option signals that the field should be
    96  // encoded as a single comma-delimited value.  Including the "space" option
    97  // similarly encodes the value as a single space-delimited string. Including
    98  // the "semicolon" option will encode the value as a semicolon-delimited string.
    99  // Including the "brackets" option signals that the multiple URL values should
   100  // have "[]" appended to the value name. "numbered" will append a number to
   101  // the end of each incidence of the value name, example:
   102  // name0=value0&name1=value1, etc.  Including the "del" struct tag (separate
   103  // from the "url" tag) will use the value of the "del" tag as the delimiter.
   104  // For example:
   105  //
   106  // 	// Encode a slice of bools as ints ("1" for true, "0" for false),
   107  // 	// separated by exclamation points "!".
   108  // 	Field []bool `url:",int" del:"!"`
   109  //
   110  // Anonymous struct fields are usually encoded as if their inner exported
   111  // fields were fields in the outer struct, subject to the standard Go
   112  // visibility rules.  An anonymous struct field with a name given in its URL
   113  // tag is treated as having that name, rather than being anonymous.
   114  //
   115  // Non-nil pointer values are encoded as the value pointed to.
   116  //
   117  // Nested structs are encoded including parent fields in value names for
   118  // scoping. e.g:
   119  //
   120  // 	"user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"
   121  //
   122  // All other values are encoded using their default string representation.
   123  //
   124  // Multiple fields that encode to the same URL parameter name will be included
   125  // as multiple URL values of the same name.
   126  func Values(v interface{}) (url.Values, error) {
   127  	values := make(url.Values)
   128  	val := reflect.ValueOf(v)
   129  	for val.Kind() == reflect.Ptr {
   130  		if val.IsNil() {
   131  			return values, nil
   132  		}
   133  		val = val.Elem()
   134  	}
   135  
   136  	if v == nil {
   137  		return values, nil
   138  	}
   139  
   140  	if val.Kind() != reflect.Struct {
   141  		return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
   142  	}
   143  
   144  	err := reflectValue(values, val, "")
   145  	return values, err
   146  }
   147  
   148  // reflectValue populates the values parameter from the struct fields in val.
   149  // Embedded structs are followed recursively (using the rules defined in the
   150  // Values function documentation) breadth-first.
   151  func reflectValue(values url.Values, val reflect.Value, scope string) error {
   152  	var embedded []reflect.Value
   153  
   154  	typ := val.Type()
   155  	for i := 0; i < typ.NumField(); i++ {
   156  		sf := typ.Field(i)
   157  		if sf.PkgPath != "" && !sf.Anonymous { // unexported
   158  			continue
   159  		}
   160  
   161  		sv := val.Field(i)
   162  		tag := sf.Tag.Get("url")
   163  		if tag == "-" {
   164  			continue
   165  		}
   166  		name, opts := parseTag(tag)
   167  
   168  		if name == "" {
   169  			if sf.Anonymous {
   170  				v := reflect.Indirect(sv)
   171  				if v.IsValid() && v.Kind() == reflect.Struct {
   172  					// save embedded struct for later processing
   173  					embedded = append(embedded, v)
   174  					continue
   175  				}
   176  			}
   177  
   178  			name = sf.Name
   179  		}
   180  
   181  		if scope != "" {
   182  			name = scope + "[" + name + "]"
   183  		}
   184  
   185  		if opts.Contains("omitempty") && isEmptyValue(sv) {
   186  			continue
   187  		}
   188  
   189  		if sv.Type().Implements(encoderType) {
   190  			// if sv is a nil pointer and the custom encoder is defined on a non-pointer
   191  			// method receiver, set sv to the zero value of the underlying type
   192  			if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(encoderType) {
   193  				sv = reflect.New(sv.Type().Elem())
   194  			}
   195  
   196  			m := sv.Interface().(Encoder)
   197  			if err := m.EncodeValues(name, &values); err != nil {
   198  				return err
   199  			}
   200  			continue
   201  		}
   202  
   203  		// recursively dereference pointers. break on nil pointers
   204  		for sv.Kind() == reflect.Ptr {
   205  			if sv.IsNil() {
   206  				break
   207  			}
   208  			sv = sv.Elem()
   209  		}
   210  
   211  		if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
   212  			var del string
   213  			if opts.Contains("comma") {
   214  				del = ","
   215  			} else if opts.Contains("space") {
   216  				del = " "
   217  			} else if opts.Contains("semicolon") {
   218  				del = ";"
   219  			} else if opts.Contains("brackets") {
   220  				name = name + "[]"
   221  			} else {
   222  				del = sf.Tag.Get("del")
   223  			}
   224  
   225  			if del != "" {
   226  				s := new(bytes.Buffer)
   227  				first := true
   228  				for i := 0; i < sv.Len(); i++ {
   229  					if first {
   230  						first = false
   231  					} else {
   232  						s.WriteString(del)
   233  					}
   234  					s.WriteString(valueString(sv.Index(i), opts, sf))
   235  				}
   236  				values.Add(name, s.String())
   237  			} else {
   238  				for i := 0; i < sv.Len(); i++ {
   239  					k := name
   240  					if opts.Contains("numbered") {
   241  						k = fmt.Sprintf("%s%d", name, i)
   242  					}
   243  					values.Add(k, valueString(sv.Index(i), opts, sf))
   244  				}
   245  			}
   246  			continue
   247  		}
   248  
   249  		if sv.Type() == timeType {
   250  			values.Add(name, valueString(sv, opts, sf))
   251  			continue
   252  		}
   253  
   254  		if sv.Kind() == reflect.Struct {
   255  			if err := reflectValue(values, sv, name); err != nil {
   256  				return err
   257  			}
   258  			continue
   259  		}
   260  
   261  		values.Add(name, valueString(sv, opts, sf))
   262  	}
   263  
   264  	for _, f := range embedded {
   265  		if err := reflectValue(values, f, scope); err != nil {
   266  			return err
   267  		}
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  // valueString returns the string representation of a value.
   274  func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string {
   275  	for v.Kind() == reflect.Ptr {
   276  		if v.IsNil() {
   277  			return ""
   278  		}
   279  		v = v.Elem()
   280  	}
   281  
   282  	if v.Kind() == reflect.Bool && opts.Contains("int") {
   283  		if v.Bool() {
   284  			return "1"
   285  		}
   286  		return "0"
   287  	}
   288  
   289  	if v.Type() == timeType {
   290  		t := v.Interface().(time.Time)
   291  		if opts.Contains("unix") {
   292  			return strconv.FormatInt(t.Unix(), 10)
   293  		}
   294  		if opts.Contains("unixmilli") {
   295  			return strconv.FormatInt((t.UnixNano() / 1e6), 10)
   296  		}
   297  		if opts.Contains("unixnano") {
   298  			return strconv.FormatInt(t.UnixNano(), 10)
   299  		}
   300  		if layout := sf.Tag.Get("layout"); layout != "" {
   301  			return t.Format(layout)
   302  		}
   303  		return t.Format(time.RFC3339)
   304  	}
   305  
   306  	return fmt.Sprint(v.Interface())
   307  }
   308  
   309  // isEmptyValue checks if a value should be considered empty for the purposes
   310  // of omitting fields with the "omitempty" option.
   311  func isEmptyValue(v reflect.Value) bool {
   312  	switch v.Kind() {
   313  	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
   314  		return v.Len() == 0
   315  	case reflect.Bool:
   316  		return !v.Bool()
   317  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   318  		return v.Int() == 0
   319  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   320  		return v.Uint() == 0
   321  	case reflect.Float32, reflect.Float64:
   322  		return v.Float() == 0
   323  	case reflect.Interface, reflect.Ptr:
   324  		return v.IsNil()
   325  	}
   326  
   327  	type zeroable interface {
   328  		IsZero() bool
   329  	}
   330  
   331  	if z, ok := v.Interface().(zeroable); ok {
   332  		return z.IsZero()
   333  	}
   334  
   335  	return false
   336  }
   337  
   338  // tagOptions is the string following a comma in a struct field's "url" tag, or
   339  // the empty string. It does not include the leading comma.
   340  type tagOptions []string
   341  
   342  // parseTag splits a struct field's url tag into its name and comma-separated
   343  // options.
   344  func parseTag(tag string) (string, tagOptions) {
   345  	s := strings.Split(tag, ",")
   346  	return s[0], s[1:]
   347  }
   348  
   349  // Contains checks whether the tagOptions contains the specified option.
   350  func (o tagOptions) Contains(option string) bool {
   351  	for _, s := range o {
   352  		if s == option {
   353  			return true
   354  		}
   355  	}
   356  	return false
   357  }
   358  

View as plain text