hana 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Manifest.txt +4 -1
- data/lib/hana.rb +84 -37
- data/test/json-patch-tests/README.md +50 -0
- data/test/json-patch-tests/spec_tests.json +203 -0
- data/test/json-patch-tests/tests.json +242 -0
- data/test/test_hana.rb +10 -1
- data/test/test_ietf.rb +42 -0
- metadata +19 -23
- data/test/test_patch.rb +0 -162
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5593991a2078ce9a518e6815df0eae13a5a6b016
|
4
|
+
data.tar.gz: b03db1e4ec9245bde4757190b51ec19b5084a932
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9a71e96247335c047ba05d71beb6eab4f65813e667c021b9e6a36769b0564d93a099326a1a06ac474dbf7a645978255ca4d193ada800e32837bcd7bf601edb0c
|
7
|
+
data.tar.gz: 86379cd00f01187cd5e504ba301c1041170b36f05b6d1ef53c8025cea539577375edc3b4d6f69d94a6fefa60df1d61417ceccfa4792a882b4fcd6ad5f62718f8
|
data/Manifest.txt
CHANGED
data/lib/hana.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Hana
|
2
|
-
VERSION = '1.0
|
2
|
+
VERSION = '1.1.0'
|
3
3
|
|
4
4
|
class Pointer
|
5
5
|
include Enumerable
|
@@ -8,23 +8,24 @@ module Hana
|
|
8
8
|
@path = Pointer.parse path
|
9
9
|
end
|
10
10
|
|
11
|
-
def each
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
def to_a; @path.dup; end
|
11
|
+
def each(&block); @path.each(&block); end
|
12
|
+
def to_a; @path.dup; end
|
16
13
|
|
17
14
|
def eval object
|
18
15
|
Pointer.eval @path, object
|
19
16
|
end
|
20
17
|
|
18
|
+
ESC = {'^/' => '/', '^^' => '^', '~0' => '~', '~1' => '/'} # :nodoc:
|
19
|
+
|
21
20
|
def self.eval list, object
|
22
21
|
list.inject(object) { |o, part| o[(Array === o ? part.to_i : part)] }
|
23
22
|
end
|
24
23
|
|
25
24
|
def self.parse path
|
26
|
-
|
27
|
-
|
25
|
+
return [''] if path == '/'
|
26
|
+
|
27
|
+
path.sub(/^\//, '').split(/(?<!\^)\//).map! { |part|
|
28
|
+
part.gsub!(/\^[\/^]|~[01]/) { |m| ESC[m] }; part
|
28
29
|
}
|
29
30
|
end
|
30
31
|
end
|
@@ -33,73 +34,119 @@ module Hana
|
|
33
34
|
class Exception < StandardError
|
34
35
|
end
|
35
36
|
|
37
|
+
class FailedTestException < Exception
|
38
|
+
attr_accessor :path, :value
|
39
|
+
|
40
|
+
def initialize path, value
|
41
|
+
super "expected #{value} at #{path}"
|
42
|
+
@path = path
|
43
|
+
@value = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class OutOfBoundsException < Exception
|
48
|
+
end
|
49
|
+
|
50
|
+
class ObjectOperationOnArrayException < Exception
|
51
|
+
end
|
52
|
+
|
36
53
|
def initialize is
|
37
54
|
@is = is
|
38
55
|
end
|
39
56
|
|
40
|
-
VALID = Hash[%w{ add move test replace remove }.map { |x| [x,x]}] # :nodoc:
|
57
|
+
VALID = Hash[%w{ add move test replace remove copy }.map { |x| [x,x]}] # :nodoc:
|
41
58
|
|
42
59
|
def apply doc
|
43
|
-
@is.each_with_object(doc) { |ins,
|
44
|
-
send VALID.fetch(ins.
|
60
|
+
@is.each_with_object(doc) { |ins, d|
|
61
|
+
send VALID.fetch(ins['op'].strip) { |k|
|
45
62
|
raise Exception, "bad method `#{k}`"
|
46
|
-
}, ins,
|
63
|
+
}, ins, d
|
47
64
|
}
|
48
65
|
end
|
49
66
|
|
50
67
|
private
|
51
68
|
|
52
69
|
def add ins, doc
|
53
|
-
list = Pointer.parse ins['
|
70
|
+
list = Pointer.parse ins['path']
|
54
71
|
key = list.pop
|
55
|
-
|
72
|
+
dest = Pointer.eval list, doc
|
73
|
+
obj = ins['value']
|
56
74
|
|
57
|
-
|
58
|
-
obj.insert key.to_i, ins['value']
|
59
|
-
else
|
60
|
-
obj[key] = ins['value']
|
61
|
-
end
|
75
|
+
add_op dest, key, obj
|
62
76
|
end
|
63
77
|
|
64
78
|
def move ins, doc
|
65
|
-
from = Pointer.parse ins['
|
66
|
-
to = Pointer.parse ins['
|
79
|
+
from = Pointer.parse ins['from']
|
80
|
+
to = Pointer.parse ins['path']
|
67
81
|
from_key = from.pop
|
68
|
-
|
82
|
+
key = to.pop
|
83
|
+
src = Pointer.eval from, doc
|
84
|
+
dest = Pointer.eval to, doc
|
69
85
|
|
70
|
-
|
86
|
+
obj = rm_op src, from_key
|
87
|
+
add_op dest, key, obj
|
88
|
+
end
|
89
|
+
|
90
|
+
def copy ins, doc
|
91
|
+
from = Pointer.parse ins['from']
|
92
|
+
to = Pointer.parse ins['path']
|
93
|
+
from_key = from.pop
|
94
|
+
key = to.pop
|
95
|
+
src = Pointer.eval from, doc
|
96
|
+
dest = Pointer.eval to, doc
|
71
97
|
|
72
98
|
if Array === src
|
73
|
-
obj = src.
|
99
|
+
obj = src.fetch from_key.to_i
|
74
100
|
else
|
75
|
-
obj = src.
|
101
|
+
obj = src.fetch from_key
|
76
102
|
end
|
77
103
|
|
78
|
-
dest
|
79
|
-
|
80
|
-
if Array === dest
|
81
|
-
dest.insert to_key.to_i, obj
|
82
|
-
else
|
83
|
-
dest[to_key] = obj
|
84
|
-
end
|
104
|
+
add_op dest, key, obj
|
85
105
|
end
|
86
106
|
|
87
107
|
def test ins, doc
|
88
|
-
expected = Pointer.new(ins['
|
89
|
-
|
108
|
+
expected = Pointer.new(ins['path']).eval doc
|
109
|
+
|
110
|
+
unless expected == ins['value']
|
111
|
+
raise FailedTestException.new(ins['value'], ins['path'])
|
112
|
+
end
|
90
113
|
end
|
91
114
|
|
92
115
|
def replace ins, doc
|
93
|
-
list = Pointer.parse ins['
|
116
|
+
list = Pointer.parse ins['path']
|
94
117
|
key = list.pop
|
95
|
-
Pointer.eval
|
118
|
+
obj = Pointer.eval list, doc
|
119
|
+
|
120
|
+
if Array === obj
|
121
|
+
obj[key.to_i] = ins['value']
|
122
|
+
else
|
123
|
+
obj[key] = ins['value']
|
124
|
+
end
|
96
125
|
end
|
97
126
|
|
98
127
|
def remove ins, doc
|
99
|
-
list = Pointer.parse ins['
|
128
|
+
list = Pointer.parse ins['path']
|
100
129
|
key = list.pop
|
101
130
|
obj = Pointer.eval list, doc
|
131
|
+
rm_op obj, key
|
132
|
+
end
|
133
|
+
|
134
|
+
def check_index obj, key
|
135
|
+
raise ObjectOperationOnArrayException unless key =~ /\A-?\d+\Z/
|
136
|
+
idx = key.to_i
|
137
|
+
raise OutOfBoundsException if idx > obj.length || idx < 0
|
138
|
+
idx
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_op dest, key, obj
|
142
|
+
if Array === dest
|
143
|
+
dest.insert check_index(dest, key), obj
|
144
|
+
else
|
145
|
+
dest[key] = obj
|
146
|
+
end
|
147
|
+
end
|
102
148
|
|
149
|
+
def rm_op obj, key
|
103
150
|
if Array === obj
|
104
151
|
obj.delete_at key.to_i
|
105
152
|
else
|
@@ -0,0 +1,50 @@
|
|
1
|
+
JSON Patch Tests
|
2
|
+
================
|
3
|
+
|
4
|
+
These are test cases for implementations of the [IETF JSON Patch
|
5
|
+
draft](http://tools.ietf.org/html/draft-ietf-appsawg-json-patch).
|
6
|
+
|
7
|
+
|
8
|
+
Test Format
|
9
|
+
-----------
|
10
|
+
|
11
|
+
Each test file is a JSON document that contains an array of test records. A
|
12
|
+
test record is an object with the following members:
|
13
|
+
|
14
|
+
- doc: The JSON document to test against
|
15
|
+
- patch: The patch(es) to apply
|
16
|
+
- expected: The expected resulting document, OR
|
17
|
+
- error: A string describing an expected error
|
18
|
+
- comment: A string describing the test
|
19
|
+
- disabled: True if the test should be skipped
|
20
|
+
|
21
|
+
All fields except 'doc' and 'patch' are optional. Test records consisting only
|
22
|
+
of a comment are also OK.
|
23
|
+
|
24
|
+
These tests are not complete, or even correct - help welcome!
|
25
|
+
|
26
|
+
|
27
|
+
Credits
|
28
|
+
-------
|
29
|
+
|
30
|
+
The seed test set was adapted from Byron Ruth's
|
31
|
+
[jsonpatch-js](https://github.com/bruth/jsonpatch-js/blob/master/test.js) and
|
32
|
+
extended by [Mike McCabe](https://github.com/mikemccabe).
|
33
|
+
|
34
|
+
|
35
|
+
License
|
36
|
+
-------
|
37
|
+
|
38
|
+
Copyright 2012 The Authors
|
39
|
+
|
40
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
41
|
+
you may not use this file except in compliance with the License.
|
42
|
+
You may obtain a copy of the License at
|
43
|
+
|
44
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
45
|
+
|
46
|
+
Unless required by applicable law or agreed to in writing, software
|
47
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
48
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
49
|
+
See the License for the specific language governing permissions and
|
50
|
+
limitations under the License.
|
@@ -0,0 +1,203 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"comment": "A.1. Adding an Object Member",
|
4
|
+
"doc": {
|
5
|
+
"foo": "bar"
|
6
|
+
},
|
7
|
+
"patch": [
|
8
|
+
{ "op": "add", "path": "/baz", "value": "qux" }
|
9
|
+
],
|
10
|
+
"expected": {
|
11
|
+
"baz": "qux",
|
12
|
+
"foo": "bar"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
|
16
|
+
{
|
17
|
+
"comment": "A.2. Adding an Array Element",
|
18
|
+
"doc": {
|
19
|
+
"foo": [ "bar", "baz" ]
|
20
|
+
},
|
21
|
+
"patch": [
|
22
|
+
{ "op": "add", "path": "/foo/1", "value": "qux" }
|
23
|
+
],
|
24
|
+
"expected": {
|
25
|
+
"foo": [ "bar", "qux", "baz" ]
|
26
|
+
}
|
27
|
+
},
|
28
|
+
|
29
|
+
{
|
30
|
+
"comment": "A.3. Removing an Object Member",
|
31
|
+
"doc": {
|
32
|
+
"baz": "qux",
|
33
|
+
"foo": "bar"
|
34
|
+
},
|
35
|
+
"patch": [
|
36
|
+
{ "op": "remove", "path": "/baz" }
|
37
|
+
],
|
38
|
+
"expected": {
|
39
|
+
"foo": "bar"
|
40
|
+
}
|
41
|
+
},
|
42
|
+
|
43
|
+
{
|
44
|
+
"comment": "A.4. Removing an Array Element",
|
45
|
+
"doc": {
|
46
|
+
"foo": [ "bar", "qux", "baz" ]
|
47
|
+
},
|
48
|
+
"patch": [
|
49
|
+
{ "op": "remove", "path": "/foo/1" }
|
50
|
+
],
|
51
|
+
"expected": {
|
52
|
+
"foo": [ "bar", "baz" ]
|
53
|
+
}
|
54
|
+
},
|
55
|
+
|
56
|
+
{
|
57
|
+
"comment": "A.5. Replacing a Value",
|
58
|
+
"doc": {
|
59
|
+
"baz": "qux",
|
60
|
+
"foo": "bar"
|
61
|
+
},
|
62
|
+
"patch": [
|
63
|
+
{ "op": "replace", "path": "/baz", "value": "boo" }
|
64
|
+
],
|
65
|
+
"expected": {
|
66
|
+
"baz": "boo",
|
67
|
+
"foo": "bar"
|
68
|
+
}
|
69
|
+
},
|
70
|
+
|
71
|
+
{
|
72
|
+
"comment": "A.6. Moving a Value",
|
73
|
+
"doc": {
|
74
|
+
"foo": {
|
75
|
+
"bar": "baz",
|
76
|
+
"waldo": "fred"
|
77
|
+
},
|
78
|
+
"qux": {
|
79
|
+
"corge": "grault"
|
80
|
+
}
|
81
|
+
},
|
82
|
+
"patch": [
|
83
|
+
{ "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
|
84
|
+
],
|
85
|
+
"expected": {
|
86
|
+
"foo": {
|
87
|
+
"bar": "baz"
|
88
|
+
},
|
89
|
+
"qux": {
|
90
|
+
"corge": "grault",
|
91
|
+
"thud": "fred"
|
92
|
+
}
|
93
|
+
}
|
94
|
+
},
|
95
|
+
|
96
|
+
{
|
97
|
+
"comment": "A.7. Moving an Array Element",
|
98
|
+
"doc": {
|
99
|
+
"foo": [ "all", "grass", "cows", "eat" ]
|
100
|
+
},
|
101
|
+
"patch": [
|
102
|
+
{ "op": "move", "from": "/foo/1", "path": "/foo/3" }
|
103
|
+
],
|
104
|
+
"expected": {
|
105
|
+
"foo": [ "all", "cows", "eat", "grass" ]
|
106
|
+
}
|
107
|
+
|
108
|
+
},
|
109
|
+
|
110
|
+
{
|
111
|
+
"comment": "A.8. Testing a Value: Success",
|
112
|
+
"doc": {
|
113
|
+
"baz": "qux",
|
114
|
+
"foo": [ "a", 2, "c" ]
|
115
|
+
},
|
116
|
+
"patch": [
|
117
|
+
{ "op": "test", "path": "/baz", "value": "qux" },
|
118
|
+
{ "op": "test", "path": "/foo/1", "value": 2 }
|
119
|
+
],
|
120
|
+
"expected": {
|
121
|
+
"baz": "qux",
|
122
|
+
"foo": [ "a", 2, "c" ]
|
123
|
+
}
|
124
|
+
},
|
125
|
+
|
126
|
+
{
|
127
|
+
"comment": "A.9. Testing a Value: Error",
|
128
|
+
"doc": {
|
129
|
+
"baz": "qux"
|
130
|
+
},
|
131
|
+
"patch": [
|
132
|
+
{ "op": "test", "path": "/baz", "value": "bar" }
|
133
|
+
],
|
134
|
+
"error": "string not equivalent"
|
135
|
+
},
|
136
|
+
|
137
|
+
{
|
138
|
+
"comment": "A.10. Adding a nested Member Object",
|
139
|
+
"doc": {
|
140
|
+
"foo": "bar"
|
141
|
+
},
|
142
|
+
"patch": [
|
143
|
+
{ "op": "add", "path": "/child", "value": { "grandchild": { } } }
|
144
|
+
],
|
145
|
+
"expected": {
|
146
|
+
"foo": "bar",
|
147
|
+
"child": {
|
148
|
+
"grandchild": {
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
},
|
153
|
+
|
154
|
+
{
|
155
|
+
"comment": "A.11. Ignoring Unrecognized Elements",
|
156
|
+
"doc": {
|
157
|
+
"foo":"bar"
|
158
|
+
},
|
159
|
+
"patch": [
|
160
|
+
{ "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
|
161
|
+
],
|
162
|
+
"expected": {
|
163
|
+
"foo":"bar",
|
164
|
+
"baz":"qux"
|
165
|
+
}
|
166
|
+
},
|
167
|
+
|
168
|
+
{
|
169
|
+
"comment": "A.12. Adding to a Non-existant Target",
|
170
|
+
"doc": {
|
171
|
+
"foo": "bar"
|
172
|
+
},
|
173
|
+
"patch": [
|
174
|
+
{ "op": "add", "path": "/baz/bat", "value": "qux" }
|
175
|
+
],
|
176
|
+
"error": "add to a non-existant target"
|
177
|
+
},
|
178
|
+
|
179
|
+
{
|
180
|
+
"comment": "Invalid JSON Patch Document",
|
181
|
+
"doc": {
|
182
|
+
"foo": "bar"
|
183
|
+
},
|
184
|
+
"patch": [
|
185
|
+
{ "op": "add", "path": "/baz", "value": "qux", "op": "remove" }
|
186
|
+
],
|
187
|
+
"error": "operation has two 'op' members"
|
188
|
+
},
|
189
|
+
|
190
|
+
{
|
191
|
+
"comment": "~ Escape Ordering",
|
192
|
+
"doc": {
|
193
|
+
"/": 9,
|
194
|
+
"~1": 10
|
195
|
+
},
|
196
|
+
"patch": [{"op": "test", "path": "/~01", "value":"10"}],
|
197
|
+
"expected": {
|
198
|
+
"/": 9,
|
199
|
+
"~1": 10
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
]
|
@@ -0,0 +1,242 @@
|
|
1
|
+
[
|
2
|
+
{ "comment": "empty list, empty docs",
|
3
|
+
"doc": {},
|
4
|
+
"patch": [],
|
5
|
+
"expected": {} },
|
6
|
+
|
7
|
+
{ "comment": "empty patch list",
|
8
|
+
"doc": {"foo": 1},
|
9
|
+
"patch": [],
|
10
|
+
"expected": {"foo": 1} },
|
11
|
+
|
12
|
+
{ "comment": "rearrangements OK?",
|
13
|
+
"doc": {"foo": 1, "bar": 2},
|
14
|
+
"patch": [],
|
15
|
+
"expected": {"bar":2, "foo": 1} },
|
16
|
+
|
17
|
+
{ "comment": "rearrangements OK? How about one level down ... array",
|
18
|
+
"doc": [{"foo": 1, "bar": 2}],
|
19
|
+
"patch": [],
|
20
|
+
"expected": [{"bar":2, "foo": 1}] },
|
21
|
+
|
22
|
+
{ "comment": "rearrangements OK? How about one level down...",
|
23
|
+
"doc": {"foo":{"foo": 1, "bar": 2}},
|
24
|
+
"patch": [],
|
25
|
+
"expected": {"foo":{"bar":2, "foo": 1}} },
|
26
|
+
|
27
|
+
{ "comment": "add replaces any existing field",
|
28
|
+
"doc": {"foo": null},
|
29
|
+
"patch": [{"op": "add", "path": "/foo", "value":1}],
|
30
|
+
"expected": {"foo": 1} },
|
31
|
+
|
32
|
+
{ "comment": "toplevel array",
|
33
|
+
"doc": [],
|
34
|
+
"patch": [{"op": "add", "path": "/0", "value": "foo"}],
|
35
|
+
"expected": ["foo"] },
|
36
|
+
|
37
|
+
{ "comment": "toplevel array, no change",
|
38
|
+
"doc": ["foo"],
|
39
|
+
"patch": [],
|
40
|
+
"expected": ["foo"] },
|
41
|
+
|
42
|
+
{ "comment": "toplevel object, numeric string",
|
43
|
+
"doc": {},
|
44
|
+
"patch": [{"op": "add", "path": "/foo", "value": "1"}],
|
45
|
+
"expected": {"foo":"1"} },
|
46
|
+
|
47
|
+
{ "comment": "toplevel object, integer",
|
48
|
+
"doc": {},
|
49
|
+
"patch": [{"op": "add", "path": "/foo", "value": 1}],
|
50
|
+
"expected": {"foo":1} },
|
51
|
+
|
52
|
+
{ "comment": "Toplevel scalar values OK?",
|
53
|
+
"doc": "foo",
|
54
|
+
"patch": [{"op": "replace", "path": "", "value": "bar"}],
|
55
|
+
"expected": "bar",
|
56
|
+
"disabled": true },
|
57
|
+
|
58
|
+
{ "comment": "Add, / target",
|
59
|
+
"doc": {},
|
60
|
+
"patch": [ {"op": "add", "path": "/", "value":1 } ],
|
61
|
+
"expected": {"":1} },
|
62
|
+
|
63
|
+
{ "comment": "Add composite value at top level",
|
64
|
+
"doc": {"foo": 1},
|
65
|
+
"patch": [{"op": "add", "path": "/bar", "value": [1, 2]}],
|
66
|
+
"expected": {"foo": 1, "bar": [1, 2]} },
|
67
|
+
|
68
|
+
{ "comment": "Add into composite value",
|
69
|
+
"doc": {"foo": 1, "baz": [{"qux": "hello"}]},
|
70
|
+
"patch": [{"op": "add", "path": "/baz/0/foo", "value": "world"}],
|
71
|
+
"expected": {"foo": 1, "baz": [{"qux": "hello", "foo": "world"}]} },
|
72
|
+
|
73
|
+
{ "doc": {"bar": [1, 2]},
|
74
|
+
"patch": [{"op": "add", "path": "/bar/8", "value": "5"}],
|
75
|
+
"error": "Out of bounds (upper)" },
|
76
|
+
|
77
|
+
{ "doc": {"bar": [1, 2]},
|
78
|
+
"patch": [{"op": "add", "path": "/bar/-1", "value": "5"}],
|
79
|
+
"error": "Out of bounds (lower)" },
|
80
|
+
|
81
|
+
{ "doc": {"foo": 1},
|
82
|
+
"patch": [{"op": "add", "path": "/bar", "value": true}],
|
83
|
+
"expected": {"foo": 1, "bar": true} },
|
84
|
+
|
85
|
+
{ "doc": {"foo": 1},
|
86
|
+
"patch": [{"op": "add", "path": "/bar", "value": false}],
|
87
|
+
"expected": {"foo": 1, "bar": false} },
|
88
|
+
|
89
|
+
{ "doc": {"foo": 1},
|
90
|
+
"patch": [{"op": "add", "path": "/bar", "value": null}],
|
91
|
+
"expected": {"foo": 1, "bar": null} },
|
92
|
+
|
93
|
+
{ "comment": "0 can be an array index or object element name",
|
94
|
+
"doc": {"foo": 1},
|
95
|
+
"patch": [{"op": "add", "path": "/0", "value": "bar"}],
|
96
|
+
"expected": {"foo": 1, "0": "bar" } },
|
97
|
+
|
98
|
+
{ "doc": ["foo"],
|
99
|
+
"patch": [{"op": "add", "path": "/1", "value": "bar"}],
|
100
|
+
"expected": ["foo", "bar"] },
|
101
|
+
|
102
|
+
{ "doc": ["foo", "sil"],
|
103
|
+
"patch": [{"op": "add", "path": "/1", "value": "bar"}],
|
104
|
+
"expected": ["foo", "bar", "sil"] },
|
105
|
+
|
106
|
+
{ "doc": ["foo", "sil"],
|
107
|
+
"patch": [{"op": "add", "path": "/0", "value": "bar"}],
|
108
|
+
"expected": ["bar", "foo", "sil"] },
|
109
|
+
|
110
|
+
{ "doc": ["foo", "sil"],
|
111
|
+
"patch": [{"op":" add", "path": "/2", "value": "bar"}],
|
112
|
+
"expected": ["foo", "sil", "bar"] },
|
113
|
+
|
114
|
+
{ "doc": ["foo", "sil"],
|
115
|
+
"patch": [{"op": "add", "path": "/bar", "value": 42}],
|
116
|
+
"error": "Object operation on array target" },
|
117
|
+
|
118
|
+
{ "doc": ["foo", "sil"],
|
119
|
+
"patch": [{"op": "add", "path": "/1", "value": ["bar", "baz"]}],
|
120
|
+
"expected": ["foo", ["bar", "baz"], "sil"],
|
121
|
+
"comment": "value in array add not flattened" },
|
122
|
+
|
123
|
+
{ "doc": {"foo": 1, "bar": [1, 2, 3, 4]},
|
124
|
+
"patch": [{"op": "remove", "path": "/bar"}],
|
125
|
+
"expected": {"foo": 1} },
|
126
|
+
|
127
|
+
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
|
128
|
+
"patch": [{"op": "remove", "path": "/baz/0/qux"}],
|
129
|
+
"expected": {"foo": 1, "baz": [{}]} },
|
130
|
+
|
131
|
+
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
|
132
|
+
"patch": [{"op": "replace", "path": "/foo", "value": [1, 2, 3, 4]}],
|
133
|
+
"expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]} },
|
134
|
+
|
135
|
+
{ "doc": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]},
|
136
|
+
"patch": [{"op": "replace", "path": "/baz/0/qux", "value": "world"}],
|
137
|
+
"expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "world"}]} },
|
138
|
+
|
139
|
+
{ "doc": ["foo"],
|
140
|
+
"patch": [{"op": "replace", "path": "/0", "value": "bar"}],
|
141
|
+
"expected": ["bar"] },
|
142
|
+
|
143
|
+
{ "doc": [""],
|
144
|
+
"patch": [{"op": "replace", "path": "/0", "value": 0}],
|
145
|
+
"expected": [0] },
|
146
|
+
|
147
|
+
{ "doc": [""],
|
148
|
+
"patch": [{"op": "replace", "path": "/0", "value": true}],
|
149
|
+
"expected": [true] },
|
150
|
+
|
151
|
+
{ "doc": [""],
|
152
|
+
"patch": [{"op": "replace", "path": "/0", "value": false}],
|
153
|
+
"expected": [false] },
|
154
|
+
|
155
|
+
{ "doc": [""],
|
156
|
+
"patch": [{"op": "replace", "path": "/0", "value": null}],
|
157
|
+
"expected": [null] },
|
158
|
+
|
159
|
+
{ "doc": ["foo", "sil"],
|
160
|
+
"patch": [{"op": "replace", "path": "/1", "value": ["bar", "baz"]}],
|
161
|
+
"expected": ["foo", ["bar", "baz"]],
|
162
|
+
"comment": "value in array replace not flattened" },
|
163
|
+
|
164
|
+
{ "comment": "spurious patch properties",
|
165
|
+
"doc": {"foo": 1},
|
166
|
+
"patch": [{"op": "test", "path": "/foo", "value": 1, "spurious": 1}],
|
167
|
+
"expected": {"foo": 1} },
|
168
|
+
|
169
|
+
{ "doc": {"foo": null},
|
170
|
+
"patch": [{"op": "test", "path": "/foo", "value": null}],
|
171
|
+
"comment": "null value should still be valid obj property" },
|
172
|
+
|
173
|
+
{ "doc": {"foo": {"foo": 1, "bar": 2}},
|
174
|
+
"patch": [{"op": "test", "path": "/foo", "value": {"bar": 2, "foo": 1}}],
|
175
|
+
"comment": "test should pass despite rearrangement" },
|
176
|
+
|
177
|
+
{ "doc": {"foo": [{"foo": 1, "bar": 2}]},
|
178
|
+
"patch": [{"op": "test", "path": "/foo", "value": [{"bar": 2, "foo": 1}]}],
|
179
|
+
"comment": "test should pass despite (nested) rearrangement" },
|
180
|
+
|
181
|
+
{ "doc": {"foo": {"bar": [1, 2, 5, 4]}},
|
182
|
+
"patch": [{"op": "test", "path": "/foo", "value": {"bar": [1, 2, 5, 4]}}],
|
183
|
+
"comment": "test should pass - no error" },
|
184
|
+
|
185
|
+
{ "doc": {"foo": {"bar": [1, 2, 5, 4]}},
|
186
|
+
"patch": [{"op": "test", "path": "/foo", "value": [1, 2]}],
|
187
|
+
"error": "test op should fail" },
|
188
|
+
|
189
|
+
{ "comment": "json-pointer tests" },
|
190
|
+
|
191
|
+
{ "comment": "Whole document",
|
192
|
+
"doc": { "foo": 1 },
|
193
|
+
"patch": [{"op": "test", "path": "", "value": {"foo": 1}}],
|
194
|
+
"disabled": true },
|
195
|
+
|
196
|
+
{ "comment": "Empty-string element",
|
197
|
+
"doc": { "": 1 },
|
198
|
+
"patch": [{"op": "test", "path": "/", "value": 1}] },
|
199
|
+
|
200
|
+
{ "doc": {
|
201
|
+
"foo": ["bar", "baz"],
|
202
|
+
"": 0,
|
203
|
+
"a/b": 1,
|
204
|
+
"c%d": 2,
|
205
|
+
"e^f": 3,
|
206
|
+
"g|h": 4,
|
207
|
+
"i\\j": 5,
|
208
|
+
"k\"l": 6,
|
209
|
+
" ": 7,
|
210
|
+
"m~n": 8
|
211
|
+
},
|
212
|
+
"patch": [{"op": "test", "path": "/foo", "value": ["bar", "baz"]},
|
213
|
+
{"op": "test", "path": "/foo/0", "value": "bar"},
|
214
|
+
{"op": "test", "path": "/", "value": 0},
|
215
|
+
{"op": "test", "path": "/a~1b", "value": 1},
|
216
|
+
{"op": "test", "path": "/c%d", "value": 2},
|
217
|
+
{"op": "test", "path": "/e^f", "value": 3},
|
218
|
+
{"op": "test", "path": "/g|h", "value": 4},
|
219
|
+
{"op": "test", "path": "/i\\j", "value": 5},
|
220
|
+
{"op": "test", "path": "/k\"l", "value": 6},
|
221
|
+
{"op": "test", "path": "/ ", "value": 7},
|
222
|
+
{"op": "test", "path": "/m~0n", "value": 8}] },
|
223
|
+
|
224
|
+
{ "comment": "Move to same location has no effect",
|
225
|
+
"doc": {"foo": 1},
|
226
|
+
"patch": [{"op": "move", "from": "/foo", "path": "/foo"}],
|
227
|
+
"expected": {"foo": 1} },
|
228
|
+
|
229
|
+
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
|
230
|
+
"patch": [{"op": "move", "from": "/foo", "path": "/bar"}],
|
231
|
+
"expected": {"baz": [{"qux": "hello"}], "bar": 1} },
|
232
|
+
|
233
|
+
{ "doc": {"baz": [{"qux": "hello"}], "bar": 1},
|
234
|
+
"patch": [{"op": "move", "from": "/baz/0/qux", "path": "/baz/1"}],
|
235
|
+
"expected": {"baz": [{}, "hello"], "bar": 1} },
|
236
|
+
|
237
|
+
{ "doc": {"baz": [{"qux": "hello"}], "bar": 1},
|
238
|
+
"patch": [{"op": "copy", "from": "/baz/0", "path": "/boo"}],
|
239
|
+
"expected": {"baz":[{"qux":"hello"}],"bar":1,"boo":{"qux":"hello"}} },
|
240
|
+
|
241
|
+
{ "comment": "tests complete" }
|
242
|
+
]
|
data/test/test_hana.rb
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestHana < Hana::TestCase
|
4
|
+
def test_no_eval
|
5
|
+
patch = Hana::Patch.new [
|
6
|
+
{ 'op' => 'eval', 'value' => '1' }
|
7
|
+
]
|
8
|
+
assert_raises(Hana::Patch::Exception) do
|
9
|
+
patch.apply('foo' => 'bar')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
4
13
|
def test_split_many
|
5
14
|
pointer = Hana::Pointer.new '/foo/bar/baz'
|
6
15
|
assert_equal %w{ foo bar baz }, pointer.to_a
|
@@ -8,7 +17,7 @@ class TestHana < Hana::TestCase
|
|
8
17
|
|
9
18
|
def test_root
|
10
19
|
pointer = Hana::Pointer.new '/'
|
11
|
-
assert_equal [], pointer.to_a
|
20
|
+
assert_equal [''], pointer.to_a
|
12
21
|
end
|
13
22
|
|
14
23
|
def test_escape
|
data/test/test_ietf.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Hana
|
5
|
+
class TestIETF < TestCase
|
6
|
+
TESTDIR = File.dirname File.expand_path __FILE__
|
7
|
+
json = File.read File.join TESTDIR, 'json-patch-tests', 'tests.json'
|
8
|
+
tests = JSON.load json
|
9
|
+
tests.each_with_index do |test, i|
|
10
|
+
next unless test['doc']
|
11
|
+
|
12
|
+
define_method("test_#{test['comment'] || i }") do
|
13
|
+
skip "disabled" if test['disabled']
|
14
|
+
|
15
|
+
doc = test['doc']
|
16
|
+
patch = test['patch']
|
17
|
+
|
18
|
+
patch = Hana::Patch.new patch
|
19
|
+
|
20
|
+
if test['error']
|
21
|
+
assert_raises(ex(test['error'])) do
|
22
|
+
patch.apply doc
|
23
|
+
end
|
24
|
+
else
|
25
|
+
assert_equal(test['expected'] || doc, patch.apply(doc))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def ex msg
|
33
|
+
case msg
|
34
|
+
when /Out of bounds/i then Hana::Patch::OutOfBoundsException
|
35
|
+
when /Object operation on array/ then
|
36
|
+
Hana::Patch::ObjectOperationOnArrayException
|
37
|
+
else
|
38
|
+
Hana::Patch::FailedTestException
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,64 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Aaron Patterson
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-05-23 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: minitest
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ~>
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
19
|
+
version: '5.0'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ~>
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
26
|
+
version: '5.0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rdoc
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ~>
|
36
32
|
- !ruby/object:Gem::Version
|
37
|
-
version: '
|
33
|
+
version: '4.0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ~>
|
44
39
|
- !ruby/object:Gem::Version
|
45
|
-
version: '
|
40
|
+
version: '4.0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: hoe
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
45
|
- - ~>
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version: '3.
|
47
|
+
version: '3.6'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
52
|
- - ~>
|
60
53
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
54
|
+
version: '3.6'
|
62
55
|
description: Implementation of [JSON Patch][1] and [JSON Pointer][2] drafts.
|
63
56
|
email:
|
64
57
|
- aaron@tenderlovemaking.com
|
@@ -67,6 +60,7 @@ extensions: []
|
|
67
60
|
extra_rdoc_files:
|
68
61
|
- CHANGELOG.rdoc
|
69
62
|
- Manifest.txt
|
63
|
+
- README.md
|
70
64
|
files:
|
71
65
|
- .autotest
|
72
66
|
- CHANGELOG.rdoc
|
@@ -75,11 +69,15 @@ files:
|
|
75
69
|
- Rakefile
|
76
70
|
- lib/hana.rb
|
77
71
|
- test/helper.rb
|
72
|
+
- test/json-patch-tests/README.md
|
73
|
+
- test/json-patch-tests/spec_tests.json
|
74
|
+
- test/json-patch-tests/tests.json
|
78
75
|
- test/test_hana.rb
|
79
|
-
- test/
|
76
|
+
- test/test_ietf.rb
|
80
77
|
- .gemtest
|
81
78
|
homepage: http://github.com/tenderlove/hana
|
82
79
|
licenses: []
|
80
|
+
metadata: {}
|
83
81
|
post_install_message:
|
84
82
|
rdoc_options:
|
85
83
|
- --main
|
@@ -87,23 +85,21 @@ rdoc_options:
|
|
87
85
|
require_paths:
|
88
86
|
- lib
|
89
87
|
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
88
|
requirements:
|
92
|
-
- -
|
89
|
+
- - '>='
|
93
90
|
- !ruby/object:Gem::Version
|
94
91
|
version: '0'
|
95
92
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
-
none: false
|
97
93
|
requirements:
|
98
|
-
- -
|
94
|
+
- - '>='
|
99
95
|
- !ruby/object:Gem::Version
|
100
96
|
version: '0'
|
101
97
|
requirements: []
|
102
98
|
rubyforge_project: hana
|
103
|
-
rubygems_version:
|
99
|
+
rubygems_version: 2.0.2
|
104
100
|
signing_key:
|
105
|
-
specification_version:
|
101
|
+
specification_version: 4
|
106
102
|
summary: Implementation of [JSON Patch][1] and [JSON Pointer][2] drafts.
|
107
103
|
test_files:
|
108
104
|
- test/test_hana.rb
|
109
|
-
- test/
|
105
|
+
- test/test_ietf.rb
|
data/test/test_patch.rb
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
module Hana
|
4
|
-
class TestPatch < TestCase
|
5
|
-
def test_no_eval
|
6
|
-
patch = Hana::Patch.new [
|
7
|
-
{ 'eval' => '1' }
|
8
|
-
]
|
9
|
-
assert_raises(Hana::Patch::Exception) do
|
10
|
-
patch.apply('foo' => 'bar')
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_add_member
|
15
|
-
patch = Hana::Patch.new [
|
16
|
-
{ 'add' => '/baz', 'value' => 'qux' }
|
17
|
-
]
|
18
|
-
|
19
|
-
result = patch.apply('foo' => 'bar')
|
20
|
-
assert_equal({'baz' => 'qux', 'foo' => 'bar'}, result)
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_add_array
|
24
|
-
patch = Hana::Patch.new [
|
25
|
-
{ "add" => "/foo/1", "value" => "qux" }
|
26
|
-
]
|
27
|
-
|
28
|
-
result = patch.apply({ "foo" => [ "bar", "baz" ] })
|
29
|
-
|
30
|
-
assert_equal({ "foo" => [ "bar", "qux", "baz" ] }, result)
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_remove_object_member
|
34
|
-
patch = Hana::Patch.new [ { "remove" => "/baz" } ]
|
35
|
-
|
36
|
-
result = patch.apply({ 'baz' => 'qux', 'foo' => 'bar' })
|
37
|
-
|
38
|
-
assert_equal({ "foo" => 'bar' }, result)
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_remove_array_element
|
42
|
-
patch = Hana::Patch.new [ { "remove" => "/foo/1" } ]
|
43
|
-
result = patch.apply({ "foo" => [ "bar", "qux", "baz" ] })
|
44
|
-
assert_equal({ "foo" => [ "bar", "baz" ] }, result)
|
45
|
-
end
|
46
|
-
|
47
|
-
def test_replace_value
|
48
|
-
patch = Hana::Patch.new [ { "replace" => "/baz", "value" => "boo" } ]
|
49
|
-
result = patch.apply({ "baz" => "qux", "foo" => "bar" })
|
50
|
-
assert_equal({ "baz" => "boo", "foo" => "bar" }, result)
|
51
|
-
end
|
52
|
-
|
53
|
-
def test_moving_a_value
|
54
|
-
doc = {
|
55
|
-
"foo" => {
|
56
|
-
"bar" => "baz",
|
57
|
-
"waldo" => "fred"
|
58
|
-
},
|
59
|
-
"qux" => {
|
60
|
-
"corge" => "grault"
|
61
|
-
}
|
62
|
-
}
|
63
|
-
|
64
|
-
patch = [ { "move" => "/foo/waldo", 'to' => "/qux/thud" } ]
|
65
|
-
|
66
|
-
expected = {
|
67
|
-
"foo" => {
|
68
|
-
"bar" => "baz"
|
69
|
-
},
|
70
|
-
"qux" => {
|
71
|
-
"corge" => "grault",
|
72
|
-
"thud" => "fred"
|
73
|
-
}
|
74
|
-
}
|
75
|
-
|
76
|
-
patch = Hana::Patch.new patch
|
77
|
-
result = patch.apply doc
|
78
|
-
assert_equal expected, result
|
79
|
-
end
|
80
|
-
|
81
|
-
def test_move_an_array_element
|
82
|
-
# An example target JSON document:
|
83
|
-
doc = {
|
84
|
-
"foo" => [ "all", "grass", "cows", "eat" ]
|
85
|
-
}
|
86
|
-
|
87
|
-
# A JSON Patch document:
|
88
|
-
patch = [
|
89
|
-
{ "move" => "/foo/1", "to" => "/foo/3" }
|
90
|
-
]
|
91
|
-
|
92
|
-
# The resulting JSON document:
|
93
|
-
expected = {
|
94
|
-
"foo" => [ "all", "cows", "eat", "grass" ]
|
95
|
-
}
|
96
|
-
|
97
|
-
patch = Hana::Patch.new patch
|
98
|
-
result = patch.apply doc
|
99
|
-
assert_equal expected, result
|
100
|
-
end
|
101
|
-
|
102
|
-
def test_testing_a_value_success
|
103
|
-
# An example target JSON document:
|
104
|
-
doc = {
|
105
|
-
"baz" => "qux",
|
106
|
-
"foo" => [ "a", 2, "c" ]
|
107
|
-
}
|
108
|
-
|
109
|
-
# A JSON Patch document that will result in successful evaluation:
|
110
|
-
patch = [
|
111
|
-
{ "test" => "/baz", "value" => "qux" },
|
112
|
-
{ "test" => "/foo/1", "value" => 2 },
|
113
|
-
{ "add" => "/bar", "value" => 2 },
|
114
|
-
]
|
115
|
-
|
116
|
-
expected = {
|
117
|
-
"baz" => "qux",
|
118
|
-
"foo" => [ "a", 2, "c" ],
|
119
|
-
'bar' => 2
|
120
|
-
}
|
121
|
-
|
122
|
-
patch = Hana::Patch.new patch
|
123
|
-
result = patch.apply doc
|
124
|
-
assert_equal expected, result
|
125
|
-
end
|
126
|
-
|
127
|
-
def test_testing_a_value_error
|
128
|
-
# An example target JSON document:
|
129
|
-
doc = { "baz" => "qux" }
|
130
|
-
|
131
|
-
# A JSON Patch document that will result in an error condition:
|
132
|
-
patch = [
|
133
|
-
{ "test" => "/baz", "value" => "bar" }
|
134
|
-
]
|
135
|
-
|
136
|
-
patch = Hana::Patch.new patch
|
137
|
-
|
138
|
-
assert_raises(Hana::Patch::Exception) do
|
139
|
-
patch.apply doc
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def test_add_nested_member_object
|
144
|
-
# An example target JSON document:
|
145
|
-
doc = { "foo" => "bar" }
|
146
|
-
# A JSON Patch document:
|
147
|
-
patch = [
|
148
|
-
{ "add" => "/child", "value" => { "grandchild" => { } } }
|
149
|
-
]
|
150
|
-
|
151
|
-
# The resulting JSON document:
|
152
|
-
expected = {
|
153
|
-
"foo" => "bar",
|
154
|
-
"child" => { "grandchild" => { } }
|
155
|
-
}
|
156
|
-
|
157
|
-
patch = Hana::Patch.new patch
|
158
|
-
result = patch.apply doc
|
159
|
-
assert_equal expected, result
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|