2018-01-22 11:19:33 +02:00

343 lines
9.2 KiB
Go

// Copyright 2014 Alvaro J. Genial. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package form
import (
"encoding"
"fmt"
"net/url"
"time"
)
type Struct struct {
B bool
I int `form:"life"`
F float64
C complex128
R rune `form:",omitempty"` // For testing when non-empty.
Re rune `form:",omitempty"` // For testing when empty.
S string
T time.Time
U url.URL
A Array
M Map
Y interface{} `form:"-"` // For testing when non-empty.
Ye interface{} `form:"-"` // For testing when empty.
Zs Slice
E // Embedded.
P P `form:"P.D\\Q.B"`
}
type SXs map[string]interface{}
type E struct {
Bytes1 []byte // For testing explicit (qualified by embedder) name, e.g. "E.Bytes1".
Bytes2 []byte // For testing implicit (unqualified) name, e.g. just "Bytes2"
}
type Z time.Time // Defined as such to test conversions.
func (z Z) String() string { return time.Time(z).String() }
type Array [3]string
type Map map[string]int
type Slice []struct {
Z Z
Q Q
Qp *Q
Q2 Q `form:"-"`
E `form:"-"`
}
// Custom marshaling
type Q struct {
a, b uint16
}
var (
_ encoding.TextMarshaler = &Q{}
_ encoding.TextUnmarshaler = &Q{}
)
func (u Q) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("%d_%d", u.a, u.b)), nil
}
func (u *Q) UnmarshalText(bs []byte) error {
_, err := fmt.Sscanf(string(bs), "%d_%d", &u.a, &u.b)
return err
}
func prepopulate(sxs SXs) SXs {
var B bool
var I int
var F float64
var C complex128
var R rune
var S string
var T time.Time
var U url.URL
var A Array
var M Map
// Y is ignored.
// Ye is ignored.
var Zs Slice
var E E
var P P
sxs["B"] = B
sxs["life"] = I
sxs["F"] = F
sxs["C"] = C
sxs["R"] = R
// Re is omitted.
sxs["S"] = S
sxs["T"] = T
sxs["U"] = U
sxs["A"] = A
sxs["M"] = M
// Y is ignored.
// Ye is ignored.
sxs["Zs"] = Zs
sxs["E"] = E
sxs["P.D\\Q.B"] = P
return sxs
}
type P struct {
A, B string
}
type direction int
const (
encOnly = 1
decOnly = 2
rndTrip = encOnly | decOnly
)
func testCases(dir direction) (cs []testCase) {
var B bool
var I int
var F float64
var C complex128
var R rune
var S string
var T time.Time
var U url.URL
const canonical = `A.0=x&A.1=y&A.2=z&B=true&C=42%2B6.6i&E.Bytes1=%00%01%02&E.Bytes2=%03%04%05&F=6.6&M.Bar=8&M.Foo=7&M.Qux=9&P%5C.D%5C%5CQ%5C.B.A=P%2FD&P%5C.D%5C%5CQ%5C.B.B=Q-B&R=8734&S=Hello%2C+there.&T=2013-10-01T07%3A05%3A34.000000088Z&U=http%3A%2F%2Fexample.org%2Ffoo%23bar&Zs.0.Q=11_22&Zs.0.Qp=33_44&Zs.0.Z=2006-12-01&life=42`
const variation = `;C=42%2B6.6i;A.0=x;M.Bar=8;F=6.6;A.1=y;R=8734;A.2=z;Zs.0.Qp=33_44;B=true;M.Foo=7;T=2013-10-01T07:05:34.000000088Z;E.Bytes1=%00%01%02;Bytes2=%03%04%05;Zs.0.Q=11_22;Zs.0.Z=2006-12-01;M.Qux=9;life=42;S=Hello,+there.;P\.D\\Q\.B.A=P/D;P\.D\\Q\.B.B=Q-B;U=http%3A%2F%2Fexample.org%2Ffoo%23bar;`
for _, c := range []testCase{
// Bools
{rndTrip, &B, "=", b(false)},
{rndTrip, &B, "=true", b(true)},
{decOnly, &B, "=false", b(false)},
// Ints
{rndTrip, &I, "=", i(0)},
{rndTrip, &I, "=42", i(42)},
{rndTrip, &I, "=-42", i(-42)},
{decOnly, &I, "=0", i(0)},
{decOnly, &I, "=-0", i(0)},
// Floats
{rndTrip, &F, "=", f(0)},
{rndTrip, &F, "=6.6", f(6.6)},
{rndTrip, &F, "=-6.6", f(-6.6)},
// Complexes
{rndTrip, &C, "=", c(complex(0, 0))},
{rndTrip, &C, "=42%2B6.6i", c(complex(42, 6.6))},
{rndTrip, &C, "=-42-6.6i", c(complex(-42, -6.6))},
// Runes
{rndTrip, &R, "=", r(0)},
{rndTrip, &R, "=97", r('a')},
{rndTrip, &R, "=8734", r('\u221E')},
// Strings
{rndTrip, &S, "=", s("")},
{rndTrip, &S, "=X+%26+Y+%26+Z", s("X & Y & Z")},
{rndTrip, &S, "=Hello%2C+there.", s("Hello, there.")},
{decOnly, &S, "=Hello, there.", s("Hello, there.")},
// Dates/Times
{rndTrip, &T, "=", t(time.Time{})},
{rndTrip, &T, "=2013-10-01T07%3A05%3A34.000000088Z", t(time.Date(2013, 10, 1, 7, 5, 34, 88, time.UTC))},
{decOnly, &T, "=2013-10-01T07:05:34.000000088Z", t(time.Date(2013, 10, 1, 7, 5, 34, 88, time.UTC))},
{rndTrip, &T, "=07%3A05%3A34.000000088Z", t(time.Date(0, 1, 1, 7, 5, 34, 88, time.UTC))},
{decOnly, &T, "=07:05:34.000000088Z", t(time.Date(0, 1, 1, 7, 5, 34, 88, time.UTC))},
{rndTrip, &T, "=2013-10-01", t(time.Date(2013, 10, 1, 0, 0, 0, 0, time.UTC))},
// URLs
{rndTrip, &U, "=", u(url.URL{})},
{rndTrip, &U, "=http%3A%2F%2Fexample.org%2Ffoo%23bar", u(url.URL{Scheme: "http", Host: "example.org", Path: "/foo", Fragment: "bar"})},
{rndTrip, &U, "=git%3A%2F%2Fgithub.com%2Fajg%2Fform.git", u(url.URL{Scheme: "git", Host: "github.com", Path: "/ajg/form.git"})},
// Structs
{rndTrip, &Struct{Y: 786}, canonical,
&Struct{
true,
42,
6.6,
complex(42, 6.6),
'\u221E',
rune(0),
"Hello, there.",
time.Date(2013, 10, 1, 7, 5, 34, 88, time.UTC),
url.URL{Scheme: "http", Host: "example.org", Path: "/foo", Fragment: "bar"},
Array{"x", "y", "z"},
Map{"Foo": 7, "Bar": 8, "Qux": 9},
786, // Y: This value should not change.
nil, // Ye: This value should not change.
Slice{{Z(time.Date(2006, 12, 1, 0, 0, 0, 0, time.UTC)), Q{11, 22}, &Q{33, 44}, Q{}, E{}}},
E{[]byte{0, 1, 2}, []byte{3, 4, 5}},
P{"P/D", "Q-B"},
},
},
{decOnly, &Struct{Y: 786}, variation,
&Struct{
true,
42,
6.6,
complex(42, 6.6),
'\u221E',
rune(0),
"Hello, there.",
time.Date(2013, 10, 1, 7, 5, 34, 88, time.UTC),
url.URL{Scheme: "http", Host: "example.org", Path: "/foo", Fragment: "bar"},
Array{"x", "y", "z"},
Map{"Foo": 7, "Bar": 8, "Qux": 9},
786, // Y: This value should not change.
nil, // Ye: This value should not change.
Slice{{Z(time.Date(2006, 12, 1, 0, 0, 0, 0, time.UTC)), Q{11, 22}, &Q{33, 44}, Q{}, E{}}},
E{[]byte{0, 1, 2}, []byte{3, 4, 5}},
P{"P/D", "Q-B"},
},
},
// Maps
{rndTrip, prepopulate(SXs{}), canonical,
SXs{"B": true,
"life": 42,
"F": 6.6,
"C": complex(42, 6.6),
"R": '\u221E',
// Re is omitted.
"S": "Hello, there.",
"T": time.Date(2013, 10, 1, 7, 5, 34, 88, time.UTC),
"U": url.URL{Scheme: "http", Host: "example.org", Path: "/foo", Fragment: "bar"},
"A": Array{"x", "y", "z"},
"M": Map{"Foo": 7, "Bar": 8, "Qux": 9},
// Y is ignored.
// Ye is ignored.
"Zs": Slice{{Z(time.Date(2006, 12, 1, 0, 0, 0, 0, time.UTC)), Q{11, 22}, &Q{33, 44}, Q{}, E{}}},
"E": E{[]byte{0, 1, 2}, []byte{3, 4, 5}},
"P.D\\Q.B": P{"P/D", "Q-B"},
},
},
{decOnly, prepopulate(SXs{}), variation,
SXs{"B": true,
"life": 42,
"F": 6.6,
"C": complex(42, 6.6),
"R": '\u221E',
// Re is omitted.
"S": "Hello, there.",
"T": time.Date(2013, 10, 1, 7, 5, 34, 88, time.UTC),
"U": url.URL{Scheme: "http", Host: "example.org", Path: "/foo", Fragment: "bar"},
"A": Array{"x", "y", "z"},
"M": Map{"Foo": 7, "Bar": 8, "Qux": 9},
// Y is ignored.
// Ye is ignored.
"Zs": Slice{{Z(time.Date(2006, 12, 1, 0, 0, 0, 0, time.UTC)), Q{11, 22}, &Q{33, 44}, Q{}, E{}}},
"E": E{[]byte{0, 1, 2}, nil},
"Bytes2": string([]byte{3, 4, 5}),
"P.D\\Q.B": P{"P/D", "Q-B"},
},
},
{rndTrip, SXs{}, canonical,
SXs{"B": "true",
"life": "42",
"F": "6.6",
"C": "42+6.6i",
"R": "8734",
// Re is omitted.
"S": "Hello, there.",
"T": "2013-10-01T07:05:34.000000088Z",
"U": "http://example.org/foo#bar",
"A": map[string]interface{}{"0": "x", "1": "y", "2": "z"},
"M": map[string]interface{}{"Foo": "7", "Bar": "8", "Qux": "9"},
// Y is ignored.
// Ye is ignored.
"Zs": map[string]interface{}{
"0": map[string]interface{}{
"Z": "2006-12-01",
"Q": "11_22",
"Qp": "33_44",
},
},
"E": map[string]interface{}{"Bytes1": string([]byte{0, 1, 2}), "Bytes2": string([]byte{3, 4, 5})},
"P.D\\Q.B": map[string]interface{}{"A": "P/D", "B": "Q-B"},
},
},
{decOnly, SXs{}, variation,
SXs{"B": "true",
"life": "42",
"F": "6.6",
"C": "42+6.6i",
"R": "8734",
// Re is omitted.
"S": "Hello, there.",
"T": "2013-10-01T07:05:34.000000088Z",
"U": "http://example.org/foo#bar",
"A": map[string]interface{}{"0": "x", "1": "y", "2": "z"},
"M": map[string]interface{}{"Foo": "7", "Bar": "8", "Qux": "9"},
// Y is ignored.
// Ye is ignored.
"Zs": map[string]interface{}{
"0": map[string]interface{}{
"Z": "2006-12-01",
"Q": "11_22",
"Qp": "33_44",
},
},
"E": map[string]interface{}{"Bytes1": string([]byte{0, 1, 2})},
"Bytes2": string([]byte{3, 4, 5}),
"P.D\\Q.B": map[string]interface{}{"A": "P/D", "B": "Q-B"},
},
},
} {
if c.d&dir != 0 {
cs = append(cs, c)
}
}
return cs
}
type testCase struct {
d direction
a interface{}
s string
b interface{}
}
func b(b bool) *bool { return &b }
func i(i int) *int { return &i }
func f(f float64) *float64 { return &f }
func c(c complex128) *complex128 { return &c }
func r(r rune) *rune { return &r }
func s(s string) *string { return &s }
func t(t time.Time) *time.Time { return &t }
func u(u url.URL) *url.URL { return &u }
func mustParseQuery(s string) url.Values {
vs, err := url.ParseQuery(s)
if err != nil {
panic(err)
}
return vs
}