Commit fb882144 authored by earncef's avatar earncef Committed by Hanzei
Browse files

URLValues without sprinf, support for maps and slices (#63)

* URLValues without sprinf, support for maps and slices

* Fixeded import from earncef/objx

* Cleanup

* Cleanup

* Fixed tests
parent a97c7cc0
......@@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
)
......@@ -13,6 +12,12 @@ import (
// 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) {
......@@ -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{}
......
......@@ -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},
}
}
......@@ -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{}
}
......@@ -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"}))
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment