From b8b73a35e9830ae509858c10dec5866b4d5c8bff Mon Sep 17 00:00:00 2001 From: earncef Date: Mon, 2 Jul 2018 16:04:55 +0530 Subject: [PATCH] URLQuery with index slice keys (#74) * URLQuery with index slice keys * Added tests * Cleanup * Added test for MSI and cleaned up redundant code * Added SetURLValuesSliceKeySuffix() * Added error to SetURLValuesSliceKeySuffix() --- conversions.go | 88 +++++++++++++++++++++++++++++++++++---------- conversions_test.go | 40 +++++++++++++++++++-- 2 files changed, 106 insertions(+), 22 deletions(-) diff --git a/conversions.go b/conversions.go index 7b0d812..8b912e9 100644 --- a/conversions.go +++ b/conversions.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "net/url" + "strconv" ) // SignatureSeparator is the character that is used to @@ -14,9 +15,34 @@ const SignatureSeparator = "_" // URLValuesSliceKeySuffix is the character that is used to // specify a suffic for slices parsed by URLValues. +// If the suffix is set to "[i]", then the index of the slice +// is used in place of i // Ex: Suffix "[]" would have the form a[]=b&a[]=c +// OR Suffix "[i]" would have the form a[0]=b&a[1]=c // OR Suffix "" would have the form a=b&a=c -var URLValuesSliceKeySuffix = "[]" +var urlValuesSliceKeySuffix = "[]" + +const ( + URLValuesSliceKeySuffixEmpty = "" + URLValuesSliceKeySuffixArray = "[]" + URLValuesSliceKeySuffixIndex = "[i]" +) + +// SetURLValuesSliceKeySuffix sets the character that is used to +// specify a suffic for slices parsed by URLValues. +// If the suffix is set to "[i]", then the index of the slice +// is used in place of i +// Ex: Suffix "[]" would have the form a[]=b&a[]=c +// OR Suffix "[i]" would have the form a[0]=b&a[1]=c +// OR Suffix "" would have the form a=b&a=c +func SetURLValuesSliceKeySuffix(s string) error { + if s == URLValuesSliceKeySuffixEmpty || s == URLValuesSliceKeySuffixArray || s == URLValuesSliceKeySuffixIndex { + urlValuesSliceKeySuffix = s + return nil + } + + return errors.New("objx: Invalid URLValuesSliceKeySuffix provided.") +} // JSON converts the contained object to a JSON string // representation @@ -106,50 +132,74 @@ func (m Map) URLValues() url.Values { } func (m Map) parseURLValues(queryMap Map, vals url.Values, key string) { + useSliceIndex := false + if urlValuesSliceKeySuffix == "[i]" { + useSliceIndex = true + } + for k, v := range queryMap { val := &Value{data: v} switch { case val.IsObjxMap(): if key == "" { - m.parseURLValues(v.(Map), vals, k) + m.parseURLValues(val.ObjxMap(), vals, k) } else { - m.parseURLValues(v.(Map), vals, key+"["+k+"]") + m.parseURLValues(val.ObjxMap(), vals, key+"["+k+"]") } case val.IsObjxMapSlice(): - sliceKey := k + URLValuesSliceKeySuffix + sliceKey := k if key != "" { - sliceKey = key + "[" + k + "]" + URLValuesSliceKeySuffix + sliceKey = key + "[" + k + "]" } - for _, sv := range val.MustObjxMapSlice() { - m.parseURLValues(sv, vals, sliceKey) - } - case val.IsMSI(): - if key == "" { - m.parseURLValues(New(v), vals, k) + if useSliceIndex { + for i, sv := range val.MustObjxMapSlice() { + sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]" + m.parseURLValues(sv, vals, sk) + } } else { - m.parseURLValues(New(v), vals, key+"["+k+"]") + sliceKey = sliceKey + urlValuesSliceKeySuffix + for _, sv := range val.MustObjxMapSlice() { + m.parseURLValues(sv, vals, sliceKey) + } } case val.IsMSISlice(): - sliceKey := k + URLValuesSliceKeySuffix + sliceKey := k if key != "" { - sliceKey = key + "[" + k + "]" + URLValuesSliceKeySuffix + sliceKey = key + "[" + k + "]" } - for _, sv := range val.MustMSISlice() { - m.parseURLValues(New(sv), vals, sliceKey) + if useSliceIndex { + for i, sv := range val.MustMSISlice() { + sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]" + m.parseURLValues(New(sv), vals, sk) + } + } else { + sliceKey = sliceKey + urlValuesSliceKeySuffix + for _, sv := range val.MustMSISlice() { + m.parseURLValues(New(sv), vals, sliceKey) + } } case val.IsStrSlice(), val.IsBoolSlice(), val.IsFloat32Slice(), val.IsFloat64Slice(), val.IsIntSlice(), val.IsInt8Slice(), val.IsInt16Slice(), val.IsInt32Slice(), val.IsInt64Slice(), val.IsUintSlice(), val.IsUint8Slice(), val.IsUint16Slice(), val.IsUint32Slice(), val.IsUint64Slice(): - sliceKey := k + URLValuesSliceKeySuffix + sliceKey := k if key != "" { - sliceKey = key + "[" + k + "]" + URLValuesSliceKeySuffix + sliceKey = key + "[" + k + "]" + } + + if useSliceIndex { + for i, sv := range val.StringSlice() { + sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]" + vals.Set(sk, sv) + } + } else { + sliceKey = sliceKey + urlValuesSliceKeySuffix + vals[sliceKey] = val.StringSlice() } - vals[sliceKey] = val.StringSlice() default: if key == "" { vals.Set(k, val.String()) diff --git a/conversions_test.go b/conversions_test.go index 61b379a..c719e31 100644 --- a/conversions_test.go +++ b/conversions_test.go @@ -93,6 +93,11 @@ func TestConversionURLValues(t *testing.T) { "bools[]": []string{"true", "false"}, "mapSlice[][age]": []string{"40"}, "mapSlice[][height]": []string{"152"}, + "msiData[age]": []string{"30"}, + "msiData[height]": []string{"162"}, + "msiData[arr][]": []string{"1", "2"}, + "msiSlice[][age]": []string{"40"}, + "msiSlice[][height]": []string{"152"}, }, u) } @@ -107,12 +112,12 @@ func TestConversionURLQuery(t *testing.T) { assert.Nil(t, err) require.NotNil(t, ue) - assert.Equal(t, "abc=123&bools[]=true&bools[]=false&data[age]=30&data[arr][]=1&data[arr][]=2&data[height]=162&mapSlice[][age]=40&mapSlice[][height]=152&name=Mat&stats[]=1&stats[]=2", ue) + assert.Equal(t, "abc=123&bools[]=true&bools[]=false&data[age]=30&data[arr][]=1&data[arr][]=2&data[height]=162&mapSlice[][age]=40&mapSlice[][height]=152&msiData[age]=30&msiData[arr][]=1&msiData[arr][]=2&msiData[height]=162&msiSlice[][age]=40&msiSlice[][height]=152&name=Mat&stats[]=1&stats[]=2", ue) } func TestConversionURLQueryNoSliceKeySuffix(t *testing.T) { m := getURLQueryMap() - objx.URLValuesSliceKeySuffix = "" + objx.SetURLValuesSliceKeySuffix(objx.URLValuesSliceKeySuffixEmpty) u, err := m.URLQuery() assert.Nil(t, err) @@ -122,7 +127,34 @@ func TestConversionURLQueryNoSliceKeySuffix(t *testing.T) { assert.Nil(t, err) require.NotNil(t, ue) - assert.Equal(t, "abc=123&bools=true&bools=false&data[age]=30&data[arr]=1&data[arr]=2&data[height]=162&mapSlice[age]=40&mapSlice[height]=152&name=Mat&stats=1&stats=2", ue) + assert.Equal(t, "abc=123&bools=true&bools=false&data[age]=30&data[arr]=1&data[arr]=2&data[height]=162&mapSlice[age]=40&mapSlice[height]=152&msiData[age]=30&msiData[arr]=1&msiData[arr]=2&msiData[height]=162&msiSlice[age]=40&msiSlice[height]=152&name=Mat&stats=1&stats=2", ue) +} + +func TestConversionURLQueryIndexSliceKeySuffix(t *testing.T) { + m := getURLQueryMap() + m.Set("mapSlice", []objx.Map{{"age": 40, "sex": "male"}, {"height": 152}}) + objx.SetURLValuesSliceKeySuffix(objx.URLValuesSliceKeySuffixIndex) + u, err := m.URLQuery() + + assert.Nil(t, err) + require.NotNil(t, u) + + ue, err := url.QueryUnescape(u) + assert.Nil(t, err) + require.NotNil(t, ue) + + assert.Equal(t, "abc=123&bools[0]=true&bools[1]=false&data[age]=30&data[arr][0]=1&data[arr][1]=2&data[height]=162&mapSlice[0][age]=40&mapSlice[0][sex]=male&mapSlice[1][height]=152&msiData[age]=30&msiData[arr][0]=1&msiData[arr][1]=2&msiData[height]=162&msiSlice[0][age]=40&msiSlice[1][height]=152&name=Mat&stats[0]=1&stats[1]=2", ue) +} + +func TestValidityURLQuerySliceKeySuffix(t *testing.T) { + err := objx.SetURLValuesSliceKeySuffix("") + assert.Nil(t, err) + err = objx.SetURLValuesSliceKeySuffix("[]") + assert.Nil(t, err) + err = objx.SetURLValuesSliceKeySuffix("[i]") + assert.Nil(t, err) + err = objx.SetURLValuesSliceKeySuffix("{}") + assert.Error(t, err) } func getURLQueryMap() objx.Map { @@ -131,6 +163,8 @@ func getURLQueryMap() objx.Map { "name": "Mat", "data": objx.Map{"age": 30, "height": 162, "arr": []int{1, 2}}, "mapSlice": []objx.Map{{"age": 40}, {"height": 152}}, + "msiData": map[string]interface{}{"age": 30, "height": 162, "arr": []int{1, 2}}, + "msiSlice": []map[string]interface{}{{"age": 40}, {"height": 152}}, "stats": []string{"1", "2"}, "bools": []bool{true, false}, }