Skip to content

Commit

Permalink
feat(SPV-793): replace QueryNestedMap with new function which handle …
Browse files Browse the repository at this point in the history
…whole query params and returns errors
  • Loading branch information
dorzepowski committed Aug 23, 2024
1 parent 60af61a commit c975260
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 76 deletions.
19 changes: 9 additions & 10 deletions internal/query/gin_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ package query

import "github.com/gin-gonic/gin"

// QueryNestedMap returns a map for a given query key.
//revive:disable:exported We want to mimic the gin API.

// ShouldGetQueryNestedMap returns a map from query params.
// In contrast to QueryMap it handles nesting in query maps like key[foo][bar]=value.
//
//revive:disable:exported We want to mimic the gin API
func QueryNestedMap(c *gin.Context, key string) (dicts map[string]interface{}) {
dicts, _ = GetQueryNestedMap(c, key)
return
func ShouldGetQueryNestedMap(c *gin.Context) (dicts map[string]interface{}, err error) {
return ShouldGetQueryNestedMapForKey(c, "")
}

// GetQueryNestedMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
// In contrast to GetQueryMap it handles nesting in query maps like key[foo][bar]=value.
func GetQueryNestedMap(c *gin.Context, key string) (map[string]interface{}, bool) {
// ShouldGetQueryNestedMapForKey returns a map from query params for a given query key.
// In contrast to QueryMap it handles nesting in query maps like key[foo][bar]=value.
// Similar to ShouldGetQueryNestedMap but it returns only the map for the given key.
func ShouldGetQueryNestedMapForKey(c *gin.Context, key string) (dicts map[string]interface{}, err error) {
q := c.Request.URL.Query()
return GetMap(q, key)
}
Expand Down
211 changes: 167 additions & 44 deletions internal/query/gin_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,103 +10,189 @@ import (
"github.com/stretchr/testify/require"
)

func TestContextQueryNestedMap(t *testing.T) {
func TestContextShouldGetQueryNestedMapSuccessfulParsing(t *testing.T) {
var emptyQueryMap map[string]interface{}

tests := map[string]struct {
url string
expectedResult map[string]interface{}
exists bool
}{
"no searched map key in query string": {
url: "?foo=bar",
"no query params": {
url: "",
expectedResult: emptyQueryMap,
exists: false,
},
"searched map key is not a map": {
url: "?mapkey=value",
expectedResult: emptyQueryMap,
exists: false,
"single query param": {
url: "?foo=bar",
expectedResult: map[string]interface{}{
"foo": "bar",
},
},
"searched map key is array": {
url: "?mapkey[]=value1&mapkey[]=value2",
expectedResult: emptyQueryMap,
exists: false,
"multiple query param": {
url: "?foo=bar&mapkey=value1",
expectedResult: map[string]interface{}{
"foo": "bar",
"mapkey": "value1",
},
},
"searched map key with invalid map access": {
url: "?mapkey[key]nested=value",
expectedResult: emptyQueryMap,
exists: false,
"map query param": {
url: "?mapkey[key]=value",
expectedResult: map[string]interface{}{
"mapkey": map[string]interface{}{
"key": "value",
},
},
},
"searched map key with valid and invalid map access": {
url: "?mapkey[key]invalidNested=value&mapkey[key][nested]=value1",
"nested map query param": {
url: "?mapkey[key][nested][moreNested]=value",
expectedResult: map[string]interface{}{
"key": map[string]interface{}{
"nested": "value1",
"mapkey": map[string]interface{}{
"key": map[string]interface{}{
"nested": map[string]interface{}{
"moreNested": "value",
},
},
},
},
exists: true,
},
"searched map key with valid before invalid map access": {
url: "?mapkey[key][nested]=value1&mapkey[key]invalidNested=value",
"map query param with explicit arrays accessors ([]) at the value level will return array": {
url: "?mapkey[key][]=value1&mapkey[key][]=value2",
expectedResult: map[string]interface{}{
"key": map[string]interface{}{
"nested": "value1",
"mapkey": map[string]interface{}{
"key": []string{"value1", "value2"},
},
},
},
"map query param with implicit arrays (duplicated key) at the value level will return only first value": {
url: "?mapkey[key]=value1&mapkey[key]=value2",
expectedResult: map[string]interface{}{
"mapkey": map[string]interface{}{
"key": "value1",
},
},
exists: true,
},
"array query param": {
url: "?mapkey[]=value1&mapkey[]=value2",
expectedResult: map[string]interface{}{
"mapkey": []string{"value1", "value2"},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
u, err := url.Parse(test.url)
require.NoError(t, err)

c := &gin.Context{
Request: &http.Request{
URL: u,
},
}

dicts, err := query.ShouldGetQueryNestedMap(c)
require.Equal(t, test.expectedResult, dicts)
require.NoError(t, err)
})
}
}

func TestContextShouldGetQueryNestedMapParsingError(t *testing.T) {
tests := map[string]struct {
url string
expectedResult map[string]interface{}
error string
}{
"searched map key with invalid map access": {
url: "?mapkey[key]nested=value",
error: "invalid access to map key",
},
"searched map key with array accessor in the middle": {
url: "?mapkey[key][][nested]=value",
error: "unsupported array-like access to map key",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
u, err := url.Parse(test.url)
require.NoError(t, err)

c := &gin.Context{
Request: &http.Request{
URL: u,
},
}

dicts, err := query.ShouldGetQueryNestedMap(c)
require.Nil(t, dicts)
require.ErrorContains(t, err, test.error)
})
}
}

func TestContextShouldGetQueryNestedForKeySuccessfulParsing(t *testing.T) {
var emptyQueryMap map[string]interface{}

tests := map[string]struct {
url string
key string
expectedResult map[string]interface{}
}{
"no searched map key in query string": {
url: "?foo=bar",
key: "mapkey",
expectedResult: emptyQueryMap,
},
"searched map key after other query params": {
url: "?foo=bar&mapkey[key]=value",
key: "mapkey",
expectedResult: map[string]interface{}{
"key": "value",
},
exists: true,
},
"searched map key before other query params": {
url: "?mapkey[key]=value&foo=bar",
key: "mapkey",
expectedResult: map[string]interface{}{
"key": "value",
},
exists: true,
},
"single key in searched map key": {
url: "?mapkey[key]=value",
key: "mapkey",
expectedResult: map[string]interface{}{
"key": "value",
},
exists: true,
},
"multiple keys in searched map key": {
url: "?mapkey[key1]=value1&mapkey[key2]=value2&mapkey[key3]=value3",
key: "mapkey",
expectedResult: map[string]interface{}{
"key1": "value1",
"key2": "value2",
"key3": "value3",
},
exists: true,
},
"nested key in searched map key": {
url: "?mapkey[foo][nested]=value1",
key: "mapkey",
expectedResult: map[string]interface{}{
"foo": map[string]interface{}{
"nested": "value1",
},
},
exists: true,
},
"multiple nested keys in single key of searched map key": {
url: "?mapkey[foo][nested1]=value1&mapkey[foo][nested2]=value2",
key: "mapkey",
expectedResult: map[string]interface{}{
"foo": map[string]interface{}{
"nested1": "value1",
"nested2": "value2",
},
},
exists: true,
},
"multiple keys with nested keys of searched map key": {
url: "?mapkey[key1][nested]=value1&mapkey[key2][nested]=value2",
key: "mapkey",
expectedResult: map[string]interface{}{
"key1": map[string]interface{}{
"nested": "value1",
Expand All @@ -115,43 +201,42 @@ func TestContextQueryNestedMap(t *testing.T) {
"nested": "value2",
},
},
exists: true,
},
"multiple levels of nesting in searched map key": {
url: "?mapkey[key][nested][moreNested]=value1",
key: "mapkey",
expectedResult: map[string]interface{}{
"key": map[string]interface{}{
"nested": map[string]interface{}{
"moreNested": "value1",
},
},
},
exists: true,
},
"query keys similar to searched map key": {
url: "?mapkey[key]=value&mapkeys[key1]=value1&mapkey1=foo",
key: "mapkey",
expectedResult: map[string]interface{}{
"key": "value",
},
exists: true,
},
"handle explicit arrays accessors ([]) at the value level": {
url: "?mapkey[key][]=value1&mapkey[key][]=value2",
key: "mapkey",
expectedResult: map[string]interface{}{
"key": []string{"value1", "value2"},
},
exists: true,
},
"implicit arrays (duplicated key) at the value level will return only first value": {
url: "?mapkey[key]=value1&mapkey[key]=value2",
key: "mapkey",
expectedResult: map[string]interface{}{
"key": "value1",
},
exists: true,
},
}
for name, test := range tests {
t.Run("getQueryMap: "+name, func(t *testing.T) {
t.Run(name, func(t *testing.T) {
u, err := url.Parse(test.url)
require.NoError(t, err)

Expand All @@ -161,11 +246,48 @@ func TestContextQueryNestedMap(t *testing.T) {
},
}

dicts, exists := query.GetQueryNestedMap(c, "mapkey")
dicts, err := query.ShouldGetQueryNestedMapForKey(c, test.key)
require.Equal(t, test.expectedResult, dicts)
require.Equal(t, test.exists, exists)
require.NoError(t, err)
})
t.Run("queryMap: "+name, func(t *testing.T) {
}
}

func TestContextShouldGetQueryNestedForKeyParsingError(t *testing.T) {
tests := map[string]struct {
url string
key string
error string
}{

"searched map key is value not a map": {
url: "?mapkey=value",
key: "mapkey",
error: "invalid access to map",
},
"searched map key is array": {
url: "?mapkey[]=value1&mapkey[]=value2",
key: "mapkey",
error: "invalid access to map",
},
"searched map key with invalid map access": {
url: "?mapkey[key]nested=value",
key: "mapkey",
error: "invalid access to map key",
},
"searched map key with valid and invalid map access": {
url: "?mapkey[key]invalidNested=value&mapkey[key][nested]=value1",
key: "mapkey",
error: "invalid access to map key",
},
"searched map key with valid before invalid map access": {
url: "?mapkey[key][nested]=value1&mapkey[key]invalidNested=value",
key: "mapkey",
error: "invalid access to map key",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
u, err := url.Parse(test.url)
require.NoError(t, err)

Expand All @@ -175,8 +297,9 @@ func TestContextQueryNestedMap(t *testing.T) {
},
}

dicts := query.QueryNestedMap(c, "mapkey")
require.Equal(t, test.expectedResult, dicts)
dicts, err := query.ShouldGetQueryNestedMapForKey(c, test.key)
require.Nil(t, dicts)
require.ErrorContains(t, err, test.error)
})
}
}
Loading

0 comments on commit c975260

Please sign in to comment.