hana 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|