Skip to content
Open
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
7 changes: 4 additions & 3 deletions patch/errs.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ func (e opMissingMapKeyErr) siblingKeysErrStr() string {
}

type opMissingIndexErr struct {
idx int
obj []interface{}
path Pointer
idx int
obj []interface{}
}

func (e opMissingIndexErr) Error() string {
return fmt.Sprintf("Expected to find array index '%d' but found array of length '%d'", e.idx, len(e.obj))
return fmt.Sprintf("Expected to find array index '%d' but found array of length '%d' for path '%s'", e.idx, len(e.obj), e.path)
}

type opMultipleMatchingIndexErr struct {
Expand Down
111 changes: 6 additions & 105 deletions patch/find_op.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package patch

import (
"fmt"
)

type FindOp struct {
Path Pointer
}
Expand All @@ -15,105 +11,10 @@ func (op FindOp) Apply(doc interface{}) (interface{}, error) {
return doc, nil
}

obj := doc

for i, token := range tokens[1:] {
isLast := i == len(tokens)-2

switch typedToken := token.(type) {
case IndexToken:
idx := typedToken.Index

typedObj, ok := obj.([]interface{})
if !ok {
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
}

if idx >= len(typedObj) {
return nil, opMissingIndexErr{idx, typedObj}
}

if isLast {
return typedObj[idx], nil
} else {
obj = typedObj[idx]
}

case AfterLastIndexToken:
errMsg := "Expected not to find after last index token in path '%s' (not supported in find operations)"
return nil, fmt.Errorf(errMsg, op.Path)

case MatchingIndexToken:
typedObj, ok := obj.([]interface{})
if !ok {
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
}

var idxs []int

for itemIdx, item := range typedObj {
typedItem, ok := item.(map[interface{}]interface{})
if ok {
if typedItem[typedToken.Key] == typedToken.Value {
idxs = append(idxs, itemIdx)
}
}
}

if typedToken.Optional && len(idxs) == 0 {
obj = map[interface{}]interface{}{typedToken.Key: typedToken.Value}

if isLast {
return obj, nil
}
} else {
if len(idxs) != 1 {
return nil, opMultipleMatchingIndexErr{NewPointer(tokens[:i+2]), idxs}
}

idx := idxs[0]

if isLast {
return typedObj[idx], nil
} else {
obj = typedObj[idx]
}
}

case KeyToken:
typedObj, ok := obj.(map[interface{}]interface{})
if !ok {
return nil, newOpMapMismatchTypeErr(tokens[:i+2], obj)
}

var found bool

obj, found = typedObj[typedToken.Key]
if !found && !typedToken.Optional {
return nil, opMissingMapKeyErr{typedToken.Key, NewPointer(tokens[:i+2]), typedObj}
}

if isLast {
return typedObj[typedToken.Key], nil
} else {
if !found {
// Determine what type of value to create based on next token
switch tokens[i+2].(type) {
case MatchingIndexToken:
obj = []interface{}{}
case KeyToken:
obj = map[interface{}]interface{}{}
default:
errMsg := "Expected to find key or matching index token at path '%s'"
return nil, fmt.Errorf(errMsg, NewPointer(tokens[:i+3]))
}
}
}

default:
return nil, opUnexpectedTokenErr{token, NewPointer(tokens[:i+2])}
}
}

return doc, nil
return (&tokenContext{
Tokens: tokens,
TokenIndex: 0,
Node: doc,
Method: methodFind,
}).Descend()
}
6 changes: 3 additions & 3 deletions patch/find_op_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ var _ = Describe("FindOp.Apply", func() {
_, err := FindOp{Path: MustNewPointerFromString("/1")}.Apply([]interface{}{})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(
"Expected to find array index '1' but found array of length '0'"))
"Expected to find array index '1' but found array of length '0' for path '/1'"))

_, err = FindOp{Path: MustNewPointerFromString("/1/1")}.Apply([]interface{}{})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(
"Expected to find array index '1' but found array of length '0'"))
"Expected to find array index '1' but found array of length '0' for path '/1'"))
})
})

Expand Down Expand Up @@ -315,7 +315,7 @@ var _ = Describe("FindOp.Apply", func() {
_, err := FindOp{Path: MustNewPointerFromString("/abc?/0")}.Apply(doc)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(
"Expected to find key or matching index token at path '/abc?/0'"))
"Expected to find array index '0' but found array of length '0' for path '/abc?/0'"))
})

