forked from cameront/go-jsonpatch
/
operation.go
245 lines (227 loc) · 5.88 KB
/
operation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package jsonpatch
import (
//"encoding/json"
//"github.com/bitly/go-simplejson"
"fmt"
ptr "github.com/xeipuuv/gojsonpointer"
"reflect"
"strconv"
"strings"
)
// Operation is a...
type OperationType string
// All available operations.
const (
Remove OperationType = "remove"
Add OperationType = "add"
Replace OperationType = "replace"
Move OperationType = "move"
Test OperationType = "test"
Copy OperationType = "copy"
)
type PatchOperation struct {
From string `json:"from,omitempty"`
Op OperationType `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
func lastObj(path string) (ptr.JsonPointer, string, error) {
lastSep := strings.LastIndex(path, "/")
parentPath := path[0:lastSep]
lastToken := path[lastSep+1:] // Skip "/"
parentPtr, err := ptr.NewJsonPointer(parentPath)
return parentPtr, lastToken, err
}
func getValue(path string, doc interface{}) (*ptr.JsonPointer, reflect.Kind, interface{}, error) {
ptr, err := ptr.NewJsonPointer(path)
if err != nil {
return nil, reflect.Invalid, nil, err
}
val, kind, err := ptr.Get(doc)
if err != nil {
return nil, reflect.Invalid, nil, err
}
return &ptr, kind, val, nil
}
func getDirect(ptr interface{}) (interface{}, error) {
indirect := reflect.Indirect(reflect.ValueOf(ptr))
if !indirect.IsValid() {
return nil, fmt.Errorf("Can only apply to pointers.")
}
return indirect.Interface(), nil
}
func (self *PatchOperation) Apply(doc interface{}) error {
var mod map[string]interface{}
direct, err := getDirect(doc)
if err != nil {
return err
}
// This is ugly and hacky, but because the version of jsonpointer we're using
// doesn't replace the entire document for empty paths, we nest it one level
// deep and prefix the from/path with our own key.
switch t := direct.(type) {
case map[string]interface{}:
mod = map[string]interface{}{"jp": t}
case []interface{}:
mod = map[string]interface{}{"jp": t}
default:
return fmt.Errorf("Can only apply to pointers to maps and arrays of interfaces.")
}
path := "/jp" + self.Path
from := "/jp" + self.From
switch self.Op {
case Add:
err = add(path, self.Value, mod)
case Copy:
err = copyOp(path, from, mod)
case Move:
err = move(path, from, self.Value, mod)
case Remove:
err = remove(path, mod)
case Replace:
err = replace(path, self.Value, mod)
case Test:
err = test(path, self.Value, mod)
default:
err = fmt.Errorf("Unknown operation type: %s", self.Op)
}
// Convert it back out before returning.
switch doc.(type) {
case *map[string]interface{}:
*doc.(*map[string]interface{}) = mod["jp"].(map[string]interface{})
case *[]interface{}:
*doc.(*[]interface{}) = mod["jp"].([]interface{})
case *interface{}:
*doc.(*interface{}) = mod["jp"].(interface{})
}
return err
}
func add(path string, value interface{}, doc interface{}) error {
parentPtr, lastToken, err := lastObj(path)
if err != nil {
return err
}
parentValue, kind, err := parentPtr.Get(doc)
if err != nil {
return err
}
if "/"+lastToken == path {
// This is a path to the root object
kind = reflect.ValueOf(doc).Kind()
}
switch kind {
case reflect.Map:
m := parentValue.(map[string]interface{})
m[lastToken] = value
case reflect.Slice:
existing := parentValue.([]interface{})
var index = len(existing)
if lastToken != "-" {
var err error
if index, err = strconv.Atoi(lastToken); err != nil {
return err
}
}
existing = append(existing, 0)
copy(existing[index+1:], existing[index:])
existing[index] = value
// Need to replace
parentPtr.Set(doc, existing)
default:
return fmt.Errorf("Cannot add to document type: %v\n", kind)
}
return nil
}
func copyOp(path string, from string, doc interface{}) error {
_, _, value, err := getValue(from, doc)
if err != nil {
return err
}
return add(path, value, doc)
}
func move(path string, from string, value interface{}, doc interface{}) error {
//if strings.HasPrefix(path, from) {
// return fmt.Errorf("Cannot move values into its own children")
//}
_, _, value, err := getValue(from, doc)
if err != nil {
return err
}
if err = remove(from, doc); err != nil {
return err
}
return add(path, value, doc)
}
func remove(path string, doc interface{}) error {
parentPtr, lastToken, err := lastObj(path)
if err != nil {
return err
}
parentVal, parentKind, err := parentPtr.Get(doc)
if err != nil {
return err
}
if "/"+lastToken == path {
parentKind = reflect.ValueOf(doc).Kind()
}
switch parentKind {
case reflect.Map:
m := parentVal.(map[string]interface{})
delete(m, lastToken)
case reflect.Slice:
index, err := strconv.Atoi(lastToken)
if err != nil {
return err
}
existing := parentVal.([]interface{})
existing[index] = nil
copy(existing[index:], existing[index+1:])
existing = existing[0 : len(existing)-1]
// Need to replace
_, err = parentPtr.Set(doc, existing)
if err != nil {
return err
}
default:
return fmt.Errorf("Unable to remove from kind %s", parentKind)
}
return nil
}
func replace(path string, value interface{}, doc interface{}) error {
parentPtr, lastToken, err := lastObj(path)
if err != nil {
return err
}
parentVal, parentKind, err := parentPtr.Get(doc)
if err != nil {
return err
}
if "/"+lastToken == path {
parentKind = reflect.ValueOf(doc).Kind()
}
switch parentKind {
case reflect.Map:
m := parentVal.(map[string]interface{})
m[lastToken] = value
case reflect.Slice:
s := parentVal.([]interface{})
index, err := strconv.Atoi(lastToken)
if err != nil {
return err
}
s[index] = value
default:
return fmt.Errorf("Unable to replace item of kind %s", parentKind)
}
return nil
}
func test(path string, value interface{}, doc interface{}) error {
_, _, pathValue, err := getValue(path, doc)
if err != nil {
return err
}
if value != nil && !reflect.DeepEqual(pathValue, value) {
return fmt.Errorf("Tested path %s: %v != %v", path, pathValue, value)
}
return nil
}