--- /dev/null
+language: go
+
+go:
+ - 1.x
+ - 1.11.x
--- /dev/null
+# How to contribute #
+
+We'd love to accept your patches and contributions to this project. There are
+a just a few small guidelines you need to follow.
+
+
+## Contributor License Agreement ##
+
+Contributions to any Google project must be accompanied by a Contributor
+License Agreement. This is not a copyright **assignment**, it simply gives
+Google permission to use and redistribute your contributions as part of the
+project.
+
+ * If you are an individual writing original source code and you're sure you
+ own the intellectual property, then you'll need to sign an [individual
+ CLA][].
+
+ * If you work for a company that wants to allow you to contribute your work,
+ then you'll need to sign a [corporate CLA][].
+
+You generally only need to submit a CLA once, so if you've already submitted
+one (even if it was for a different project), you probably don't need to do it
+again.
+
+[individual CLA]: https://developers.google.com/open-source/cla/individual
+[corporate CLA]: https://developers.google.com/open-source/cla/corporate
+
+
+## Submitting a patch ##
+
+ 1. It's generally best to start by opening a new issue describing the bug or
+ feature you're intending to fix. Even if you think it's relatively minor,
+ it's helpful to know what people are working on. Mention in the initial
+ issue that you are planning to work on that bug or feature so that it can
+ be assigned to you.
+
+ 1. Follow the normal process of [forking][] the project, and setup a new
+ branch to work in. It's important that each group of changes be done in
+ separate branches in order to ensure that a pull request only includes the
+ commits related to that bug or feature.
+
+ 1. Go makes it very simple to ensure properly formatted code, so always run
+ `go fmt` on your code before committing it. You should also run
+ [golint][] over your code. As noted in the [golint readme][], it's not
+ strictly necessary that your code be completely "lint-free", but this will
+ help you find common style issues.
+
+ 1. Any significant changes should almost always be accompanied by tests. The
+ project already has good test coverage, so look at some of the existing
+ tests if you're unsure how to go about it. [gocov][] and [gocov-html][]
+ are invaluable tools for seeing which parts of your code aren't being
+ exercised by your tests.
+
+ 1. Do your best to have [well-formed commit messages][] for each change.
+ This provides consistency throughout the project, and ensures that commit
+ messages are able to be formatted properly by various git tools.
+
+ 1. Finally, push the commits to your fork and submit a [pull request][].
+
+[forking]: https://help.github.com/articles/fork-a-repo
+[golint]: https://github.com/golang/lint
+[golint readme]: https://github.com/golang/lint/blob/master/README
+[gocov]: https://github.com/axw/gocov
+[gocov-html]: https://github.com/matm/gocov-html
+[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
+[pull request]: https://help.github.com/articles/creating-a-pull-request
--- /dev/null
+Copyright (c) 2013 Google. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+# go-querystring #
+
+[![GoDoc](https://godoc.org/github.com/google/go-querystring/query?status.svg)](https://godoc.org/github.com/google/go-querystring/query) [![Build Status](https://travis-ci.org/google/go-querystring.svg?branch=master)](https://travis-ci.org/google/go-querystring)
+
+go-querystring is Go library for encoding structs into URL query parameters.
+
+## Usage ##
+
+```go
+import "github.com/google/go-querystring/query"
+```
+
+go-querystring is designed to assist in scenarios where you want to construct a
+URL using a struct that represents the URL query parameters. You might do this
+to enforce the type safety of your parameters, for example, as is done in the
+[go-github][] library.
+
+The query package exports a single `Values()` function. A simple example:
+
+```go
+type Options struct {
+ Query string `url:"q"`
+ ShowAll bool `url:"all"`
+ Page int `url:"page"`
+}
+
+opt := Options{ "foo", true, 2 }
+v, _ := query.Values(opt)
+fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
+```
+
+[go-github]: https://github.com/google/go-github/commit/994f6f8405f052a117d2d0b500054341048fbb08
+
+## License ##
+
+This library is distributed under the BSD-style license found in the [LICENSE](./LICENSE)
+file.
--- /dev/null
+module github.com/google/go-querystring
--- /dev/null
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package query implements encoding of structs into URL query parameters.
+//
+// As a simple example:
+//
+// type Options struct {
+// Query string `url:"q"`
+// ShowAll bool `url:"all"`
+// Page int `url:"page"`
+// }
+//
+// opt := Options{ "foo", true, 2 }
+// v, _ := query.Values(opt)
+// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
+//
+// The exact mapping between Go values and url.Values is described in the
+// documentation for the Values() function.
+package query
+
+import (
+ "bytes"
+ "fmt"
+ "net/url"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var timeType = reflect.TypeOf(time.Time{})
+
+var encoderType = reflect.TypeOf(new(Encoder)).Elem()
+
+// Encoder is an interface implemented by any type that wishes to encode
+// itself into URL values in a non-standard way.
+type Encoder interface {
+ EncodeValues(key string, v *url.Values) error
+}
+
+// Values returns the url.Values encoding of v.
+//
+// Values expects to be passed a struct, and traverses it recursively using the
+// following encoding rules.
+//
+// Each exported struct field is encoded as a URL parameter unless
+//
+// - the field's tag is "-", or
+// - the field is empty and its tag specifies the "omitempty" option
+//
+// The empty values are false, 0, any nil pointer or interface value, any array
+// slice, map, or string of length zero, and any time.Time that returns true
+// for IsZero().
+//
+// The URL parameter name defaults to the struct field name but can be
+// specified in the struct field's tag value. The "url" key in the struct
+// field's tag value is the key name, followed by an optional comma and
+// options. For example:
+//
+// // Field is ignored by this package.
+// Field int `url:"-"`
+//
+// // Field appears as URL parameter "myName".
+// Field int `url:"myName"`
+//
+// // Field appears as URL parameter "myName" and the field is omitted if
+// // its value is empty
+// Field int `url:"myName,omitempty"`
+//
+// // Field appears as URL parameter "Field" (the default), but the field
+// // is skipped if empty. Note the leading comma.
+// Field int `url:",omitempty"`
+//
+// For encoding individual field values, the following type-dependent rules
+// apply:
+//
+// Boolean values default to encoding as the strings "true" or "false".
+// Including the "int" option signals that the field should be encoded as the
+// strings "1" or "0".
+//
+// time.Time values default to encoding as RFC3339 timestamps. Including the
+// "unix" option signals that the field should be encoded as a Unix time (see
+// time.Unix())
+//
+// Slice and Array values default to encoding as multiple URL values of the
+// same name. Including the "comma" option signals that the field should be
+// encoded as a single comma-delimited value. Including the "space" option
+// similarly encodes the value as a single space-delimited string. Including
+// the "semicolon" option will encode the value as a semicolon-delimited string.
+// Including the "brackets" option signals that the multiple URL values should
+// have "[]" appended to the value name. "numbered" will append a number to
+// the end of each incidence of the value name, example:
+// name0=value0&name1=value1, etc.
+//
+// Anonymous struct fields are usually encoded as if their inner exported
+// fields were fields in the outer struct, subject to the standard Go
+// visibility rules. An anonymous struct field with a name given in its URL
+// tag is treated as having that name, rather than being anonymous.
+//
+// Non-nil pointer values are encoded as the value pointed to.
+//
+// Nested structs are encoded including parent fields in value names for
+// scoping. e.g:
+//
+// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"
+//
+// All other values are encoded using their default string representation.
+//
+// Multiple fields that encode to the same URL parameter name will be included
+// as multiple URL values of the same name.
+func Values(v interface{}) (url.Values, error) {
+ values := make(url.Values)
+ val := reflect.ValueOf(v)
+ for val.Kind() == reflect.Ptr {
+ if val.IsNil() {
+ return values, nil
+ }
+ val = val.Elem()
+ }
+
+ if v == nil {
+ return values, nil
+ }
+
+ if val.Kind() != reflect.Struct {
+ return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
+ }
+
+ err := reflectValue(values, val, "")
+ return values, err
+}
+
+// reflectValue populates the values parameter from the struct fields in val.
+// Embedded structs are followed recursively (using the rules defined in the
+// Values function documentation) breadth-first.
+func reflectValue(values url.Values, val reflect.Value, scope string) error {
+ var embedded []reflect.Value
+
+ typ := val.Type()
+ for i := 0; i < typ.NumField(); i++ {
+ sf := typ.Field(i)
+ if sf.PkgPath != "" && !sf.Anonymous { // unexported
+ continue
+ }
+
+ sv := val.Field(i)
+ tag := sf.Tag.Get("url")
+ if tag == "-" {
+ continue
+ }
+ name, opts := parseTag(tag)
+ if name == "" {
+ if sf.Anonymous && sv.Kind() == reflect.Struct {
+ // save embedded struct for later processing
+ embedded = append(embedded, sv)
+ continue
+ }
+
+ name = sf.Name
+ }
+
+ if scope != "" {
+ name = scope + "[" + name + "]"
+ }
+
+ if opts.Contains("omitempty") && isEmptyValue(sv) {
+ continue
+ }
+
+ if sv.Type().Implements(encoderType) {
+ if !reflect.Indirect(sv).IsValid() {
+ sv = reflect.New(sv.Type().Elem())
+ }
+
+ m := sv.Interface().(Encoder)
+ if err := m.EncodeValues(name, &values); err != nil {
+ return err
+ }
+ continue
+ }
+
+ if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
+ var del byte
+ if opts.Contains("comma") {
+ del = ','
+ } else if opts.Contains("space") {
+ del = ' '
+ } else if opts.Contains("semicolon") {
+ del = ';'
+ } else if opts.Contains("brackets") {
+ name = name + "[]"
+ }
+
+ if del != 0 {
+ s := new(bytes.Buffer)
+ first := true
+ for i := 0; i < sv.Len(); i++ {
+ if first {
+ first = false
+ } else {
+ s.WriteByte(del)
+ }
+ s.WriteString(valueString(sv.Index(i), opts))
+ }
+ values.Add(name, s.String())
+ } else {
+ for i := 0; i < sv.Len(); i++ {
+ k := name
+ if opts.Contains("numbered") {
+ k = fmt.Sprintf("%s%d", name, i)
+ }
+ values.Add(k, valueString(sv.Index(i), opts))
+ }
+ }
+ continue
+ }
+
+ for sv.Kind() == reflect.Ptr {
+ if sv.IsNil() {
+ break
+ }
+ sv = sv.Elem()
+ }
+
+ if sv.Type() == timeType {
+ values.Add(name, valueString(sv, opts))
+ continue
+ }
+
+ if sv.Kind() == reflect.Struct {
+ reflectValue(values, sv, name)
+ continue
+ }
+
+ values.Add(name, valueString(sv, opts))
+ }
+
+ for _, f := range embedded {
+ if err := reflectValue(values, f, scope); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// valueString returns the string representation of a value.
+func valueString(v reflect.Value, opts tagOptions) string {
+ for v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ return ""
+ }
+ v = v.Elem()
+ }
+
+ if v.Kind() == reflect.Bool && opts.Contains("int") {
+ if v.Bool() {
+ return "1"
+ }
+ return "0"
+ }
+
+ if v.Type() == timeType {
+ t := v.Interface().(time.Time)
+ if opts.Contains("unix") {
+ return strconv.FormatInt(t.Unix(), 10)
+ }
+ return t.Format(time.RFC3339)
+ }
+
+ return fmt.Sprint(v.Interface())
+}
+
+// isEmptyValue checks if a value should be considered empty for the purposes
+// of omitting fields with the "omitempty" option.
+func isEmptyValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+ }
+
+ if v.Type() == timeType {
+ return v.Interface().(time.Time).IsZero()
+ }
+
+ return false
+}
+
+// tagOptions is the string following a comma in a struct field's "url" tag, or
+// the empty string. It does not include the leading comma.
+type tagOptions []string
+
+// parseTag splits a struct field's url tag into its name and comma-separated
+// options.
+func parseTag(tag string) (string, tagOptions) {
+ s := strings.Split(tag, ",")
+ return s[0], s[1:]
+}
+
+// Contains checks whether the tagOptions contains the specified option.
+func (o tagOptions) Contains(option string) bool {
+ for _, s := range o {
+ if s == option {
+ return true
+ }
+ }
+ return false
+}
--- /dev/null
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package query
+
+import (
+ "fmt"
+ "net/url"
+ "reflect"
+ "testing"
+ "time"
+)
+
+type Nested struct {
+ A SubNested `url:"a"`
+ B *SubNested `url:"b"`
+ Ptr *SubNested `url:"ptr,omitempty"`
+}
+
+type SubNested struct {
+ Value string `url:"value"`
+}
+
+func TestValues_types(t *testing.T) {
+ str := "string"
+ strPtr := &str
+ timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)
+
+ tests := []struct {
+ in interface{}
+ want url.Values
+ }{
+ {
+ // basic primitives
+ struct {
+ A string
+ B int
+ C uint
+ D float32
+ E bool
+ }{},
+ url.Values{
+ "A": {""},
+ "B": {"0"},
+ "C": {"0"},
+ "D": {"0"},
+ "E": {"false"},
+ },
+ },
+ {
+ // pointers
+ struct {
+ A *string
+ B *int
+ C **string
+ D *time.Time
+ }{
+ A: strPtr,
+ C: &strPtr,
+ D: &timeVal,
+ },
+ url.Values{
+ "A": {str},
+ "B": {""},
+ "C": {str},
+ "D": {"2000-01-01T12:34:56Z"},
+ },
+ },
+ {
+ // slices and arrays
+ struct {
+ A []string
+ B []string `url:",comma"`
+ C []string `url:",space"`
+ D [2]string
+ E [2]string `url:",comma"`
+ F [2]string `url:",space"`
+ G []*string `url:",space"`
+ H []bool `url:",int,space"`
+ I []string `url:",brackets"`
+ J []string `url:",semicolon"`
+ K []string `url:",numbered"`
+ }{
+ A: []string{"a", "b"},
+ B: []string{"a", "b"},
+ C: []string{"a", "b"},
+ D: [2]string{"a", "b"},
+ E: [2]string{"a", "b"},
+ F: [2]string{"a", "b"},
+ G: []*string{&str, &str},
+ H: []bool{true, false},
+ I: []string{"a", "b"},
+ J: []string{"a", "b"},
+ K: []string{"a", "b"},
+ },
+ url.Values{
+ "A": {"a", "b"},
+ "B": {"a,b"},
+ "C": {"a b"},
+ "D": {"a", "b"},
+ "E": {"a,b"},
+ "F": {"a b"},
+ "G": {"string string"},
+ "H": {"1 0"},
+ "I[]": {"a", "b"},
+ "J": {"a;b"},
+ "K0": {"a"},
+ "K1": {"b"},
+ },
+ },
+ {
+ // other types
+ struct {
+ A time.Time
+ B time.Time `url:",unix"`
+ C bool `url:",int"`
+ D bool `url:",int"`
+ }{
+ A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
+ B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
+ C: true,
+ D: false,
+ },
+ url.Values{
+ "A": {"2000-01-01T12:34:56Z"},
+ "B": {"946730096"},
+ "C": {"1"},
+ "D": {"0"},
+ },
+ },
+ {
+ struct {
+ Nest Nested `url:"nest"`
+ }{
+ Nested{
+ A: SubNested{
+ Value: "that",
+ },
+ },
+ },
+ url.Values{
+ "nest[a][value]": {"that"},
+ "nest[b]": {""},
+ },
+ },
+ {
+ struct {
+ Nest Nested `url:"nest"`
+ }{
+ Nested{
+ Ptr: &SubNested{
+ Value: "that",
+ },
+ },
+ },
+ url.Values{
+ "nest[a][value]": {""},
+ "nest[b]": {""},
+ "nest[ptr][value]": {"that"},
+ },
+ },
+ {
+ nil,
+ url.Values{},
+ },
+ }
+
+ for i, tt := range tests {
+ v, err := Values(tt.in)
+ if err != nil {
+ t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
+ }
+
+ if !reflect.DeepEqual(tt.want, v) {
+ t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
+ }
+ }
+}
+
+func TestValues_omitEmpty(t *testing.T) {
+ str := ""
+ s := struct {
+ a string
+ A string
+ B string `url:",omitempty"`
+ C string `url:"-"`
+ D string `url:"omitempty"` // actually named omitempty, not an option
+ E *string `url:",omitempty"`
+ }{E: &str}
+
+ v, err := Values(s)
+ if err != nil {
+ t.Errorf("Values(%v) returned error: %v", s, err)
+ }
+
+ want := url.Values{
+ "A": {""},
+ "omitempty": {""},
+ "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is
+ }
+ if !reflect.DeepEqual(want, v) {
+ t.Errorf("Values(%v) returned %v, want %v", s, v, want)
+ }
+}
+
+type A struct {
+ B
+}
+
+type B struct {
+ C string
+}
+
+type D struct {
+ B
+ C string
+}
+
+type e struct {
+ B
+ C string
+}
+
+type F struct {
+ e
+}
+
+func TestValues_embeddedStructs(t *testing.T) {
+ tests := []struct {
+ in interface{}
+ want url.Values
+ }{
+ {
+ A{B{C: "foo"}},
+ url.Values{"C": {"foo"}},
+ },
+ {
+ D{B: B{C: "bar"}, C: "foo"},
+ url.Values{"C": {"foo", "bar"}},
+ },
+ {
+ F{e{B: B{C: "bar"}, C: "foo"}}, // With unexported embed
+ url.Values{"C": {"foo", "bar"}},
+ },
+ }
+
+ for i, tt := range tests {
+ v, err := Values(tt.in)
+ if err != nil {
+ t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
+ }
+
+ if !reflect.DeepEqual(tt.want, v) {
+ t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
+ }
+ }
+}
+
+func TestValues_invalidInput(t *testing.T) {
+ _, err := Values("")
+ if err == nil {
+ t.Errorf("expected Values() to return an error on invalid input")
+ }
+}
+
+type EncodedArgs []string
+
+func (m EncodedArgs) EncodeValues(key string, v *url.Values) error {
+ for i, arg := range m {
+ v.Set(fmt.Sprintf("%s.%d", key, i), arg)
+ }
+ return nil
+}
+
+func TestValues_Marshaler(t *testing.T) {
+ s := struct {
+ Args EncodedArgs `url:"arg"`
+ }{[]string{"a", "b", "c"}}
+ v, err := Values(s)
+ if err != nil {
+ t.Errorf("Values(%q) returned error: %v", s, err)
+ }
+
+ want := url.Values{
+ "arg.0": {"a"},
+ "arg.1": {"b"},
+ "arg.2": {"c"},
+ }
+ if !reflect.DeepEqual(want, v) {
+ t.Errorf("Values(%q) returned %v, want %v", s, v, want)
+ }
+}
+
+func TestValues_MarshalerWithNilPointer(t *testing.T) {
+ s := struct {
+ Args *EncodedArgs `url:"arg"`
+ }{}
+ v, err := Values(s)
+ if err != nil {
+ t.Errorf("Values(%v) returned error: %v", s, err)
+ }
+
+ want := url.Values{}
+ if !reflect.DeepEqual(want, v) {
+ t.Errorf("Values(%v) returned %v, want %v", s, v, want)
+ }
+}
+
+func TestTagParsing(t *testing.T) {
+ name, opts := parseTag("field,foobar,foo")
+ if name != "field" {
+ t.Fatalf("name = %q, want field", name)
+ }
+ for _, tt := range []struct {
+ opt string
+ want bool
+ }{
+ {"foobar", true},
+ {"foo", true},
+ {"bar", false},
+ {"field", false},
+ } {
+ if opts.Contains(tt.opt) != tt.want {
+ t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
+ }
+ }
+}