pax_global_header00006660000000000000000000000064133474536650014532gustar00rootroot0000000000000052 comment=44c6ddd0a2342c386950e880b658017258da92fc go-querystring-1.0.0/000077500000000000000000000000001334745366500145275ustar00rootroot00000000000000go-querystring-1.0.0/.gitignore000066400000000000000000000000071334745366500165140ustar00rootroot00000000000000*.test go-querystring-1.0.0/CONTRIBUTING.md000066400000000000000000000061521334745366500167640ustar00rootroot00000000000000# 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 go-querystring-1.0.0/LICENSE000066400000000000000000000026771334745366500155500ustar00rootroot00000000000000Copyright (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. go-querystring-1.0.0/README.md000066400000000000000000000022571334745366500160140ustar00rootroot00000000000000# go-querystring # go-querystring is Go library for encoding structs into URL query parameters. **Documentation:** **Build Status:** [![Build Status](https://drone.io/github.com/google/go-querystring/status.png)](https://drone.io/github.com/google/go-querystring/latest) ## 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. go-querystring-1.0.0/go.mod000066400000000000000000000000501334745366500156300ustar00rootroot00000000000000module github.com/google/go-querystring go-querystring-1.0.0/query/000077500000000000000000000000001334745366500156745ustar00rootroot00000000000000go-querystring-1.0.0/query/encode.go000066400000000000000000000210061334745366500174570ustar00rootroot00000000000000// 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 } go-querystring-1.0.0/query/encode_test.go000066400000000000000000000135711334745366500205260ustar00rootroot00000000000000// 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(%q) 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(%q) 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(%q) returned error: %v", s, err) } want := url.Values{} if !reflect.DeepEqual(want, v) { t.Errorf("Values(%q) 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) } } }