json-patch 1.0.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.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1,3 @@
1
+ [submodule "test/json-patch-tests"]
2
+ path = test/json-patch-tests
3
+ url = git://github.com/json-patch/json-patch-tests.git
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - jruby-19mode
7
+ - rbx-19mode
8
+ before_install:
9
+ - git submodule update --init --recursive
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in json-patch.gemspec
4
+ gem 'minitest'
5
+ gem 'coveralls', require: false
6
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Guille Carlos
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ # JSON::Patch (Version 1 Coming Soon)
2
+ [![Build Status](https://travis-ci.org/guillec/json-patch.png)](https://travis-ci.org/guillec/json-patch)
3
+ [![Code Climate](https://codeclimate.com/github/guillec/json-patch.png)](https://codeclimate.com/github/guillec/json-patch)
4
+ [![Coverage Status](https://coveralls.io/repos/guillec/json-patch/badge.png)](https://coveralls.io/r/guillec/json-patch)
5
+
6
+
7
+ This gem augments Ruby's built-in JSON library to support JSON Patch
8
+ (identified by the json-patch+json media type). http://tools.ietf.org/html/rfc6902
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'json-patch'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install json-patch
23
+
24
+ ## Usage
25
+
26
+ Then, use it:
27
+
28
+ ```ruby
29
+ # The example from http://tools.ietf.org/html/rfc6902#appendix-A
30
+
31
+ # Add Object Member
32
+ target_document = <<-JSON
33
+ { "foo": "bar"}
34
+ JSON
35
+
36
+ operations_document = <<-JSON
37
+ [
38
+ { "op": "add", "path": "/baz", "value": "qux" }
39
+ ]
40
+ JSON
41
+
42
+ JSON.patch(target_document, operations_document)
43
+ # =>
44
+ { "baz": "qux", "foo": "bar" }
45
+
46
+
47
+ # Add Array Element
48
+ target_document = <<-JSON
49
+ { "foo": [ "bar", "baz" ] }
50
+ JSON
51
+
52
+ operations_document = <<-JSON
53
+ [
54
+ { "op": "add", "path": "/foo/1", "value": "qux" }
55
+ ]
56
+ JSON
57
+
58
+ JSON.patch(target_document, operations_document)
59
+ # =>
60
+ { "foo": [ "bar", "qux", "baz" ] }
61
+ ```
62
+
63
+ If you'd prefer to operate on pure Ruby objects rather than JSON
64
+ strings, you can construct a JSON::Patch object instead.
65
+
66
+ ```ruby
67
+ target_document = { "foo" => [ "bar", "baz" ] }
68
+ operations_document = [{ "op" => "add", "path" => "/foo/1", "value" => "qux" }]
69
+
70
+ JSON::Patch.new(target_document, operations_document).call
71
+ # =>
72
+ { "foo" => [ "bar", "qux", "baz" ] }
73
+ ```
74
+
75
+
76
+ ## Contributing
77
+
78
+ 1. Fork it
79
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
80
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
81
+ 4. Push to the branch (`git push origin my-new-feature`)
82
+ 5. Create new Pull Request
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/*_test.rb']
8
+ t.ruby_opts = ["-w"]
9
+ t.verbose = true
10
+ end
11
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'json/patch/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "json-patch"
8
+ spec.version = Json::Patch::VERSION
9
+ spec.authors = ["Guille Carlos"]
10
+ spec.email = ["guille@bitpop.in"]
11
+ spec.description = %q{An implementation of RFC 6902: JSON Patch.}
12
+ spec.summary = %q{An implementation of RFC 6902: JSON Patch.}
13
+ spec.homepage = "https://github.com/guillec/json-patch"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,187 @@
1
+ require 'json'
2
+ require 'json/patch/railtie' if defined?(Rails)
3
+
4
+ module JSON
5
+ PatchError = Class.new(StandardError)
6
+ PatchOutOfBoundException = Class.new(StandardError)
7
+ PatchObjectOperationOnArrayException = Class.new(StandardError)
8
+
9
+ def self.patch(target_doc, operations_doc)
10
+ target_doc = JSON.parse(target_doc)
11
+ operations_doc = JSON.parse(operations_doc)
12
+ result_doc = JSON::Patch.new(target_doc, operations_doc).call
13
+ JSON.dump(result_doc)
14
+ end
15
+
16
+ class Patch
17
+
18
+ def initialize(target_doc, operations_doc)
19
+ @target_doc = target_doc
20
+ @operations_doc = operations_doc
21
+ end
22
+
23
+ def call
24
+ return @target_doc if @operations_doc.empty?
25
+ @operations_doc.each do |operation|
26
+ operation = operation.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
27
+ if allowed?(operation)
28
+ @target_doc = send(operation[:op].to_sym, @target_doc, operation)
29
+ end
30
+ end
31
+ return @target_doc
32
+ end
33
+
34
+ private
35
+ def allowed?(operation)
36
+ operation.fetch(:op) { raise JSON::PatchError }
37
+ raise JSON::PatchError unless ["add","remove","replace","move","copy","test"].include?(operation[:op])
38
+ operation.fetch(:path) { raise JSON::PatchError }
39
+ true
40
+ end
41
+
42
+ def add(target_doc, operation_doc)
43
+ path = operation_doc[:path]
44
+ value = operation_doc.fetch(:value) { raise JSON::PatchError }
45
+
46
+ add_operation(target_doc, path, value)
47
+ target_doc
48
+ end
49
+
50
+ def remove(target_doc, operation_doc)
51
+ path = operation_doc.fetch(:path) { raise JSON::PatchError }
52
+
53
+ remove_operation(target_doc, path)
54
+ target_doc
55
+ end
56
+
57
+ def replace(target_doc, operation_doc)
58
+ remove(target_doc, operation_doc)
59
+ add(target_doc, operation_doc)
60
+ target_doc
61
+ end
62
+
63
+ def move(target_doc, operation_doc)
64
+ src = operation_doc.fetch(:from) { raise JSON::PatchError }
65
+ dest = operation_doc[:path]
66
+ value = remove_operation(target_doc, src)
67
+
68
+ add_operation(target_doc, dest, value)
69
+ target_doc
70
+ end
71
+
72
+ def copy(target_doc, operation_doc)
73
+ src = operation_doc.fetch(:from) { raise JSON::PatchError }
74
+ dest = operation_doc[:path]
75
+ value = find_value(target_doc, operation_doc, src)
76
+
77
+ add_operation(target_doc, dest, value)
78
+ target_doc
79
+ end
80
+
81
+ def test(target_doc, operation_doc)
82
+ path = operation_doc[:path]
83
+ value = find_value(target_doc, operation_doc, path)
84
+ test_value = operation_doc.fetch(:value) { raise JSON::PatchError }
85
+
86
+ raise JSON::PatchError if value != test_value
87
+ target_doc if value == test_value
88
+ end
89
+
90
+ def add_operation(target_doc, path, value)
91
+ path_array = split_path(path)
92
+ ref_token = path_array.pop
93
+ target_item = build_target_array(path_array, target_doc)
94
+
95
+ add_array(target_doc, path_array, target_item, ref_token, value) if target_item.kind_of? Array
96
+ add_object(target_doc, target_item, ref_token, value) unless target_item.kind_of? Array
97
+ end
98
+
99
+ def add_object(target_doc, target_item, ref_token, value)
100
+ raise JSON::PatchError if target_item.nil?
101
+ if ref_token.nil?
102
+ target_doc.replace(value)
103
+ else
104
+ target_item[ref_token] = value
105
+ end
106
+ end
107
+
108
+ def add_array(doc, path_array, target_item, ref_token, value)
109
+ return unless valid_index?(target_item, ref_token)
110
+ if ref_token == "-"
111
+ new_array = target_item << value
112
+ else
113
+ new_array = target_item.insert ref_token.to_i, value
114
+ end
115
+ add_to_target_document(doc, path_array, target_item, new_array)
116
+ end
117
+
118
+ def valid_index?(item_array, index)
119
+ raise JSON::PatchObjectOperationOnArrayException unless index =~ /\A-?\d+\Z/ || index == "-"
120
+ index = index == "-" ? item_array.length : index.to_i
121
+ raise JSON::PatchOutOfBoundException if index.to_i > item_array.length || index.to_i < 0
122
+ true
123
+ end
124
+
125
+ def remove_operation(target_doc, path)
126
+ path_array = split_path(path)
127
+ ref_token = path_array.pop
128
+ target_item = build_target_array(path_array, target_doc)
129
+ raise JSON::PatchObjectOperationOnArrayException if target_item.nil?
130
+
131
+ if Array === target_item
132
+ target_item.delete_at ref_token.to_i if valid_index?(target_item, ref_token)
133
+ else
134
+ target_item.delete ref_token
135
+ end
136
+ end
137
+
138
+ def find_value(target_doc, operation_doc, path)
139
+ path_array = split_path(path)
140
+ ref_token = path_array.pop
141
+ target_item = build_target_array(path_array, target_doc)
142
+ if Array === target_item
143
+ if is_a_number?(ref_token)
144
+ target_item.at ref_token.to_i
145
+ else
146
+ raise JSON::PatchObjectOperationOnArrayException
147
+ end
148
+ else
149
+ target_item[ref_token]
150
+ end
151
+ end
152
+
153
+ def is_a_number?(s)
154
+ s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
155
+ end
156
+
157
+ def build_target_array(path_array, target_doc)
158
+ path_array.inject(target_doc) do |doc, item|
159
+ key = (doc.kind_of?(Array) ? item.to_i : item)
160
+ if doc.kind_of?(Array)
161
+ doc[key]
162
+ else
163
+ doc.has_key?(key) ? doc[key] : doc[key.to_sym] unless doc.kind_of?(Array)
164
+ end
165
+ end
166
+ end
167
+
168
+ def add_to_target_document(doc, path, target_item, array)
169
+ path.inject(doc) do |obj, part|
170
+ key = (Array === doc ? part.to_i : part)
171
+ doc[key]
172
+ end
173
+ end
174
+
175
+ def split_path(path)
176
+ escape_characters = {'^/' => '/', '^^' => '^', '~0' => '~', '~1' => '/'}
177
+ if path == '/'
178
+ ['']
179
+ else
180
+ path.sub(/^\//, '').split(/(?<!\^)\//).map! { |part|
181
+ part.gsub!(/\^[\/^]|~[01]/) { |m| escape_characters[m] }
182
+ part
183
+ }
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails'
2
+
3
+ module JSON
4
+ class Patch
5
+ # This class registers our gem with Rails.
6
+ class Railtie < ::Rails::Railtie
7
+ # When the application loads, this will cause Rails to know
8
+ # # how to serve up the proper type.
9
+ initializer 'json-patch' do
10
+ Mime::Type.register 'application/json-patch+json', :json_patch
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Json
2
+ module Patch
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,77 @@
1
+ require 'test_helper'
2
+ require 'json/patch'
3
+
4
+ describe "IETF JSON Patch Test" do
5
+
6
+ TESTDIR = File.dirname File.expand_path __FILE__
7
+ spec_json = File.read File.join TESTDIR, 'json-patch-tests', 'spec_tests.json'
8
+ specs = JSON.load spec_json
9
+
10
+ describe "JSON.patch" do
11
+ specs.each_with_index do |spec, index|
12
+ next unless spec['doc']
13
+
14
+
15
+ #This test I am skipping
16
+ #Because it test if a operations object has two op members
17
+ #Since the first step in the process is to convert to hash
18
+ #it gets rid of the similar keys.
19
+ next if spec['comment'] == 'A.13 Invalid JSON Patch Document'
20
+
21
+
22
+ comment = spec['comment']
23
+ unless spec['disabled']
24
+
25
+ describe "A JSON String " do
26
+ it "#{comment || spec['error'] || index}" do
27
+
28
+ target_doc = JSON.dump(spec['doc']) if spec['doc']
29
+ operations_doc = JSON.dump(spec['patch']) if spec['patch']
30
+ expected_doc = JSON.dump(spec['expected']) if spec['expected']
31
+
32
+ if spec['error']
33
+ assert_raises(ex(spec['error'])) do
34
+ JSON.patch(target_doc, operations_doc)
35
+ end
36
+ else
37
+ result_doc = JSON.patch(target_doc, operations_doc)
38
+ assert_equal JSON.parse(expected_doc || target_doc), JSON.parse(result_doc)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "JSON::Patch.new" do
44
+ it "#{comment || spec['error'] || index}" do
45
+
46
+ target_doc = eval(spec['doc'].to_s) if spec['doc']
47
+ operations_doc = eval(spec['patch'].to_s) if spec['patch']
48
+ expected_doc = eval(spec['expected'].to_s) if spec['expected']
49
+
50
+ if spec['error']
51
+ assert_raises(ex(spec['error'])) do
52
+ JSON::Patch.new(target_doc, operations_doc).call
53
+ end
54
+ else
55
+ result_doc = JSON::Patch.new(target_doc, operations_doc).call
56
+ assert_equal (expected_doc || target_doc), result_doc
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+ def ex msg
67
+ case msg
68
+ when /Out of bounds/i then
69
+ JSON::PatchOutOfBoundException
70
+ when /Object operation on array target/ then
71
+ JSON::PatchObjectOperationOnArrayException
72
+ else
73
+ JSON::PatchError
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,71 @@
1
+ require 'test_helper'
2
+ require 'json/patch'
3
+
4
+ describe "IETF JSON Patch Test" do
5
+
6
+ TESTDIR = File.dirname File.expand_path __FILE__
7
+ spec_json = File.read File.join TESTDIR, 'json-patch-tests', 'tests.json'
8
+ specs = JSON.load spec_json
9
+
10
+ describe "Test JSON File" do
11
+ specs.each_with_index do |spec, index|
12
+ next unless spec['doc']
13
+ comment = spec['comment']
14
+ unless spec['disabled']
15
+
16
+ describe "JSON.patch" do
17
+ it "#{comment || spec['error'] || index}" do
18
+
19
+ target_doc = JSON.dump(spec['doc']) if spec['doc']
20
+ operations_doc = JSON.dump(spec['patch']) if spec['patch']
21
+ expected_doc = JSON.dump(spec['expected']) if spec['expected']
22
+
23
+ if spec['error']
24
+ assert_raises(ex(spec['error'])) do
25
+ JSON.patch(target_doc, operations_doc)
26
+ end
27
+ else
28
+ result_doc = JSON.patch(target_doc, operations_doc)
29
+ assert_equal JSON.parse(expected_doc || target_doc), JSON.parse(result_doc)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "JSON::Patch.new" do
35
+ it "#{comment || spec['error'] || index}" do
36
+
37
+ target_doc = spec['doc'] if spec['doc']
38
+ operations_doc = spec['patch'] if spec['patch']
39
+ expected_doc = spec['expected'] if spec['expected']
40
+
41
+ if spec['error']
42
+ assert_raises(ex(spec['error'])) do
43
+ JSON::Patch.new(target_doc, operations_doc).call
44
+ end
45
+ else
46
+ result_doc = JSON::Patch.new(target_doc, operations_doc).call
47
+ assert_equal (expected_doc || target_doc), result_doc
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+ def ex msg
57
+ case msg
58
+ when /Out of bounds/i then
59
+ JSON::PatchOutOfBoundException
60
+ when /Object operation on array target/i then
61
+ JSON::PatchObjectOperationOnArrayException
62
+ when /with bad number/i then
63
+ JSON::PatchObjectOperationOnArrayException
64
+ when /get array element 1/ then
65
+ JSON::PatchObjectOperationOnArrayException
66
+ else
67
+ JSON::PatchError
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,355 @@
1
+ require 'test_helper'
2
+ require 'json/patch'
3
+
4
+ describe "Section 4: Operation objects" do
5
+
6
+ describe "Operation objects MUST have at least one 'op' member" do
7
+ let(:target_document) { %q'{}' }
8
+ let(:operation_document) { %q'[{"path":"/a/b/c"}]' }
9
+
10
+ it "will raise exception when no 'op' member exist" do
11
+ assert_raises(JSON::PatchError) do
12
+ JSON.patch(target_document, operation_document)
13
+ end
14
+ end
15
+ end
16
+
17
+ describe "Operation objects 'op' member MUST be one of the correct values" do
18
+ let(:target_document) { %q'{ "foo":["bar","baz"] }' }
19
+ let(:add_operation_document) { %q'[{"op":"add","path":"/foo/1","value":"qux"}]' }
20
+ let(:remove_operation_document) { %q'[{ "op": "remove", "path": "/baz" }]' }
21
+ let(:replace_operation_document) { %q'[{"op":"replace","path":"/foo/1","value":"qux"}]' }
22
+ let(:move_operation_document) { %q'[{"op":"replace","from":"foo","path":"/foo/1","value":"qux"}]' }
23
+ let(:copy_operation_document) { %q'[{"op":"replace","from":"foo","path":"/foo/1","value":"qux"}]' }
24
+ let(:test_operation_document) { %q'[{"op":"test", "path":"/foo/1","value":"baz"}]' }
25
+ let(:error_operation_document) { %q'[{"op": "hammer time"}]' }
26
+
27
+ it "can contain a 'add' value" do
28
+ assert JSON.patch(target_document, add_operation_document)
29
+ end
30
+
31
+ it "can contain a 'remove' value" do
32
+ assert JSON.patch(target_document, remove_operation_document)
33
+ end
34
+
35
+ it "can contain a 'replace' value" do
36
+ assert JSON.patch(target_document, replace_operation_document)
37
+ end
38
+
39
+ it "can contain a 'move' value" do
40
+ assert JSON.patch(target_document, move_operation_document)
41
+ end
42
+
43
+ it "can contain a 'copy' value" do
44
+ assert JSON.patch(target_document, copy_operation_document)
45
+ end
46
+
47
+ it "can contain a 'test' value" do
48
+ assert JSON.patch(target_document, test_operation_document)
49
+ end
50
+
51
+ it "will raise exception when 'op' member contains invalid 'hammer time' value" do
52
+ assert_raises(JSON::PatchError) do
53
+ JSON.patch(target_document, error_operation_document)
54
+ end
55
+ end
56
+ end
57
+
58
+ describe "Operation objects MUST have at least one path member" do
59
+ let(:target_document) { %q'{ "foo":["bar","baz"] }' }
60
+ let(:operation_document) { %q'[{"op":"add", "value":"qux"}]' }
61
+
62
+ it "will raise exception when no 'path' member exist" do
63
+ assert_raises(JSON::PatchError) do
64
+ JSON.patch(target_document, operation_document)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "Operation members not define by the action MUST be ignored" do
70
+ let(:target_document) { %q'{ "foo":["bar","baz"] }' }
71
+ let(:operation_document) { %q'[{"op":"add","path":"/foo/1","value":"qux", "ignore":"This please"}]' }
72
+
73
+ it "ignores the 'ignore' member of the add operation_document" do
74
+ expected = %q'{"foo":["bar","qux","baz"]}'
75
+ assert_equal expected, JSON.patch(target_document, operation_document)
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ describe "Section 4.1: The add operation" do
82
+
83
+ describe "If the target location specifies an array index" do
84
+ let(:target_document) { %q'{ "foo":["bar","baz"] }' }
85
+ let(:operation_document) { %q'[{"op":"add","path":"/foo/1","value":"qux"}]' }
86
+
87
+ it "inserts the value into the array at specified index" do
88
+ expected = %q'{"foo":["bar","qux","baz"]}'
89
+ assert_equal expected, JSON.patch(target_document, operation_document)
90
+ end
91
+ end
92
+
93
+ describe "If the target location species a object member that does not exist" do
94
+ let(:target_document) { %q'{"foo":"bar"}' }
95
+ let(:operation_document) { %q'[{ "op": "add", "path": "/baz", "value": "qux" }]' }
96
+
97
+ it "it will add the object to the target_document" do
98
+ expected = %q'{"foo":"bar","baz":"qux"}'
99
+ assert_equal expected, JSON.patch(target_document, operation_document)
100
+ end
101
+ end
102
+
103
+ describe "If the target location species a member that does exist" do
104
+ let(:target_document) { %q'{"foo":"bar","baz":"wat"}' }
105
+ let(:operation_document) { %q'[{ "op": "add", "path": "/baz", "value": "qux" }]' }
106
+
107
+ it "it replaces the value" do
108
+ expected = %q'{"foo":"bar","baz":"qux"}'
109
+ assert_equal expected, JSON.patch(target_document, operation_document)
110
+ end
111
+ end
112
+
113
+ describe "The add operation MUST contina a 'value' member" do
114
+ let(:target_document) { %q'{"foo":"bar","baz":"wat"}' }
115
+ let(:operation_document) { %q'[{ "op": "add", "path": "/baz" }]' }
116
+
117
+ it "will raise exception if no 'value' member" do
118
+ assert_raises(JSON::PatchError) do
119
+ JSON.patch(target_document, operation_document)
120
+ end
121
+ end
122
+ end
123
+
124
+ =begin
125
+ TODO
126
+ When the operation is applied, the target location MUST reference one of:
127
+
128
+ 1. The root of the target document - whereupon the specified value
129
+ becomes the entire content of the target document.
130
+
131
+ 2. A member to add to an existing object - whereupon the supplied
132
+ value is added to that object at the indicated location. If the
133
+ member already exists, it is replaced by the specified value.
134
+
135
+ 3. An element to add to an existing array - whereupon the supplied
136
+ value is added to the array at the indicated location. Any
137
+ elements at or above the specified index are shifted one position
138
+ to the right. The specified index MUST NOT be greater than the
139
+ number of elements in the array. If the "-" character is used to
140
+ index the end of the array (see [RFC6901]), this has the effect of
141
+ appending the value to the array.
142
+ =end
143
+
144
+ end
145
+
146
+ describe "Section 4.2: The remove operation" do
147
+
148
+ describe "Removing a object member" do
149
+ let(:target_document) { %q'{"foo":"bar","baz":"qux"}' }
150
+ let(:operation_document) { %q'[{ "op": "remove", "path": "/baz" }]' }
151
+
152
+ it "will remove memeber of object at the target location" do
153
+ expected = %q'{"foo":"bar"}'
154
+ assert_equal expected, JSON.patch(target_document, operation_document)
155
+ end
156
+ end
157
+
158
+ describe "Removing a array element" do
159
+ let(:target_document) { %q'{"foo":["bar","qux","baz"]}' }
160
+ let(:operation_document) { %q'[{ "op": "remove", "path": "/foo/1" }]' }
161
+
162
+ it "will remove object in array at the target location" do
163
+ expected = %q'{"foo":["bar","baz"]}'
164
+ assert_equal expected, JSON.patch(target_document, operation_document)
165
+ end
166
+ end
167
+
168
+ describe "Target location MUST exist for the remove operation" do
169
+ let(:target_document) { %q'{"foo":["bar","qux","baz"]}' }
170
+ let(:operation_document) { %q'[{ "op": "remove"}]' }
171
+
172
+ it "will raise an exception if no target is specified" do
173
+ assert_raises(JSON::PatchError) do
174
+ JSON.patch(target_document, operation_document)
175
+ end
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ describe "Section 4.3: The replace operation" do
182
+
183
+ describe "Replacing a value" do
184
+ let(:target_document) { %q'{"foo":"bar","baz":"qux"}' }
185
+ let(:operation_document) { %q'[{ "op": "replace", "path": "/baz", "value": "boo" }]' }
186
+
187
+ it "will replace old value with a new value at target location" do
188
+ expected = %q'{"foo":"bar","baz":"boo"}'
189
+ assert_equal expected, JSON.patch(target_document, operation_document)
190
+ end
191
+ end
192
+
193
+ describe "The replace operation document MUST contain a 'value' member" do
194
+ let(:target_document) { %q'{"foo":"bar","baz":"qux"}' }
195
+ let(:operation_document) { %q'[{ "op": "replace", "path": "/baz" }]' }
196
+
197
+ it "will raise an exception if no 'value' is specified" do
198
+ assert_raises(JSON::PatchError) do
199
+ JSON.patch(target_document, operation_document)
200
+ end
201
+ end
202
+ end
203
+
204
+ describe "The replace operation MUST have a target location" do
205
+ let(:target_document) { %q'{"foo":"bar","baz":"qux"}' }
206
+ let(:operation_document) { %q'[{ "op": "replace", "value": "boo" }]' }
207
+
208
+ it "will raise an exception if no target is specified" do
209
+ assert_raises(JSON::PatchError) do
210
+ JSON.patch(target_document, operation_document)
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "Section 4.4: The move operation" do
217
+
218
+ describe "The move operation" do
219
+ let(:target_document) { %q'{"foo":{"bar":"baz","waldo":"fred"},"qux":{"corge":"grault"}}' }
220
+ let(:operation_document) { %q'[{ "op": "move", "from":"/foo/waldo", "path": "/qux/thud" }]' }
221
+
222
+ it "will remove the value at a specified location and add it to the target location" do
223
+ expected = %q'{"foo":{"bar":"baz"},"qux":{"corge":"grault","thud":"fred"}}'
224
+ assert_equal expected, JSON.patch(target_document, operation_document)
225
+ end
226
+ end
227
+
228
+ describe "The move operation" do
229
+ let(:target_document) { %q'{"foo":["add","grass","cows","eat"]}' }
230
+ let(:operation_document) { %q'[{ "op": "move", "from":"/foo/1", "path": "/foo/3" }]' }
231
+
232
+ it "will move a array element to new location" do
233
+ expected = %q'{"foo":["add","cows","eat","grass"]}'
234
+ assert_equal expected, JSON.patch(target_document, operation_document)
235
+ end
236
+ end
237
+
238
+ describe "The move operation MUST hav a 'from' memeber" do
239
+ let(:target_document) { %q'{"foo":"bar","baz":"qux"}' }
240
+ let(:operation_document) { %q'[{ "op": "move", "value": "boo" }]' }
241
+
242
+ it "will raise an exception if no from 'from' location is specified" do
243
+ assert_raises(JSON::PatchError) do
244
+ JSON.patch(target_document, operation_document)
245
+ end
246
+ end
247
+ end
248
+
249
+ =begin
250
+ TODO The "from" location MUST NOT be a proper prefix of the "path"
251
+ location; i.e., a location cannot be moved into one of its children.
252
+ =end
253
+
254
+ end
255
+
256
+ describe "Section 4.5: The copy operation" do
257
+
258
+ describe "The copy operation" do
259
+ let(:target_document) { %q'{"foo":{"bar":"baz","waldo":"fred"},"qux":{"corge":"grault"}}' }
260
+ let(:operation_document) { %q'[{ "op": "copy", "from":"/foo/waldo", "path": "/qux/waldo" }]' }
261
+
262
+ it "will copy a value from a specified location to the target location" do
263
+ expected = %q'{"foo":{"bar":"baz","waldo":"fred"},"qux":{"corge":"grault","waldo":"fred"}}'
264
+ assert_equal expected, JSON.patch(target_document, operation_document)
265
+ end
266
+ end
267
+
268
+ describe "The copy operation MUST have a 'from' member" do
269
+ let(:target_document) { %q'{"foo":"bar","baz":"qux"}' }
270
+ let(:operation_document) { %q'[{ "op": "copy", "path": "/foo" }]' }
271
+
272
+ it "will raise an exception if no 'from' location is specified" do
273
+ assert_raises(JSON::PatchError) do
274
+ JSON.patch(target_document, operation_document)
275
+ end
276
+ end
277
+ end
278
+
279
+ end
280
+
281
+ describe "Section 4.6: The test operation" do
282
+
283
+ #The "test" operation tests that a value at the target location is equal to a specified value.
284
+
285
+ describe "The test operation MUST contain a 'value' member" do
286
+ let(:target_document) { %q'{"baz":"qux","foo":["a",2,"c"]}' }
287
+ let(:operation_document) { %q'[{ "op": "test", "path": "/baz"}, {"op": "test", "path": "/foo/1"}]' }
288
+
289
+ it "will raise a exception if no 'value' is specified" do
290
+ assert_raises(JSON::PatchError) do
291
+ JSON.patch(target_document, operation_document)
292
+ end
293
+ end
294
+ end
295
+
296
+ describe "Testing that strings have the same number of Unicode characters and their code points are byte-to-byte equal" do
297
+ let(:target_document) { %q'{"baz":"qux","foo":["a",2,"c"]}' }
298
+ let(:operation_document) { %q'[{ "op": "test", "path": "/baz", "value": "qux"}]' }
299
+
300
+ it "will return true since the strings are equal" do
301
+ assert JSON.patch(target_document, operation_document)
302
+ end
303
+ end
304
+
305
+ describe "Testing that numbers are equal if their values are numerically equal" do
306
+ let(:target_document) { %q'{"baz": 1,"foo":["a",2,"c"]}' }
307
+ let(:operation_document) { %q'[{ "op": "test", "path": "/baz", "value": 1}]' }
308
+
309
+ it "will return true since the numbers are equal" do
310
+ assert JSON.patch(target_document, operation_document)
311
+ end
312
+ end
313
+
314
+ describe "Testing that arrays are equal if they contain then same number of values and these values are equal" do
315
+ let(:target_document) { %q'{"baz": 1,"foo":["a",2,"c"]}' }
316
+ let(:operation_document) { %q'[{"op": "test", "path": "/foo", "value": ["a",2,"c"]}]' }
317
+
318
+ it "will return true since arrays and values are equal" do
319
+ assert JSON.patch(target_document, operation_document)
320
+ end
321
+ end
322
+
323
+ describe "Testing that objects are equal if they contain then same number of members and each member has same keys and values" do
324
+ let(:target_document) { %q'{"baz": 1,"foo":{"foo": "bar","hammer": "time"}}' }
325
+ let(:operation_document) { %q'[{"op": "test", "path": "/foo", "value": {"foo": "bar", "hammer":"time"}}]' }
326
+
327
+ it "will return true since objects equal" do
328
+ assert JSON.patch(target_document, operation_document)
329
+ end
330
+ end
331
+
332
+ =begin
333
+ TODO
334
+ 5 literals (false, true, and null): are considered equal if they are
335
+ the same.
336
+
337
+ Also, note that ordering of the serialization of object members is
338
+ not significant.
339
+ =end
340
+
341
+ end
342
+
343
+ describe "JSON::Patch object" do
344
+
345
+ describe "JSON::Patch.new " do
346
+ let(:target_document) { {"foo" => { "bar" => "baz", "waldo" => "fred" }, "qux" => { "corge" => "grault" } } }
347
+ let(:operation_document) { [{ "op"=> "copy", "from" => "/foo/waldo", "path" => "/qux/waldo" }] }
348
+
349
+ it "can handle plain ruby objects" do
350
+ expected = {"foo"=>{"bar"=>"baz","waldo"=>"fred"},"qux"=>{"corge"=>"grault","waldo"=>"fred"}}
351
+ assert_equal expected, JSON::Patch.new(target_document, operation_document).call
352
+ end
353
+ end
354
+
355
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter do |source_file|
4
+ source_file.filename =~ /test/
5
+ end
6
+ end
7
+
8
+ require 'coveralls'
9
+ Coveralls.wear!
10
+
11
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json-patch
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Guille Carlos
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: ! 'An implementation of RFC 6902: JSON Patch.'
47
+ email:
48
+ - guille@bitpop.in
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - .gitmodules
55
+ - .travis.yml
56
+ - Gemfile
57
+ - LICENSE.txt
58
+ - README.md
59
+ - Rakefile
60
+ - json-patch.gemspec
61
+ - lib/json/patch.rb
62
+ - lib/json/patch/railtie.rb
63
+ - lib/json/patch/version.rb
64
+ - test/ietf_spec_test.rb
65
+ - test/ietf_test.rb
66
+ - test/json-patch_test.rb
67
+ - test/ruby_ietf_spec_test.rb
68
+ - test/test_helper.rb
69
+ homepage: https://github.com/guillec/json-patch
70
+ licenses:
71
+ - MIT
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.25
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: ! 'An implementation of RFC 6902: JSON Patch.'
94
+ test_files:
95
+ - test/ietf_spec_test.rb
96
+ - test/ietf_test.rb
97
+ - test/json-patch_test.rb
98
+ - test/ruby_ietf_spec_test.rb
99
+ - test/test_helper.rb
100
+ has_rdoc: