Skip to content

Commit

Permalink
Integer-index encode all arrays
Browse files Browse the repository at this point in the history
Changes all arrays from classic Rack encoding:

``` sh
arr[]=...&arr[]=...&arr[]=...
```

To integer-indexed encoding:

``` sh
arr[0]=...&arr[1]=...&arr[2]=...
```

See some additional background in: stripe/stripe-ruby#674
  • Loading branch information
brandur committed Aug 14, 2018
1 parent 27f4015 commit bddb882
Showing 9 changed files with 16 additions and 74 deletions.
2 changes: 1 addition & 1 deletion account.go
Original file line number Diff line number Diff line change
@@ -126,7 +126,7 @@ type AccountParams struct {

// LegalEntityParams represents a legal_entity during account creation/updates.
type LegalEntityParams struct {
AdditionalOwners []*AdditionalOwnerParams `form:"additional_owners,indexed"`
AdditionalOwners []*AdditionalOwnerParams `form:"additional_owners"`

// AdditionalOwnersEmpty can be set to clear a legal entity's additional
// owners.
5 changes: 5 additions & 0 deletions account/client_test.go
Original file line number Diff line number Diff line change
@@ -56,11 +56,16 @@ func TestAccountNew(t *testing.T) {
AdditionalOwners: []*stripe.AdditionalOwnerParams{
{
FirstName: stripe.String("Jane"),
LastName: stripe.String("Doe"),
Verification: &stripe.IdentityVerificationParams{
Document: stripe.String("file_345"),
DocumentBack: stripe.String("file_567"),
},
},
{
FirstName: stripe.String("John"),
LastName: stripe.String("Doe"),
},
},
DOB: &stripe.DOBParams{
Day: stripe.Int64(1),
22 changes: 2 additions & 20 deletions form/form.go
Original file line number Diff line number Diff line change
@@ -49,11 +49,6 @@ type field struct {
}

type formOptions struct {
// IndexedArray indicates that contrary to standard "Rack-style" form
// encoding, array items should be index like `arr[0]=...&arr[1]=...`
// (normally it'd be `arr[]=...`).
IndexedArray bool

// Empty indicates that a field's value should be emptied in that its value
// should be an empty string. It's used to workaround the fact that an
// empty string is a string's zero value and wouldn't normally be encoded.
@@ -166,17 +161,10 @@ func buildArrayOrSliceEncoder(t reflect.Type) encoderFunc {
elemF := getCachedOrBuildTypeEncoder(t.Elem())

return func(values *Values, v reflect.Value, keyParts []string, _ bool, options *formOptions) {
// FormatKey automatically adds square brackets, so just pass an empty
// string into the breadcrumb trail
arrNames := append(keyParts, "")
var arrNames []string

for i := 0; i < v.Len(); i++ {
// The one exception to the above is when options have requested
// that this array/slice be indexed. In that case we produce a hash
// keyed with integers which the Stripe API knows how to interpret.
if options != nil && options.IndexedArray {
arrNames = append(keyParts, strconv.Itoa(i))
}
arrNames = append(keyParts, strconv.Itoa(i))

indexV := v.Index(i)
elemF(values, indexV, arrNames, indexV.Kind() == reflect.Ptr, nil)
@@ -454,12 +442,6 @@ func parseTag(tag string) (string, *formOptions) {
}
options.Empty = true

case "indexed":
if options == nil {
options = &formOptions{}
}
options.IndexedArray = true

case "zero":
if options == nil {
options = &formOptions{}
49 changes: 2 additions & 47 deletions form/form_test.go
Original file line number Diff line number Diff line change
@@ -24,9 +24,6 @@ type testStruct struct {
Array [3]string `form:"array"`
ArrayPtr *[3]string `form:"array_ptr"`

ArrayIndexed [3]string `form:"array_indexed,indexed"`
ArrayIndexedPtr *[3]string `form:"array_indexed_ptr,indexed"`

Bool bool `form:"bool"`
BoolPtr *bool `form:"bool_ptr"`

@@ -59,9 +56,6 @@ type testStruct struct {
String string `form:"string"`
StringPtr *string `form:"string_ptr"`

SliceIndexed []string `form:"slice_indexed,indexed"`
SliceIndexedPtr *[]string `form:"slice_indexed_ptr,indexed"`

SubStruct testSubStruct `form:"substruct"`
SubStructPtr *testSubStruct `form:"substruct_ptr"`

@@ -176,7 +170,7 @@ func TestAppendTo(t *testing.T) {
}{
{"appender", &testStruct{Appender: &testAppender{String: "123"}}, "123"},

{"array_indexed[2]", &testStruct{ArrayIndexed: arrayVal}, "3"},
{"array[2]", &testStruct{Array: arrayVal}, "3"},

{"bool", &testStruct{Bool: boolValT}, "true"},
{"bool_ptr", &testStruct{}, ""},
@@ -248,7 +242,7 @@ func TestAppendTo(t *testing.T) {
"baz",
},

{"slice_indexed[2]", &testStruct{SliceIndexed: sliceVal}, "3"},
{"slice[2]", &testStruct{Slice: sliceVal}, "3"},

{"string", &testStruct{String: stringVal}, stringVal},
{"string_ptr", &testStruct{StringPtr: &stringVal}, stringVal},
@@ -295,44 +289,6 @@ func TestAppendTo(t *testing.T) {
}
}

func TestAppendTo_DuplicatedNames(t *testing.T) {
arrayVal := [3]string{"1", "2", "3"}
sliceVal := []string{"1", "2", "3"}

testCases := []struct {
field string
data *testStruct
want interface{}
}{
{"array[]", &testStruct{Array: arrayVal}, sliceVal},
{"array_ptr[]", &testStruct{ArrayPtr: &arrayVal}, sliceVal},
{"slice[]", &testStruct{Slice: sliceVal}, sliceVal},
{"slice_ptr[]", &testStruct{SlicePtr: &sliceVal}, sliceVal},

// Tests slice nested inside of map nested inside of another map
{
"map[foo][bar][]",
&testStruct{Map: map[string]interface{}{
"foo": map[string]interface{}{"bar": sliceVal},
}},
sliceVal,
},
}
for _, tc := range testCases {
t.Run(tc.field, func(t *testing.T) {
form := &Values{}
AppendTo(form, tc.data)
values := form.ToValues()
//fmt.Printf("values = %+v", values)

// This is the only difference between this test case and the one
// above: we used square brackets to grab a []string instead of
// just a single value.
assert.Equal(t, tc.want, values[tc.field])
})
}
}

func TestAppendTo_IgnoredFields(t *testing.T) {
form := &Values{}
data := &testStruct{Ignored: "value"}
@@ -405,7 +361,6 @@ func TestParseTag(t *testing.T) {
}{
{"id", "id", nil},
{"id,empty", "id", &formOptions{Empty: true}},
{"id,indexed", "id", &formOptions{IndexedArray: true}},
{"id,zero", "id", &formOptions{Zero: true}},

// invalid invocations
2 changes: 1 addition & 1 deletion invoice.go
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ type InvoiceParams struct {
Coupon *string `form:"coupon"`
InvoiceItems *InvoiceUpcomingInvoiceItemParams `form:"invoice_items"`
SubscriptionBillingCycleAnchor *int64 `form:"subscription_billing_cycle_anchor"`
SubscriptionItems []*SubscriptionItemsParams `form:"subscription_items,indexed"`
SubscriptionItems []*SubscriptionItemsParams `form:"subscription_items"`
SubscriptionPlan *string `form:"subscription_plan"`
SubscriptionProrate *bool `form:"subscription_prorate"`
SubscriptionProrationDate *int64 `form:"subscription_proration_date"`
4 changes: 2 additions & 2 deletions order.go
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ type OrderParams struct {
Currency *string `form:"currency"`
Customer *string `form:"customer"`
Email *string `form:"email"`
Items []*OrderItemParams `form:"items,indexed"`
Items []*OrderItemParams `form:"items"`
Shipping *ShippingParams `form:"shipping"`
}

@@ -74,7 +74,7 @@ type OrderUpdateShippingParams struct {
// OrderReturnParams is the set of parameters that can be used when returning orders.
type OrderReturnParams struct {
Params `form:"*"`
Items []*OrderItemParams `form:"items,indexed"`
Items []*OrderItemParams `form:"items"`
}

// Shipping describes the shipping hash on an order.
2 changes: 1 addition & 1 deletion params_test.go
Original file line number Diff line number Diff line change
@@ -196,7 +196,7 @@ func TestListParams_Expand(t *testing.T) {
{
InitialBody: [][2]string{{"foo", "bar"}, {"foo", "baz"}},
Expand: []string{"data", "data.foo"},
ExpectedBody: [][2]string{{"foo", "bar"}, {"foo", "baz"}, {"expand[]", "data"}, {"expand[]", "data.foo"}},
ExpectedBody: [][2]string{{"foo", "bar"}, {"foo", "baz"}, {"expand[0]", "data"}, {"expand[1]", "data.foo"}},
},
}

2 changes: 1 addition & 1 deletion plan.go
Original file line number Diff line number Diff line change
@@ -119,7 +119,7 @@ type PlanParams struct {
Nickname *string `form:"nickname"`
Product *PlanProductParams `form:"product"`
ProductID *string `form:"product"`
Tiers []*PlanTierParams `form:"tiers,indexed"`
Tiers []*PlanTierParams `form:"tiers"`
TiersMode *string `form:"tiers_mode"`
TransformUsage *PlanTransformUsageParams `form:"transform_usage"`
TrialPeriodDays *int64 `form:"trial_period_days"`
2 changes: 1 addition & 1 deletion sub.go
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ type SubscriptionParams struct {
Coupon *string `form:"coupon"`
Customer *string `form:"customer"`
DaysUntilDue *int64 `form:"days_until_due"`
Items []*SubscriptionItemsParams `form:"items,indexed"`
Items []*SubscriptionItemsParams `form:"items"`
OnBehalfOf *string `form:"on_behalf_of"`
Plan *string `form:"plan"`
Prorate *bool `form:"prorate"`

0 comments on commit bddb882

Please sign in to comment.