json-patch 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: