blob: 755d8ba3b1bb67ef24dd142c4bbcd90f4ff00082 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001package jsonpatch
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "strconv"
8 "strings"
9)
10
11const (
12 eRaw = iota
13 eDoc
14 eAry
15)
16
17type lazyNode struct {
18 raw *json.RawMessage
19 doc partialDoc
20 ary partialArray
21 which int
22}
23
24type operation map[string]*json.RawMessage
25
26// Patch is an ordered collection of operations.
27type Patch []operation
28
29type partialDoc map[string]*lazyNode
30type partialArray []*lazyNode
31
32type container interface {
33 get(key string) (*lazyNode, error)
34 set(key string, val *lazyNode) error
35 add(key string, val *lazyNode) error
36 remove(key string) error
37}
38
39func newLazyNode(raw *json.RawMessage) *lazyNode {
40 return &lazyNode{raw: raw, doc: nil, ary: nil, which: eRaw}
41}
42
43func (n *lazyNode) MarshalJSON() ([]byte, error) {
44 switch n.which {
45 case eRaw:
46 return json.Marshal(n.raw)
47 case eDoc:
48 return json.Marshal(n.doc)
49 case eAry:
50 return json.Marshal(n.ary)
51 default:
52 return nil, fmt.Errorf("Unknown type")
53 }
54}
55
56func (n *lazyNode) UnmarshalJSON(data []byte) error {
57 dest := make(json.RawMessage, len(data))
58 copy(dest, data)
59 n.raw = &dest
60 n.which = eRaw
61 return nil
62}
63
64func (n *lazyNode) intoDoc() (*partialDoc, error) {
65 if n.which == eDoc {
66 return &n.doc, nil
67 }
68
69 if n.raw == nil {
70 return nil, fmt.Errorf("Unable to unmarshal nil pointer as partial document")
71 }
72
73 err := json.Unmarshal(*n.raw, &n.doc)
74
75 if err != nil {
76 return nil, err
77 }
78
79 n.which = eDoc
80 return &n.doc, nil
81}
82
83func (n *lazyNode) intoAry() (*partialArray, error) {
84 if n.which == eAry {
85 return &n.ary, nil
86 }
87
88 if n.raw == nil {
89 return nil, fmt.Errorf("Unable to unmarshal nil pointer as partial array")
90 }
91
92 err := json.Unmarshal(*n.raw, &n.ary)
93
94 if err != nil {
95 return nil, err
96 }
97
98 n.which = eAry
99 return &n.ary, nil
100}
101
102func (n *lazyNode) compact() []byte {
103 buf := &bytes.Buffer{}
104
105 if n.raw == nil {
106 return nil
107 }
108
109 err := json.Compact(buf, *n.raw)
110
111 if err != nil {
112 return *n.raw
113 }
114
115 return buf.Bytes()
116}
117
118func (n *lazyNode) tryDoc() bool {
119 if n.raw == nil {
120 return false
121 }
122
123 err := json.Unmarshal(*n.raw, &n.doc)
124
125 if err != nil {
126 return false
127 }
128
129 n.which = eDoc
130 return true
131}
132
133func (n *lazyNode) tryAry() bool {
134 if n.raw == nil {
135 return false
136 }
137
138 err := json.Unmarshal(*n.raw, &n.ary)
139
140 if err != nil {
141 return false
142 }
143
144 n.which = eAry
145 return true
146}
147
148func (n *lazyNode) equal(o *lazyNode) bool {
149 if n.which == eRaw {
150 if !n.tryDoc() && !n.tryAry() {
151 if o.which != eRaw {
152 return false
153 }
154
155 return bytes.Equal(n.compact(), o.compact())
156 }
157 }
158
159 if n.which == eDoc {
160 if o.which == eRaw {
161 if !o.tryDoc() {
162 return false
163 }
164 }
165
166 if o.which != eDoc {
167 return false
168 }
169
170 for k, v := range n.doc {
171 ov, ok := o.doc[k]
172
173 if !ok {
174 return false
175 }
176
177 if v == nil && ov == nil {
178 continue
179 }
180
181 if !v.equal(ov) {
182 return false
183 }
184 }
185
186 return true
187 }
188
189 if o.which != eAry && !o.tryAry() {
190 return false
191 }
192
193 if len(n.ary) != len(o.ary) {
194 return false
195 }
196
197 for idx, val := range n.ary {
198 if !val.equal(o.ary[idx]) {
199 return false
200 }
201 }
202
203 return true
204}
205
206func (o operation) kind() string {
207 if obj, ok := o["op"]; ok {
208 var op string
209
210 err := json.Unmarshal(*obj, &op)
211
212 if err != nil {
213 return "unknown"
214 }
215
216 return op
217 }
218
219 return "unknown"
220}
221
222func (o operation) path() string {
223 if obj, ok := o["path"]; ok {
224 var op string
225
226 err := json.Unmarshal(*obj, &op)
227
228 if err != nil {
229 return "unknown"
230 }
231
232 return op
233 }
234
235 return "unknown"
236}
237
238func (o operation) from() string {
239 if obj, ok := o["from"]; ok {
240 var op string
241
242 err := json.Unmarshal(*obj, &op)
243
244 if err != nil {
245 return "unknown"
246 }
247
248 return op
249 }
250
251 return "unknown"
252}
253
254func (o operation) value() *lazyNode {
255 if obj, ok := o["value"]; ok {
256 return newLazyNode(obj)
257 }
258
259 return nil
260}
261
262func isArray(buf []byte) bool {
263Loop:
264 for _, c := range buf {
265 switch c {
266 case ' ':
267 case '\n':
268 case '\t':
269 continue
270 case '[':
271 return true
272 default:
273 break Loop
274 }
275 }
276
277 return false
278}
279
280func findObject(pd *container, path string) (container, string) {
281 doc := *pd
282
283 split := strings.Split(path, "/")
284
285 if len(split) < 2 {
286 return nil, ""
287 }
288
289 parts := split[1 : len(split)-1]
290
291 key := split[len(split)-1]
292
293 var err error
294
295 for _, part := range parts {
296
297 next, ok := doc.get(decodePatchKey(part))
298
299 if next == nil || ok != nil {
300 return nil, ""
301 }
302
303 if isArray(*next.raw) {
304 doc, err = next.intoAry()
305
306 if err != nil {
307 return nil, ""
308 }
309 } else {
310 doc, err = next.intoDoc()
311
312 if err != nil {
313 return nil, ""
314 }
315 }
316 }
317
318 return doc, decodePatchKey(key)
319}
320
321func (d *partialDoc) set(key string, val *lazyNode) error {
322 (*d)[key] = val
323 return nil
324}
325
326func (d *partialDoc) add(key string, val *lazyNode) error {
327 (*d)[key] = val
328 return nil
329}
330
331func (d *partialDoc) get(key string) (*lazyNode, error) {
332 return (*d)[key], nil
333}
334
335func (d *partialDoc) remove(key string) error {
336 _, ok := (*d)[key]
337 if !ok {
338 return fmt.Errorf("Unable to remove nonexistent key: %s", key)
339 }
340
341 delete(*d, key)
342 return nil
343}
344
345func (d *partialArray) set(key string, val *lazyNode) error {
346 if key == "-" {
347 *d = append(*d, val)
348 return nil
349 }
350
351 idx, err := strconv.Atoi(key)
352 if err != nil {
353 return err
354 }
355
356 sz := len(*d)
357 if idx+1 > sz {
358 sz = idx + 1
359 }
360
361 ary := make([]*lazyNode, sz)
362
363 cur := *d
364
365 copy(ary, cur)
366
367 if idx >= len(ary) {
368 return fmt.Errorf("Unable to access invalid index: %d", idx)
369 }
370
371 ary[idx] = val
372
373 *d = ary
374 return nil
375}
376
377func (d *partialArray) add(key string, val *lazyNode) error {
378 if key == "-" {
379 *d = append(*d, val)
380 return nil
381 }
382
383 idx, err := strconv.Atoi(key)
384 if err != nil {
385 return err
386 }
387
388 ary := make([]*lazyNode, len(*d)+1)
389
390 cur := *d
391
392 if idx < 0 {
393 idx *= -1
394
395 if idx > len(ary) {
396 return fmt.Errorf("Unable to access invalid index: %d", idx)
397 }
398 idx = len(ary) - idx
399 }
400
401 copy(ary[0:idx], cur[0:idx])
402 ary[idx] = val
403 copy(ary[idx+1:], cur[idx:])
404
405 *d = ary
406 return nil
407}
408
409func (d *partialArray) get(key string) (*lazyNode, error) {
410 idx, err := strconv.Atoi(key)
411
412 if err != nil {
413 return nil, err
414 }
415
416 if idx >= len(*d) {
417 return nil, fmt.Errorf("Unable to access invalid index: %d", idx)
418 }
419
420 return (*d)[idx], nil
421}
422
423func (d *partialArray) remove(key string) error {
424 idx, err := strconv.Atoi(key)
425 if err != nil {
426 return err
427 }
428
429 cur := *d
430
431 if idx >= len(cur) {
432 return fmt.Errorf("Unable to remove invalid index: %d", idx)
433 }
434
435 ary := make([]*lazyNode, len(cur)-1)
436
437 copy(ary[0:idx], cur[0:idx])
438 copy(ary[idx:], cur[idx+1:])
439
440 *d = ary
441 return nil
442
443}
444
445func (p Patch) add(doc *container, op operation) error {
446 path := op.path()
447
448 con, key := findObject(doc, path)
449
450 if con == nil {
451 return fmt.Errorf("jsonpatch add operation does not apply: doc is missing path: %s", path)
452 }
453
454 return con.add(key, op.value())
455}
456
457func (p Patch) remove(doc *container, op operation) error {
458 path := op.path()
459
460 con, key := findObject(doc, path)
461
462 if con == nil {
463 return fmt.Errorf("jsonpatch remove operation does not apply: doc is missing path: %s", path)
464 }
465
466 return con.remove(key)
467}
468
469func (p Patch) replace(doc *container, op operation) error {
470 path := op.path()
471
472 con, key := findObject(doc, path)
473
474 if con == nil {
475 return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing path: %s", path)
476 }
477
478 val, ok := con.get(key)
479 if val == nil || ok != nil {
480 return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing key: %s", path)
481 }
482
483 return con.set(key, op.value())
484}
485
486func (p Patch) move(doc *container, op operation) error {
487 from := op.from()
488
489 con, key := findObject(doc, from)
490
491 if con == nil {
492 return fmt.Errorf("jsonpatch move operation does not apply: doc is missing from path: %s", from)
493 }
494
495 val, err := con.get(key)
496 if err != nil {
497 return err
498 }
499
500 err = con.remove(key)
501 if err != nil {
502 return err
503 }
504
505 path := op.path()
506
507 con, key = findObject(doc, path)
508
509 if con == nil {
510 return fmt.Errorf("jsonpatch move operation does not apply: doc is missing destination path: %s", path)
511 }
512
513 return con.set(key, val)
514}
515
516func (p Patch) test(doc *container, op operation) error {
517 path := op.path()
518
519 con, key := findObject(doc, path)
520
521 if con == nil {
522 return fmt.Errorf("jsonpatch test operation does not apply: is missing path: %s", path)
523 }
524
525 val, err := con.get(key)
526
527 if err != nil {
528 return err
529 }
530
531 if val == nil {
532 if op.value().raw == nil {
533 return nil
534 }
535 return fmt.Errorf("Testing value %s failed", path)
536 }
537
538 if val.equal(op.value()) {
539 return nil
540 }
541
542 return fmt.Errorf("Testing value %s failed", path)
543}
544
545func (p Patch) copy(doc *container, op operation) error {
546 from := op.from()
547
548 con, key := findObject(doc, from)
549
550 if con == nil {
551 return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing from path: %s", from)
552 }
553
554 val, err := con.get(key)
555 if err != nil {
556 return err
557 }
558
559 path := op.path()
560
561 con, key = findObject(doc, path)
562
563 if con == nil {
564 return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing destination path: %s", path)
565 }
566
567 return con.set(key, val)
568}
569
570// Equal indicates if 2 JSON documents have the same structural equality.
571func Equal(a, b []byte) bool {
572 ra := make(json.RawMessage, len(a))
573 copy(ra, a)
574 la := newLazyNode(&ra)
575
576 rb := make(json.RawMessage, len(b))
577 copy(rb, b)
578 lb := newLazyNode(&rb)
579
580 return la.equal(lb)
581}
582
583// DecodePatch decodes the passed JSON document as an RFC 6902 patch.
584func DecodePatch(buf []byte) (Patch, error) {
585 var p Patch
586
587 err := json.Unmarshal(buf, &p)
588
589 if err != nil {
590 return nil, err
591 }
592
593 return p, nil
594}
595
596// Apply mutates a JSON document according to the patch, and returns the new
597// document.
598func (p Patch) Apply(doc []byte) ([]byte, error) {
599 return p.ApplyIndent(doc, "")
600}
601
602// ApplyIndent mutates a JSON document according to the patch, and returns the new
603// document indented.
604func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
605 var pd container
606 if doc[0] == '[' {
607 pd = &partialArray{}
608 } else {
609 pd = &partialDoc{}
610 }
611
612 err := json.Unmarshal(doc, pd)
613
614 if err != nil {
615 return nil, err
616 }
617
618 err = nil
619
620 for _, op := range p {
621 switch op.kind() {
622 case "add":
623 err = p.add(&pd, op)
624 case "remove":
625 err = p.remove(&pd, op)
626 case "replace":
627 err = p.replace(&pd, op)
628 case "move":
629 err = p.move(&pd, op)
630 case "test":
631 err = p.test(&pd, op)
632 case "copy":
633 err = p.copy(&pd, op)
634 default:
635 err = fmt.Errorf("Unexpected kind: %s", op.kind())
636 }
637
638 if err != nil {
639 return nil, err
640 }
641 }
642
643 if indent != "" {
644 return json.MarshalIndent(pd, "", indent)
645 }
646
647 return json.Marshal(pd)
648}
649
650// From http://tools.ietf.org/html/rfc6901#section-4 :
651//
652// Evaluation of each reference token begins by decoding any escaped
653// character sequence. This is performed by first transforming any
654// occurrence of the sequence '~1' to '/', and then transforming any
655// occurrence of the sequence '~0' to '~'.
656
657var (
658 rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
659)
660
661func decodePatchKey(k string) string {
662 return rfc6901Decoder.Replace(k)
663}