Skip to content

Commit

Permalink
URLValues without sprinf, support for maps and slices (#63)
Browse files Browse the repository at this point in the history
* URLValues without sprinf, support for maps and slices

* Fixeded import from earncef/objx

* Cleanup

* Cleanup

* Fixed tests
  • Loading branch information
geseq authored and hanzei committed May 27, 2018
1 parent a97c7cc commit fb88214
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 9 deletions.
69 changes: 64 additions & 5 deletions conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
)

// SignatureSeparator is the character that is used to
// separate the Base64 string from the security signature.
const SignatureSeparator = "_"

// URLValuesSliceKeySuffix is the character that is used to
// specify a suffic for slices parsed by URLValues.
// Ex: Suffix "[]" would have the form a[]=b&a[]=c
// OR Suffix "" would have the form a=b&a=c
var URLValuesSliceKeySuffix = "[]"

// JSON converts the contained object to a JSON string
// representation
func (m Map) JSON() (string, error) {
Expand Down Expand Up @@ -94,13 +99,67 @@ func (m Map) MustSignedBase64(key string) string {
// function requires that the wrapped object be a map[string]interface{}
func (m Map) URLValues() url.Values {
vals := make(url.Values)
for k, v := range m {
//TODO: can this be done without sprintf?
vals.Set(k, fmt.Sprintf("%v", v))
}

m.parseURLValues(m, vals, "")

return vals
}

func (m Map) parseURLValues(queryMap Map, vals url.Values, key string) {
for k, v := range queryMap {
val := &Value{data: v}
switch {
case val.IsObjxMap():
if key == "" {
m.parseURLValues(v.(Map), vals, k)
} else {
m.parseURLValues(v.(Map), vals, key+"["+k+"]")
}
case val.IsObjxMapSlice():
sliceKey := k + URLValuesSliceKeySuffix
if key != "" {
sliceKey = key + "[" + k + "]" + URLValuesSliceKeySuffix
}

for _, sv := range val.MustObjxMapSlice() {
m.parseURLValues(sv, vals, sliceKey)
}
case val.IsMSI():
if key == "" {
m.parseURLValues(New(v), vals, k)
} else {
m.parseURLValues(New(v), vals, key+"["+k+"]")
}
case val.IsMSISlice():
sliceKey := k + URLValuesSliceKeySuffix
if key != "" {
sliceKey = key + "[" + k + "]" + 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
if key != "" {
sliceKey = key + "[" + k + "]" + URLValuesSliceKeySuffix
}

vals[sliceKey] = val.StringSlice()
default:
if key == "" {
vals.Set(k, val.String())
} else {
vals.Set(key+"["+k+"]", val.String())
}
}
}
}

// URLQuery gets an encoded URL query representing the given
// Obj. This function requires that the wrapped object be a
// map[string]interface{}
Expand Down
49 changes: 45 additions & 4 deletions conversions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,58 @@ func TestConversionSignedBase64WithError(t *testing.T) {
}

func TestConversionURLValues(t *testing.T) {
m := objx.Map{"abc": 123, "name": "Mat"}
m := getURLQueryMap()
u := m.URLValues()

assert.Equal(t, url.Values{"abc": []string{"123"}, "name": []string{"Mat"}}, u)
assert.Equal(t, url.Values{
"abc": []string{"123"},
"name": []string{"Mat"},
"data[age]": []string{"30"},
"data[height]": []string{"162"},
"data[arr][]": []string{"1", "2"},
"stats[]": []string{"1", "2"},
"bools[]": []string{"true", "false"},
"mapSlice[][age]": []string{"40"},
"mapSlice[][height]": []string{"152"},
}, u)
}

func TestConversionURLQuery(t *testing.T) {
m := objx.Map{"abc": 123, "name": "Mat"}
m := getURLQueryMap()
u, err := m.URLQuery()

assert.Nil(t, err)
require.NotNil(t, u)
assert.Equal(t, "abc=123&name=Mat", u)

ue, err := url.QueryUnescape(u)
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)
}

func TestConversionURLQueryNoSliceKeySuffix(t *testing.T) {
m := getURLQueryMap()
objx.URLValuesSliceKeySuffix = ""
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=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)
}

func getURLQueryMap() objx.Map {
return objx.Map{
"abc": 123,
"name": "Mat",
"data": objx.Map{"age": 30, "height": 162, "arr": []int{1, 2}},
"mapSlice": []objx.Map{objx.Map{"age": 40}, objx.Map{"height": 152}},
"stats": []string{"1", "2"},
"bools": []bool{true, false},
}
}
104 changes: 104 additions & 0 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,107 @@ func (v *Value) String() string {
}
return fmt.Sprintf("%#v", v.Data())
}

