343 lines
9.2 KiB
Go
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
|
|
}
|