Skip to content

Commit 58f4977

Browse files
committed
Add prepend operator
1 parent dea88d2 commit 58f4977

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

Diff for: patch/pointer.go

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ func NewPointerFromString(str string) (Pointer, error) {
5252
continue
5353
}
5454

55+
// parse as before first index
56+
if isLast && tok == "+" {
57+
tokens = append(tokens, BeforeFirstIndexToken{})
58+
continue
59+
}
60+
5561
// parse wildcard
5662
if tok == "*" {
5763
tokens = append(tokens, WildcardToken{})

Diff for: patch/replace_op_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ var _ = Describe("ReplaceOp.Apply", func() {
132132
Expect(res).To(Equal([]interface{}{1, 2, 3, 10}))
133133
})
134134

135+
It("prepends new item", func() {
136+
res, err := ReplaceOp{Path: MustNewPointerFromString("/+"), Value: 10}.Apply([]interface{}{})
137+
Expect(err).ToNot(HaveOccurred())
138+
Expect(res).To(Equal([]interface{}{10}))
139+
140+
res, err = ReplaceOp{Path: MustNewPointerFromString("/+"), Value: 10}.Apply([]interface{}{1, 2, 3})
141+
Expect(err).ToNot(HaveOccurred())
142+
Expect(res).To(Equal([]interface{}{10, 1, 2, 3}))
143+
})
144+
135145
It("appends nested array item", func() {
136146
doc := []interface{}{[]interface{}{10, 11, 12}, 2, 3}
137147

@@ -140,6 +150,14 @@ var _ = Describe("ReplaceOp.Apply", func() {
140150
Expect(res).To(Equal([]interface{}{[]interface{}{10, 11, 12, 100}, 2, 3}))
141151
})
142152

153+
It("prepends nested array item", func() {
154+
doc := []interface{}{[]interface{}{10, 11, 12}, 2, 3}
155+
156+
res, err := ReplaceOp{Path: MustNewPointerFromString("/0/+"), Value: 100}.Apply(doc)
157+
Expect(err).ToNot(HaveOccurred())
158+
Expect(res).To(Equal([]interface{}{[]interface{}{100, 10, 11, 12}, 2, 3}))
159+
})
160+
143161
It("appends array item from an array that is inside a map", func() {
144162
doc := map[interface{}]interface{}{
145163
"abc": []interface{}{1, 2, 3},
@@ -153,6 +171,19 @@ var _ = Describe("ReplaceOp.Apply", func() {
153171
}))
154172
})
155173

174+
It("prepends array item from an array that is inside a map", func() {
175+
doc := map[interface{}]interface{}{
176+
"abc": []interface{}{1, 2, 3},
177+
}
178+
179+
res, err := ReplaceOp{Path: MustNewPointerFromString("/abc/+"), Value: 10}.Apply(doc)
180+
Expect(err).ToNot(HaveOccurred())
181+
182+
Expect(res).To(Equal(map[interface{}]interface{}{
183+
"abc": []interface{}{10, 1, 2, 3},
184+
}))
185+
})
186+
156187
It("returns an error if after last index token is not last", func() {
157188
ptr := NewPointer([]Token{RootToken{}, AfterLastIndexToken{}, KeyToken{}})
158189

@@ -162,6 +193,15 @@ var _ = Describe("ReplaceOp.Apply", func() {
162193
"Expected after last index token to be last in path '/-/'"))
163194
})
164195

196+
It("returns an error if before first index token is not last", func() {
197+
ptr := NewPointer([]Token{RootToken{}, BeforeFirstIndexToken{}, KeyToken{}})
198+
199+
_, err := ReplaceOp{Path: ptr}.Apply([]interface{}{})
200+
Expect(err).To(HaveOccurred())
201+
Expect(err.Error()).To(Equal(
202+
"Expected before first index token to be last in path '/+/'"))
203+
})
204+
165205
It("returns an error if it's not an array being accessed", func() {
166206
_, err := ReplaceOp{Path: MustNewPointerFromString("/-")}.Apply(map[interface{}]interface{}{})
167207
Expect(err).To(HaveOccurred())
@@ -175,6 +215,20 @@ var _ = Describe("ReplaceOp.Apply", func() {
175215
Expect(err.Error()).To(Equal(
176216
"Expected to find an array at path '/key/-' but found 'map[interface {}]interface {}'"))
177217
})
218+
219+
It("returns an error if it's not an array being accessed", func() {
220+
_, err := ReplaceOp{Path: MustNewPointerFromString("/+")}.Apply(map[interface{}]interface{}{})
221+
Expect(err).To(HaveOccurred())
222+
Expect(err.Error()).To(Equal(
223+
"Expected to find an array at path '/+' but found 'map[interface {}]interface {}'"))
224+
225+
doc := map[interface{}]interface{}{"key": map[interface{}]interface{}{}}
226+
227+
_, err = ReplaceOp{Path: MustNewPointerFromString("/key/+")}.Apply(doc)
228+
Expect(err).To(HaveOccurred())
229+
Expect(err.Error()).To(Equal(
230+
"Expected to find an array at path '/key/+' but found 'map[interface {}]interface {}'"))
231+
})
178232
})
179233

180234
Describe("array item with matching key and value", func() {
@@ -429,6 +483,18 @@ var _ = Describe("ReplaceOp.Apply", func() {
429483
}))
430484
})
431485

486+
It("creates missing key with array value for index access if key is not expected to exist", func() {
487+
doc := map[interface{}]interface{}{"xyz": "xyz"}
488+
489+
res, err := ReplaceOp{Path: MustNewPointerFromString("/abc?/+"), Value: 1}.Apply(doc)
490+
Expect(err).ToNot(HaveOccurred())
491+
492+
Expect(res).To(Equal(map[interface{}]interface{}{
493+
"abc": []interface{}{1},
494+
"xyz": "xyz",
495+
}))
496+
})
497+
432498
It("returns an error if missing key needs to be created but next access does not make sense", func() {
433499
doc := map[interface{}]interface{}{"xyz": "xyz"}
434500

Diff for: patch/token_prepend.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package patch
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
type BeforeFirstIndexToken struct{}
9+
10+
func (t BeforeFirstIndexToken) String() string {
11+
return "+"
12+
}
13+
14+
func (t BeforeFirstIndexToken) processDescent(ctx *tokenContext) (interface{}, error) {
15+
switch ctx.Method {
16+
case methodFind:
17+
errMsg := "Expected not to find before first token in path '%s' (not supported in find operations)"
18+
return nil, fmt.Errorf(errMsg, NewPointer(ctx.Tokens))
19+
20+
case methodReplace:
21+
typedObj, ok := ctx.Node.([]interface{})
22+
if !ok {
23+
return nil, newOpArrayMismatchTypeErr(ctx.Tokens[:ctx.TokenIndex+1], ctx.Node)
24+
}
25+
26+
if ctx.IsLast() {
27+
v, err := ctx.Value()
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
ctx.Setter(append([]interface{}{v}, typedObj...))
33+
return nil, nil
34+
}
35+
36+
return nil, fmt.Errorf("Expected before first index token to be last in path '%s'", Pointer{tokens: ctx.Tokens})
37+
38+
case methodRemove:
39+
return nil, opUnexpectedTokenErr{t, NewPointer(ctx.Tokens[:ctx.TokenIndex+1])}
40+
41+
default:
42+
return nil, errors.New("unsupported")
43+
}
44+
}
45+
46+
func (t BeforeFirstIndexToken) createEmptyValueForNext() (interface{}, error) {
47+
return []interface{}{}, nil
48+
}

0 commit comments

Comments
 (0)