Skip to content

Commit b4a5eb3

Browse files
committed
Refactor to provide common descent logic for each token type
1 parent 37a72c6 commit b4a5eb3

14 files changed

+344
-377
lines changed

patch/errs.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ func (e opMissingMapKeyErr) siblingKeysErrStr() string {
5151
}
5252

5353
type opMissingIndexErr struct {
54-
idx int
55-
obj []interface{}
54+
path Pointer
55+
idx int
56+
obj []interface{}
5657
}
5758

5859
func (e opMissingIndexErr) Error() string {
59-
return fmt.Sprintf("Expected to find array index '%d' but found array of length '%d'", e.idx, len(e.obj))
60+
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)
6061
}
6162

6263
type opMultipleMatchingIndexErr struct {

patch/find_op.go

+6-105
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package patch
22

3-
import (
4-
"fmt"
5-
)
6-
73
type FindOp struct {
84
Path Pointer
95
}
@@ -15,105 +11,10 @@ func (op FindOp) Apply(doc interface{}) (interface{}, error) {
1511
return doc, nil
1612
}
1713

18-
obj := doc
19-
20-
for i, token := range tokens[1:] {
21-
isLast := i == len(tokens)-2
22-
23-
switch typedToken := token.(type) {
24-
case IndexToken:
25-
idx := typedToken.Index
26-
27-
typedObj, ok := obj.([]interface{})
28-
if !ok {
29-
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
30-
}
31-
32-
if idx >= len(typedObj) {
33-
return nil, opMissingIndexErr{idx, typedObj}
34-
}
35-
36-
if isLast {
37-
return typedObj[idx], nil
38-
} else {
39-
obj = typedObj[idx]
40-
}
41-
42-
case AfterLastIndexToken:
43-
errMsg := "Expected not to find after last index token in path '%s' (not supported in find operations)"
44-
return nil, fmt.Errorf(errMsg, op.Path)
45-
46-
case MatchingIndexToken:
47-
typedObj, ok := obj.([]interface{})
48-
if !ok {
49-
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
50-
}
51-
52-
var idxs []int
53-
54-
for itemIdx, item := range typedObj {
55-
typedItem, ok := item.(map[interface{}]interface{})
56-
if ok {
57-
if typedItem[typedToken.Key] == typedToken.Value {
58-
idxs = append(idxs, itemIdx)
59-
}
60-
}
61-
}
62-
63-
if typedToken.Optional && len(idxs) == 0 {
64-
obj = map[interface{}]interface{}{typedToken.Key: typedToken.Value}
65-
66-
if isLast {
67-
return obj, nil
68-
}
69-
} else {
70-
if len(idxs) != 1 {
71-
return nil, opMultipleMatchingIndexErr{NewPointer(tokens[:i+2]), idxs}
72-
}
73-
74-
idx := idxs[0]
75-
76-
if isLast {
77-
return typedObj[idx], nil
78-
} else {
79-
obj = typedObj[idx]
80-
}
81-
}
82-
83-
case KeyToken:
84-
typedObj, ok := obj.(map[interface{}]interface{})
85-
if !ok {
86-
return nil, newOpMapMismatchTypeErr(tokens[:i+2], obj)
87-
}
88-
89-
var found bool
90-
91-
obj, found = typedObj[typedToken.Key]
92-
if !found && !typedToken.Optional {
93-
return nil, opMissingMapKeyErr{typedToken.Key, NewPointer(tokens[:i+2]), typedObj}
94-
}
95-
96-
if isLast {
97-
return typedObj[typedToken.Key], nil
98-
} else {
99-
if !found {
100-
// Determine what type of value to create based on next token
101-
switch tokens[i+2].(type) {
102-
case MatchingIndexToken:
103-
obj = []interface{}{}
104-
case KeyToken:
105-
obj = map[interface{}]interface{}{}
106-
default:
107-
errMsg := "Expected to find key or matching index token at path '%s'"
108-
return nil, fmt.Errorf(errMsg, NewPointer(tokens[:i+3]))
109-
}
110-
}
111-
}
112-
113-
default:
114-
return nil, opUnexpectedTokenErr{token, NewPointer(tokens[:i+2])}
115-
}
116-
}
117-
118-
return doc, nil
14+
return (&tokenContext{
15+
Tokens: tokens,
16+
TokenIndex: 0,
17+
Node: doc,
18+
Method: methodFind,
19+
}).Descend()
11920
}

patch/find_op_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ var _ = Describe("FindOp.Apply", func() {
6363
_, err := FindOp{Path: MustNewPointerFromString("/1")}.Apply([]interface{}{})
6464
Expect(err).To(HaveOccurred())
6565
Expect(err.Error()).To(Equal(
66-
"Expected to find array index '1' but found array of length '0'"))
66+
"Expected to find array index '1' but found array of length '0' for path '/1'"))
6767

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

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