// StringSlice returns the value always as a []string
func (v *Value) StringSlice(optionalDefault ...[]string) []string {
switch {
case v.IsStrSlice():
return v.MustStrSlice()
case v.IsBoolSlice():
slice := v.MustBoolSlice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatBool(iv)
}
return vals
case v.IsFloat32Slice():
slice := v.MustFloat32Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatFloat(float64(iv), 'f', -1, 32)
}
return vals
case v.IsFloat64Slice():
slice := v.MustFloat64Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatFloat(iv, 'f', -1, 64)
}
return vals
case v.IsIntSlice():
slice := v.MustIntSlice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatInt(int64(iv), 10)
}
return vals
case v.IsInt8Slice():
slice := v.MustInt8Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatInt(int64(iv), 10)
}
return vals
case v.IsInt16Slice():
slice := v.MustInt16Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatInt(int64(iv), 10)
}
return vals
case v.IsInt32Slice():
slice := v.MustInt32Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatInt(int64(iv), 10)
}
return vals
case v.IsInt64Slice():
slice := v.MustInt64Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatInt(iv, 10)
}
return vals
case v.IsUintSlice():
slice := v.MustUintSlice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatUint(uint64(iv), 10)
}
return vals
case v.IsUint8Slice():
slice := v.MustUint8Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatUint(uint64(iv), 10)
}
return vals
case v.IsUint16Slice():
slice := v.MustUint16Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatUint(uint64(iv), 10)
}
return vals
case v.IsUint32Slice():
slice := v.MustUint32Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatUint(uint64(iv), 10)
}
return vals
case v.IsUint64Slice():
slice := v.MustUint64Slice()
vals := make([]string, len(slice))
for i, iv := range slice {
vals[i] = strconv.FormatUint(iv, 10)
}
return vals
}
if len(optionalDefault) == 1 {
return optionalDefault[0]
}

return []string{}
}
67 changes: 67 additions & 0 deletions value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,70 @@ func TestStringTypeOther(t *testing.T) {

assert.Equal(t, "[]string{\"foo\", \"bar\"}", m.Get("other").String())
}

func TestStringSliceTypeString(t *testing.T) {
m := objx.Map{
"string": []string{"foo", "bar"},
}

assert.Equal(t, []string{"foo", "bar"}, m.Get("string").StringSlice())
}

func TestStringSliceTypeBool(t *testing.T) {
m := objx.Map{
"bool": []bool{true, false},
}

assert.Equal(t, []string{"true", "false"}, m.Get("bool").StringSlice())
}

func TestStringSliceTypeInt(t *testing.T) {
m := objx.Map{
"int": []int{1, 2},
"int8": []int8{8, 9},
"int16": []int16{16, 17},
"int32": []int32{32, 33},
"int64": []int64{64, 65},
}

assert.Equal(t, []string{"1", "2"}, m.Get("int").StringSlice())
assert.Equal(t, []string{"8", "9"}, m.Get("int8").StringSlice())
assert.Equal(t, []string{"16", "17"}, m.Get("int16").StringSlice())
assert.Equal(t, []string{"32", "33"}, m.Get("int32").StringSlice())
assert.Equal(t, []string{"64", "65"}, m.Get("int64").StringSlice())
}

func TestStringSliceTypeUint(t *testing.T) {
m := objx.Map{
"uint": []uint{1, 2},
"uint8": []uint8{8, 9},
"uint16": []uint16{16, 17},
"uint32": []uint32{32, 33},
"uint64": []uint64{64, 65},
}

assert.Equal(t, []string{"1", "2"}, m.Get("uint").StringSlice())
assert.Equal(t, []string{"8", "9"}, m.Get("uint8").StringSlice())
assert.Equal(t, []string{"16", "17"}, m.Get("uint16").StringSlice())
assert.Equal(t, []string{"32", "33"}, m.Get("uint32").StringSlice())
assert.Equal(t, []string{"64", "65"}, m.Get("uint64").StringSlice())
}

func TestStringSliceTypeFloat(t *testing.T) {
m := objx.Map{
"float32": []float32{32.32, 33.33},
"float64": []float64{64.64, 65.65},
}

assert.Equal(t, []string{"32.32", "33.33"}, m.Get("float32").StringSlice())
assert.Equal(t, []string{"64.64", "65.65"}, m.Get("float64").StringSlice())
}

func TestStringSliceTypeOther(t *testing.T) {
m := objx.Map{
"other": "foo",
}

assert.Equal(t, []string{}, m.Get("other").StringSlice())
assert.Equal(t, []string{"bar"}, m.Get("other").StringSlice([]string{"bar"}))
}

0 comments on commit fb88214

Please sign in to comment.