It("returns an error if it's not a map when key is being accessed", func() {
Expand Down
58 changes: 20 additions & 38 deletions patch/pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ func NewPointerFromString(str string) (Pointer, error) {
continue
}

// parse as before first index
if isLast && tok == "+" {
tokens = append(tokens, BeforeFirstIndexToken{})
continue
}

// parse wildcard
if tok == "*" {
tokens = append(tokens, WildcardToken{})
continue
}

// parse as index
idx, err := strconv.Atoi(tok)
if err == nil {
Expand Down Expand Up @@ -108,47 +120,17 @@ func (p Pointer) IsSet() bool { return len(p.tokens) > 0 }
func (p Pointer) String() string {
var strs []string

optional := false

seenOptional := false
for _, token := range p.tokens {
switch typedToken := token.(type) {
case RootToken:
strs = append(strs, "")

case IndexToken:
strs = append(strs, fmt.Sprintf("%d", typedToken.Index))

case AfterLastIndexToken:
strs = append(strs, "-")

case MatchingIndexToken:
key := rfc6901Encoder.Replace(typedToken.Key)
val := rfc6901Encoder.Replace(typedToken.Value)

if typedToken.Optional {
if !optional {
val += "?"
optional = true
}
s := token.String()
if strings.HasSuffix(s, "?") {
if seenOptional {
s = s[:len(s)-1]
} else {
seenOptional = true
}

strs = append(strs, fmt.Sprintf("%s=%s", key, val))

case KeyToken:
str := rfc6901Encoder.Replace(typedToken.Key)

if typedToken.Optional { // /key?/key2/key3
if !optional {
str += "?"
optional = true
}
}

strs = append(strs, str)

default:
panic(fmt.Sprintf("Unknown token type '%T'", typedToken))
}
strs = append(strs, s)
}

return strings.Join(strs, "/")
Expand Down
101 changes: 9 additions & 92 deletions patch/remove_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,98 +15,15 @@ func (op RemoveOp) Apply(doc interface{}) (interface{}, error) {
return nil, fmt.Errorf("Cannot remove entire document")
}

obj := doc
prevUpdate := func(newObj interface{}) { doc = newObj }

for i, token := range tokens[1:] {
isLast := i == len(tokens)-2

switch typedToken := token.(type) {
case IndexToken:
idx := typedToken.Index

typedObj, ok := obj.([]interface{})
if !ok {
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
}

if idx >= len(typedObj) {
return nil, opMissingIndexErr{idx, typedObj}
}

if isLast {
var newAry []interface{}
newAry = append(newAry, typedObj[:idx]...)
newAry = append(newAry, typedObj[idx+1:]...)
prevUpdate(newAry)
} else {
obj = typedObj[idx]
prevUpdate = func(newObj interface{}) { typedObj[idx] = newObj }
}

case MatchingIndexToken:
typedObj, ok := obj.([]interface{})
if !ok {
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
}

var idxs []int

for itemIdx, item := range typedObj {
typedItem, ok := item.(map[interface{}]interface{})
if ok {
if typedItem[typedToken.Key] == typedToken.Value {
idxs = append(idxs, itemIdx)
}
}
}

if typedToken.Optional && len(idxs) == 0 {
return doc, nil
}

if len(idxs) != 1 {
return nil, opMultipleMatchingIndexErr{NewPointer(tokens[:i+2]), idxs}
}

idx := idxs[0]

if isLast {
var newAry []interface{}
newAry = append(newAry, typedObj[:idx]...)
newAry = append(newAry, typedObj[idx+1:]...)
prevUpdate(newAry)
} else {
obj = typedObj[idx]
// no need to change prevUpdate since matching item can only be a map
}

case KeyToken:
typedObj, ok := obj.(map[interface{}]interface{})
if !ok {
return nil, newOpMapMismatchTypeErr(tokens[:i+2], obj)
}

var found bool

obj, found = typedObj[typedToken.Key]
if !found {
if typedToken.Optional {
return doc, nil
}

return nil, opMissingMapKeyErr{typedToken.Key, NewPointer(tokens[:i+2]), typedObj}
}

if isLast {
delete(typedObj, typedToken.Key)
} else {
prevUpdate = func(newObj interface{}) { typedObj[typedToken.Key] = newObj }
}

default:
return nil, opUnexpectedTokenErr{token, NewPointer(tokens[:i+2])}
}
_, err := (&tokenContext{
Tokens: tokens,
TokenIndex: 0,
Node: doc,
Setter: func(newObj interface{}) { doc = newObj },
Method: methodRemove,
}).Descend()
if err != nil {
return nil, err
}

return doc, nil
Expand Down
4 changes: 2 additions & 2 deletions patch/remove_op_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ var _ = Describe("RemoveOp.Apply", func() {
_, err := RemoveOp{Path: MustNewPointerFromString("/1")}.Apply([]interface{}{})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(
"Expected to find array index '1' but found array of length '0'"))
"Expected to find array index '1' but found array of length '0' for path '/1'"))

_, err = RemoveOp{Path: MustNewPointerFromString("/1/1")}.Apply([]interface{}{})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(
"Expected to find array index '1' but found array of length '0'"))
"Expected to find array index '1' but found array of length '0' for path '/1'"))
})
})

Expand Down
Loading