321321
It("returns an error if it's not a map when key is being accessed", func() {

patch/pointer.go

+8-38
Original file line numberDiff line numberDiff line change
@@ -108,47 +108,17 @@ func (p Pointer) IsSet() bool { return len(p.tokens) > 0 }
108108
func (p Pointer) String() string {
109109
var strs []string
110110

111-
optional := false
112-
111+
seenOptional := false
113112
for _, token := range p.tokens {
114-
switch typedToken := token.(type) {
115-
case RootToken:
116-
strs = append(strs, "")
117-
118-
case IndexToken:
119-
strs = append(strs, fmt.Sprintf("%d", typedToken.Index))
120-
121-
case AfterLastIndexToken:
122-
strs = append(strs, "-")
123-
124-
case MatchingIndexToken:
125-
key := rfc6901Encoder.Replace(typedToken.Key)
126-
val := rfc6901Encoder.Replace(typedToken.Value)
127-
128-
if typedToken.Optional {
129-
if !optional {
130-
val += "?"
131-
optional = true
132-
}
113+
s := token.String()
114+
if strings.HasSuffix(s, "?") {
115+
if seenOptional {
116+
s = s[:len(s)-1]
117+
} else {
118+
seenOptional = true
133119
}
134-
135-
strs = append(strs, fmt.Sprintf("%s=%s", key, val))
136-
137-
case KeyToken:
138-
str := rfc6901Encoder.Replace(typedToken.Key)
139-
140-
if typedToken.Optional { // /key?/key2/key3
141-
if !optional {
142-
str += "?"
143-
optional = true
144-
}
145-
}
146-
147-
strs = append(strs, str)
148-
149-
default:
150-
panic(fmt.Sprintf("Unknown token type '%T'", typedToken))
151120
}
121+
strs = append(strs, s)
152122
}
153123

154124
return strings.Join(strs, "/")

patch/remove_op.go

+9-92
Original file line numberDiff line numberDiff line change
@@ -15,98 +15,15 @@ func (op RemoveOp) Apply(doc interface{}) (interface{}, error) {
1515
return nil, fmt.Errorf("Cannot remove entire document")
1616
}
1717

18-
obj := doc
19-
prevUpdate := func(newObj interface{}) { doc = newObj }
20-
21-
for i, token := range tokens[1:] {
22-
isLast := i == len(tokens)-2
23-
24-
switch typedToken := token.(type) {
25-
case IndexToken:
26-
idx := typedToken.Index
27-
28-
typedObj, ok := obj.([]interface{})
29-
if !ok {
30-
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
31-
}
32-
33-
if idx >= len(typedObj) {
34-
return nil, opMissingIndexErr{idx, typedObj}
35-
}
36-
37-
if isLast {
38-
var newAry []interface{}
39-
newAry = append(newAry, typedObj[:idx]...)
40-
newAry = append(newAry, typedObj[idx+1:]...)
41-
prevUpdate(newAry)
42-
} else {
43-
obj = typedObj[idx]
44-
prevUpdate = func(newObj interface{}) { typedObj[idx] = newObj }
45-
}
46-
47-
case MatchingIndexToken:
48-
typedObj, ok := obj.([]interface{})
49-
if !ok {
50-
return nil, newOpArrayMismatchTypeErr(tokens[:i+2], obj)
51-
}
52-
53-
var idxs []int
54-
55-
for itemIdx, item := range typedObj {
56-
typedItem, ok := item.(map[interface{}]interface{})
57-
if ok {
58-
if typedItem[typedToken.Key] == typedToken.Value {
59-
idxs = append(idxs, itemIdx)
60-
}
61-
}
62-
}
63-
64-
if typedToken.Optional && len(idxs) == 0 {
65-
return doc, nil
66-
}
67-
68-
if len(idxs) != 1 {
69-
return nil, opMultipleMatchingIndexErr{NewPointer(tokens[:i+2]), idxs}
70-
}
71-
72-
idx := idxs[0]
73-
74-
if isLast {
75-
var newAry []interface{}
76-
newAry = append(newAry, typedObj[:idx]...)
77-
newAry = append(newAry, typedObj[idx+1:]...)
78-
prevUpdate(newAry)
79-
} else {
80-
obj = typedObj[idx]
81-
// no need to change prevUpdate since matching item can only be a map
82-
}
83-
84-
case KeyToken:
85-
typedObj, ok := obj.(map[interface{}]interface{})
86-
if !ok {
87-
return nil, newOpMapMismatchTypeErr(tokens[:i+2], obj)
88-
}
89-
90-
var found bool
91-
92-
obj, found = typedObj[typedToken.Key]
93-
if !found {
94-
if typedToken.Optional {
95-
return doc, nil
96-
}
97-
98-
return nil, opMissingMapKeyErr{typedToken.Key, NewPointer(tokens[:i+2]), typedObj}
99-
}
100-
101-
if isLast {
102-
delete(typedObj, typedToken.Key)
103-
} else {
104-
prevUpdate = func(newObj interface{}) { typedObj[typedToken.Key] = newObj }
105-
}
106-
107-
default:
108-
return nil, opUnexpectedTokenErr{token, NewPointer(tokens[:i+2])}
109-
}
18+
_, err := (&tokenContext{
19+
Tokens: tokens,
20+
TokenIndex: 0,
21+
Node: doc,
22+
Setter: func(newObj interface{}) { doc = newObj },
23+
Method: methodRemove,
24+
}).Descend()
25+
if err != nil {
26+
return nil, err
11027
}
11128

11229
return doc, nil

patch/remove_op_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ var _ = Describe("RemoveOp.Apply", func() {
6464
_, err := RemoveOp{Path: MustNewPointerFromString("/1")}.Apply([]interface{}{})
6565
Expect(err).To(HaveOccurred())
6666
Expect(err.Error()).To(Equal(
67-
"Expected to find array index '1' but found array of length '0'"))
67+
"Expected to find array index '1' but found array of length '0' for path '/1'"))
6868

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

0 commit comments

Comments
 (0)