Skip to content

Commit 01d25e9

Browse files
committed
Handle from="" more properly. Fixes #192
1 parent dc808d1 commit 01d25e9

File tree

4 files changed

+95
-46
lines changed

4 files changed

+95
-46
lines changed

v5/merge.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@ func pruneDocNulls(doc *partialDoc) *partialDoc {
8888
func pruneAryNulls(ary *partialArray) *partialArray {
8989
newAry := []*lazyNode{}
9090

91-
for _, v := range *ary {
91+
for _, v := range ary.nodes {
9292
if v != nil {
9393
pruneNulls(v)
9494
}
9595
newAry = append(newAry, v)
9696
}
9797

98-
*ary = newAry
98+
ary.nodes = newAry
9999

100100
return ary
101101
}
@@ -151,15 +151,15 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
151151
}
152152
} else {
153153
patchAry := &partialArray{}
154-
patchErr = json.Unmarshal(patchData, patchAry)
154+
patchErr = json.Unmarshal(patchData, &patchAry.nodes)
155155

156156
if patchErr != nil {
157157
return nil, errBadJSONPatch
158158
}
159159

160160
pruneAryNulls(patchAry)
161161

162-
out, patchErr := json.Marshal(patchAry)
162+
out, patchErr := json.Marshal(patchAry.nodes)
163163

164164
if patchErr != nil {
165165
return nil, errBadJSONPatch

v5/merge_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func TestMergePatchNilArray(t *testing.T) {
7070
}
7171

7272
for _, c := range cases {
73+
t.Log(c.original)
7374
act := mergePatch(c.original, c.patch)
7475

7576
if !compareJSON(c.res, act) {

v5/patch.go

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ var (
4545
type lazyNode struct {
4646
raw *json.RawMessage
4747
doc *partialDoc
48-
ary partialArray
48+
ary *partialArray
4949
which int
5050
}
5151

@@ -56,11 +56,15 @@ type Operation map[string]*json.RawMessage
5656
type Patch []Operation
5757

5858
type partialDoc struct {
59+
self *lazyNode
5960
keys []string
6061
obj map[string]*lazyNode
6162
}
6263

63-
type partialArray []*lazyNode
64+
type partialArray struct {
65+
self *lazyNode
66+
nodes []*lazyNode
67+
}
6468

6569
type container interface {
6670
get(key string, options *ApplyOptions) (*lazyNode, error)
@@ -114,7 +118,7 @@ func (n *lazyNode) MarshalJSON() ([]byte, error) {
114118
case eDoc:
115119
return json.Marshal(n.doc)
116120
case eAry:
117-
return json.Marshal(n.ary)
121+
return json.Marshal(n.ary.nodes)
118122
default:
119123
return nil, ErrUnknownType
120124
}
@@ -199,6 +203,14 @@ func (n *partialDoc) UnmarshalJSON(data []byte) error {
199203
return nil
200204
}
201205

206+
func (n *partialArray) UnmarshalJSON(data []byte) error {
207+
return json.Unmarshal(data, &n.nodes)
208+
}
209+
210+
func (n *partialArray) MarshalJSON() ([]byte, error) {
211+
return json.Marshal(n.nodes)
212+
}
213+
202214
func skipValue(d *json.Decoder) error {
203215
t, err := d.Token()
204216
if err != nil {
@@ -264,7 +276,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) {
264276

265277
func (n *lazyNode) intoAry() (*partialArray, error) {
266278
if n.which == eAry {
267-
return &n.ary, nil
279+
return n.ary, nil
268280
}
269281

270282
if n.raw == nil {
@@ -278,7 +290,7 @@ func (n *lazyNode) intoAry() (*partialArray, error) {
278290
}
279291

280292
n.which = eAry
281-
return &n.ary, nil
293+
return n.ary, nil
282294
}
283295

284296
func (n *lazyNode) compact() []byte {
@@ -380,12 +392,12 @@ func (n *lazyNode) equal(o *lazyNode) bool {
380392
return false
381393
}
382394

383-
if len(n.ary) != len(o.ary) {
395+
if len(n.ary.nodes) != len(o.ary.nodes) {
384396
return false
385397
}
386398

387-
for idx, val := range n.ary {
388-
if !val.equal(o.ary[idx]) {
399+
for idx, val := range n.ary.nodes {
400+
if !val.equal(o.ary.nodes[idx]) {
389401
return false
390402
}
391403
}
@@ -497,6 +509,9 @@ func findObject(pd *container, path string, options *ApplyOptions) (container, s
497509
split := strings.Split(path, "/")
498510

499511
if len(split) < 2 {
512+
if path == "" {
513+
return doc, ""
514+
}
500515
return nil, ""
501516
}
502517

@@ -552,6 +567,9 @@ func (d *partialDoc) add(key string, val *lazyNode, options *ApplyOptions) error
552567
}
553568

554569
func (d *partialDoc) get(key string, options *ApplyOptions) (*lazyNode, error) {
570+
if key == "" {
571+
return d.self, nil
572+
}
555573
v, ok := d.obj[key]
556574
if !ok {
557575
return v, errors.Wrapf(ErrMissing, "unable to get nonexistent key: %s", key)
@@ -591,19 +609,19 @@ func (d *partialArray) set(key string, val *lazyNode, options *ApplyOptions) err
591609
if !options.SupportNegativeIndices {
592610
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
593611
}
594-
if idx < -len(*d) {
612+
if idx < -len(d.nodes) {
595613
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
596614
}
597-
idx += len(*d)
615+
idx += len(d.nodes)
598616
}
599617

600-
(*d)[idx] = val
618+
d.nodes[idx] = val
601619
return nil
602620
}
603621

604622
func (d *partialArray) add(key string, val *lazyNode, options *ApplyOptions) error {
605623
if key == "-" {
606-
*d = append(*d, val)
624+
d.nodes = append(d.nodes, val)
607625
return nil
608626
}
609627

@@ -612,11 +630,11 @@ func (d *partialArray) add(key string, val *lazyNode, options *ApplyOptions) err
612630
return errors.Wrapf(err, "value was not a proper array index: '%s'", key)
613631
}
614632

615-
sz := len(*d) + 1
633+
sz := len(d.nodes) + 1
616634

617635
ary := make([]*lazyNode, sz)
618636

619-
cur := *d
637+
cur := d
620638

621639
if idx >= len(ary) {
622640
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
@@ -632,15 +650,19 @@ func (d *partialArray) add(key string, val *lazyNode, options *ApplyOptions) err
632650
idx += len(ary)
633651
}
634652

635-
copy(ary[0:idx], cur[0:idx])
653+
copy(ary[0:idx], cur.nodes[0:idx])
636654
ary[idx] = val
637-
copy(ary[idx+1:], cur[idx:])
655+
copy(ary[idx+1:], cur.nodes[idx:])
638656

639-
*d = ary
657+
d.nodes = ary
640658
return nil
641659
}
642660

643661
func (d *partialArray) get(key string, options *ApplyOptions) (*lazyNode, error) {
662+
if key == "" {
663+
return d.self, nil
664+
}
665+
644666
idx, err := strconv.Atoi(key)
645667

646668
if err != nil {
@@ -651,17 +673,17 @@ func (d *partialArray) get(key string, options *ApplyOptions) (*lazyNode, error)
651673
if !options.SupportNegativeIndices {
652674
return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
653675
}
654-
if idx < -len(*d) {
676+
if idx < -len(d.nodes) {
655677
return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
656678
}
657-
idx += len(*d)
679+
idx += len(d.nodes)
658680
}
659681

660-
if idx >= len(*d) {
682+
if idx >= len(d.nodes) {
661683
return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
662684
}
663685

664-
return (*d)[idx], nil
686+
return d.nodes[idx], nil
665687
}
666688

667689
func (d *partialArray) remove(key string, options *ApplyOptions) error {
@@ -670,9 +692,9 @@ func (d *partialArray) remove(key string, options *ApplyOptions) error {
670692
return err
671693
}
672694

673-
cur := *d
695+
cur := d
674696

675-
if idx >= len(cur) {
697+
if idx >= len(cur.nodes) {
676698
if options.AllowMissingPathOnRemove {
677699
return nil
678700
}
@@ -683,21 +705,21 @@ func (d *partialArray) remove(key string, options *ApplyOptions) error {
683705
if !options.SupportNegativeIndices {
684706
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
685707
}
686-
if idx < -len(cur) {
708+
if idx < -len(cur.nodes) {
687709
if options.AllowMissingPathOnRemove {
688710
return nil
689711
}
690712
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
691713
}
692-
idx += len(cur)
714+
idx += len(cur.nodes)
693715
}
694716

695-
ary := make([]*lazyNode, len(cur)-1)
717+
ary := make([]*lazyNode, len(cur.nodes)-1)
696718

697-
copy(ary[0:idx], cur[0:idx])
698-
copy(ary[idx:], cur[idx+1:])
719+
copy(ary[0:idx], cur.nodes[0:idx])
720+
copy(ary[idx:], cur.nodes[idx+1:])
699721

700-
*d = ary
722+
d.nodes = ary
701723
return nil
702724
}
703725

@@ -762,9 +784,9 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
762784
if arrIndex, err = strconv.Atoi(part); err == nil {
763785
pa, ok := doc.(*partialArray)
764786

765-
if ok && arrIndex >= len(*pa)+1 {
787+
if ok && arrIndex >= len(pa.nodes)+1 {
766788
// Pad the array with null values up to the required index.
767-
for i := len(*pa); i <= arrIndex-1; i++ {
789+
for i := len(pa.nodes); i <= arrIndex-1; i++ {
768790
doc.add(strconv.Itoa(i), newLazyNode(newRawMessage(rawJSONNull)), options)
769791
}
770792
}
@@ -899,7 +921,7 @@ func (p Patch) replace(doc *container, op Operation, options *ApplyOptions) erro
899921

900922
switch val.which {
901923
case eAry:
902-
*doc = &val.ary
924+
*doc = val.ary
903925
case eDoc:
904926
*doc = val.doc
905927
case eRaw:
@@ -934,6 +956,10 @@ func (p Patch) move(doc *container, op Operation, options *ApplyOptions) error {
934956
return errors.Wrapf(err, "move operation failed to decode from")
935957
}
936958

959+
if from == "" {
960+
return errors.Wrapf(ErrInvalid, "unable to move entire document to another path")
961+
}
962+
937963
con, key := findObject(doc, from, options)
938964

939965
if con == nil {
@@ -983,7 +1009,7 @@ func (p Patch) test(doc *container, op Operation, options *ApplyOptions) error {
9831009
self.doc = sv
9841010
self.which = eDoc
9851011
case *partialArray:
986-
self.ary = *sv
1012+
self.ary = sv
9871013
self.which = eAry
9881014
}
9891015

@@ -1030,7 +1056,7 @@ func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64, op
10301056
con, key := findObject(doc, from, options)
10311057

10321058
if con == nil {
1033-
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing from path: %s", from)
1059+
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing from path: \"%s\"", from)
10341060
}
10351061

10361062
val, err := con.get(key, options)
@@ -1117,11 +1143,18 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
11171143
return doc, nil
11181144
}
11191145

1146+
raw := json.RawMessage(doc)
1147+
self := newLazyNode(&raw)
1148+
11201149
var pd container
11211150
if doc[0] == '[' {
1122-
pd = &partialArray{}
1151+
pd = &partialArray{
1152+
self: self,
1153+
}
11231154
} else {
1124-
pd = &partialDoc{}
1155+
pd = &partialDoc{
1156+
self: self,
1157+
}
11251158
}
11261159

11271160
err := json.Unmarshal(doc, pd)

v5/patch_test.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,20 @@ var Cases = []Case{
578578
false,
579579
false,
580580
},
581+
{
582+
`{"foo": 1}`,
583+
`[ { "op": "copy", "from": "", "path": "/bar"}]`,
584+
`{"foo": 1, "bar": {"foo": 1}}`,
585+
false,
586+
false,
587+
},
588+
{
589+
`[{"foo": 1}]`,
590+
`[ { "op": "copy", "from": "", "path": "/1"}]`,
591+
`[{"foo": 1}, [{"foo": 1}]]`,
592+
false,
593+
false,
594+
},
581595
}
582596

583597
type BadCase struct {
@@ -635,11 +649,6 @@ var BadCases = []BadCase{
635649
`[ { "op": "add", "pathz": "/baz", "value": "qux" } ]`,
636650
true,
637651
},
638-
{
639-
`{ "foo": "bar" }`,
640-
`[ { "op": "add", "path": "", "value": "qux" } ]`,
641-
false,
642-
},
643652
{
644653
`{ "foo": ["bar","baz"]}`,
645654
`[ { "op": "replace", "path": "/foo/2", "value": "bum"}]`,
@@ -727,6 +736,11 @@ var BadCases = []BadCase{
727736
`[{"op": "copy", "path": "/qux", "from": "/baz"}]`,
728737
false,
729738
},
739+
{
740+
`{ "foo": "bar"}`,
741+
`[{"op": "move", "path": "/qux", "from": ""}]`,
742+
false,
743+
},
730744
}
731745

732746
// This is not thread safe, so we cannot run patch tests in parallel.
@@ -802,9 +816,10 @@ func TestAllCases(t *testing.T) {
802816
}
803817

804818
if err == nil && !c.failOnDecode {
805-
_, err = p.Apply([]byte(c.doc))
819+
out, err := p.Apply([]byte(c.doc))
806820

807821
if err == nil {
822+
t.Log(string(out))
808823
t.Errorf("Patch %q should have failed to apply but it did not", c.patch)
809824
}
810825

0 commit comments

Comments
 (0)