diff --git a/formam.go b/formam.go index 93c6bfa..6909c06 100644 --- a/formam.go +++ b/formam.go @@ -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 + } } } @@ -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: @@ -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) @@ -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() diff --git a/formam_test.go b/formam_test.go index 66e7886..d48cef3 100644 --- a/formam_test.go +++ b/formam_test.go @@ -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 @@ -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"}, @@ -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 { @@ -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") + } +}