Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 90 additions & 64 deletions formam.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,65 +210,71 @@ func (dec Decoder) init() error {
// analyzePath analyzes the current path to walk through it.
// For example: users[0].name
func (dec *Decoder) analyzePath() (err error) {
inBracket := false
bracketClosed := false
traversedByBracket := false
nesting := 0
lastPos := 0
endPos := 0

// parse path
for i, char := range []byte(dec.path) {
if char == '[' && inBracket == false {
// found an opening bracket
bracketClosed = false
inBracket = true
dec.field = dec.path[lastPos:i]
lastPos = i + 1
continue
} else if inBracket {
// it is inside of bracket, so get its value
if char == ']' {
// found an closing bracket, so it will be recently close, so put as true the bracketClosed
// and put as false inBracket and pass the value of bracket to dec.key
inBracket = false
bracketClosed = true
if endPos == 0 { // foo[] without number.
dec.index = dec.path[lastPos:i]
} else {
dec.index = dec.path[lastPos:endPos]
}
lastPos = i + 1
// traverse the path
err = dec.traverse()
// flush the index already used by traverse
dec.index = ""
// check if the "traverse" failed
if err != nil {
return
}
} else {
// still inside the bracket, so to save the end position
endPos = i + 1
}
continue
} else if !inBracket {
// not found any bracket, so try found a field
if char == '.' {
// found a field, we need to know if the field is next of a closing bracket,
// for example: [0].Field
if bracketClosed {
bracketClosed = false
lastPos = i + 1
continue
}
// found a field, but is not next of a closing bracket, for example: Field1.Field2
switch char {
case '[':
// save current access field
if nesting == 0 {
traversedByBracket = false
dec.field = dec.path[lastPos:i]
//dec.field = tmp[:i]
lastPos = i + 1
if err = dec.traverse(); err != nil {
return
}
}
continue
nesting += 1

case ']':
// no matching open bracket - regular character
if nesting == 0 {
continue
}

// decrease nesting
nesting -= 1
// if still inside outer brackets - regular character
// for example: [nested[brackets]]
if nesting > 0 {
continue
}

traversedByBracket = true
dec.index = dec.path[lastPos:i]
lastPos = i + 1
// traverse the path
err = dec.traverse()
// flush the index already used by traverse
dec.index = ""
// check if the "traverse" failed
if err != nil {
return
}

case '.':
// inside brackets - regular character
// for example: [key.with.dots]
if nesting > 0 {
continue
}

// found a field, we need to know if the field is next to a closing bracket,
// if it is then no need to traverse again
// for example: [0].Field
if traversedByBracket {
traversedByBracket = false
lastPos = i + 1
continue
}

// found a field, but is not next to a closing bracket,
// for example: Field1.Field2
dec.field = dec.path[lastPos:i]
lastPos = i + 1
if err = dec.traverse(); err != nil {
return
}
}
}

Expand All @@ -284,6 +290,8 @@ func (dec *Decoder) analyzePath() (err error) {
func (dec *Decoder) traverse() error {
// check if there is field, if is so, then it should be struct or map (access by .)
if dec.field != "" {
dec.traverseIndirect()

// check if is a struct or map
switch dec.curr.Kind() {
case reflect.Struct:
Expand All @@ -296,26 +304,21 @@ func (dec *Decoder) traverse() error {
}
dec.field = ""
}
// check if is a interface and it is not nil. This mean that the interface
// has a struct, map or slice as value
if dec.curr.Kind() == reflect.Interface && !dec.curr.IsNil() {
dec.curr = dec.curr.Elem()
}
// check if it is a pointer
if dec.curr.Kind() == reflect.Ptr {
if dec.curr.IsNil() {
dec.curr.Set(reflect.New(dec.curr.Type().Elem()))
}
dec.curr = dec.curr.Elem()
}

// check if there is access to slice/array or map (access by [])
if dec.index != "" {
dec.traverseIndirect()

switch dec.curr.Kind() {
case reflect.Array:
index, err := strconv.Atoi(dec.index)
if err != nil {
return newError(ErrCodeArrayIndex, dec.field, dec.path, "array index is not a number: %s", err)
}
if dec.curr.Len() <= index {
return newError(ErrCodeArrayIndex, dec.field, dec.path, "array index is out of bounds")
}

dec.curr = dec.curr.Index(index)
case reflect.Slice:
index, err := strconv.Atoi(dec.index)
Expand All @@ -339,6 +342,29 @@ func (dec *Decoder) traverse() error {
return nil
}

func (dec *Decoder) traverseIndirect() {
loop:
// nested indirections
for {
switch {
// check if is a interface and it is not nil. This mean that the interface
// has a struct, map or slice as value
case dec.curr.Kind() == reflect.Interface && !dec.curr.IsNil():
dec.curr = dec.curr.Elem()

// check if it is a pointer
case dec.curr.Kind() == reflect.Ptr:
if dec.curr.IsNil() {
dec.curr.Set(reflect.New(dec.curr.Type().Elem()))
}
dec.curr = dec.curr.Elem()

default:
break loop
}
}
}

// walkMap puts in Decoder.curr the map concrete for decode the current value
func (dec *Decoder) traverseInMap(byField bool) {
n := dec.curr.Type()
Expand Down
85 changes: 85 additions & 0 deletions formam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ type TestStruct struct {

// pointer
Pointer *string
// pointer to pointer
PointerToPointer **string
// pointer to struct
PointerToStruct *struct{ Field float64 }
// pointer to map
Expand Down Expand Up @@ -228,6 +230,7 @@ var vals = url.Values{

// pointer
"Pointer": []string{"20"},
"PointerToPointer": []string{"20"},
"PointerToStruct.Field": []string{"20"},
"PointerToMap[es]": []string{"20"},
"PointerToSlice[0].ID": []string{"20"},
Expand Down Expand Up @@ -468,6 +471,18 @@ func TestDecodeInStruct(t *testing.T) {
} else if *m.Pointer == "" {
t.Error("Pointer is not nil but is empty")
}
if m.PointerToPointer == nil {
t.Error("PointerToPointer is nil")
} else if *m.PointerToPointer == nil {
t.Error("PointerToPointer is not nil but nested pointer is nil")
} else if **m.PointerToPointer == "" {
t.Error("PointerToPointer and nested pointer is not nil but is empty")
}
if m.PointerToStruct == nil {
t.Error("PointerToStruct is nil")
} else if m.PointerToStruct.Field == 0.0 {
t.Error("PointerToStruct is not nil but is empty")
}
if m.PointerToMap == nil {
t.Error("Pointer is nil")
} else if len(*m.PointerToMap) == 0 {
Expand Down Expand Up @@ -1026,3 +1041,73 @@ func errorContains(out error, want string) bool {
}
return strings.Contains(out.Error(), want)
}

func TestUnbalancedBracketsAndNestedPointers(t *testing.T) {
var s struct {
MapStringString map[string]string `formam:"MapStringString"`
MapStringPtrStruct map[string]*struct {
ID string `formam:"ID"`
PtrStruct *struct {
String string `formam:"String"`
} `formam:"PtrStruct"`
} `formam:"MapStringPtrStruct"`
MapStringMapStringString map[string]map[string]string `formam:"MapStringMapStringString"`
}

vals := url.Values{
"MapStringString[a[b][c]d]": []string{"MapStringString[a[b][c]d]"},
"MapStringString[name.with.dots]": []string{"MapStringString[name.with.dots]"},
"MapStringPtrStruct[key].PtrStruct.String": []string{"MapStringPtrStruct[key].PtrStruct.String"},
"MapStringPtrStruct[k1].ID": []string{"MapStringPtrStruct[k1].ID"},
"MapStringPtrStruct[k2]ID": []string{"MapStringPtrStruct[k2]ID"},
"MapStringMapStringString[a[b[c]d]]q]w": []string{"MapStringMapStringString[a[b[c]d]]q]w"},
}

dec := formam.NewDecoder(nil)

if err := dec.Decode(vals, &s); err != nil {
t.Fatalf("error when decode %s", err)
}

if v, ok := s.MapStringString["a[b][c]d"]; !ok {
t.Error("The key \"a[b][c]d\" in MapStringString does not exists")
} else if v != "MapStringString[a[b][c]d]" {
t.Error("The value in key \"a[b][c]d\" of MapStringString is incorrect")
}

if v, ok := s.MapStringString["name.with.dots"]; !ok {
t.Error("The key \"name.with.dots\" in MapStringString does not exists")
} else if v != "MapStringString[name.with.dots]" {
t.Error("The value in key \"name.with.dots\" of MapStringString is incorrect")
}

if v, ok := s.MapStringPtrStruct["key"]; !ok {
t.Error("The key \"key\" in MapStringPtrStruct does not exists")
} else if v == nil {
t.Error("MapStringPtrStruct[key] is nil")
} else if v.PtrStruct == nil {
t.Error("MapStringPtrStruct[key].PtrStruct is nil")
} else if v.PtrStruct.String != "MapStringPtrStruct[key].PtrStruct.String" {
t.Error("The value of \"MapStringPtrStruct[key].PtrStruct.String\" is incorrect")
}

if v, ok := s.MapStringPtrStruct["k2"]; !ok {
t.Error("The key \"k2\" in MapStringPtrStruct does not exists")
} else if v.ID != "MapStringPtrStruct[k2]ID" {
t.Error("The value in key \"k2\" of MapStringPtrStruct is incorrect")
}

if v, ok := s.MapStringPtrStruct["k2"]; !ok {
t.Error("The key \"k2\" in MapStringPtrStruct does not exists")
} else if v.ID != "MapStringPtrStruct[k2]ID" {
t.Error("The value in key \"k2\" of MapStringPtrStruct is incorrect")
}

if v, ok := s.MapStringMapStringString["a[b[c]d]"]; !ok {
t.Error("The key \"a[b[c]d]\" in MapStringMapStringString does not exists")
} else if vv, ok := v["q]w"]; !ok {
t.Error("The key \"q]w\" in MapStringMapStringString[a[b[c]d]] does not exists")
} else if vv != "MapStringMapStringString[a[b[c]d]]q]w" {
t.Error("The value in key \"q]w\" of MapStringMapStringString[a[b[c]d]] is incorrect")
}
}