json_expressions 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Godfrey Chan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,361 @@
1
+ JSON Expressions
2
+ ================
3
+
4
+ ## Introduction
5
+
6
+ Your API is a contract between your service and your developers. Therefore it is important to know what exactly your JSON API is returning to the developers and make sure you don't accidentally change that contract without updating the documentations. Perhaps you should write some controller tests for your JSON endpoints:
7
+
8
+ ```ruby
9
+ # MiniTest::Unit example
10
+ class UsersControllerTest < MiniTest::Unit::TestCase
11
+ def test_get_a_user
12
+ server_response = get '/users/chancancode.json'
13
+
14
+ json = JSON.parse server_response.body
15
+
16
+ assert user = json['user']
17
+
18
+ assert user_id = user['id']
19
+ assert_equal 'chancancode', user['username']
20
+ assert_equal 'Godfrey Chan', user['full_name']
21
+ assert_equal 'godfrey@example.com', user['email']
22
+ assert_equal 'Administrator', user['type']
23
+ assert_kind_of Fixnum, user['points']
24
+ assert_match /\Ahttps?\:\/\/.*\z/i, user['homepage']
25
+
26
+ assert posts = user['posts']
27
+
28
+ assert_kind_of Fixnum, posts[0]['id']
29
+ assert_equal 'Hello world!', posts[0]['subject']
30
+ assert_equal user_id, posts[0]['user_id']
31
+
32
+ assert_kind_of Fixnum, posts[1]['id']
33
+ assert_equal 'Hello world!', posts[1]['subject']
34
+ assert_equal user_id, posts[1]['user_id']
35
+ end
36
+ end
37
+ ```
38
+
39
+ There are many problems with this approach of JSON matching:
40
+
41
+ * It could get out of hand really quickly
42
+ * It is not very readable
43
+ * It flattens the structure of the JSON and it's difficult to visualize what the JSON actually looks like
44
+ * It does not guard against extra parameters that you might have accidentally included (password hashes, credit card numbers etc)
45
+ * Matching nested objects and arrays is tricky, especially when you don't want to enforce a particular ordering of the returned objects
46
+
47
+ json_expression allows you to express the structure and content of the JSON you're expecting with very readable Ruby code while preserving the flexibility of the "manual" approach.
48
+
49
+ ## Dependencies
50
+
51
+ * Ruby 1.9+
52
+
53
+ ## Usage
54
+
55
+ Add it to your Gemfile:
56
+
57
+ ```ruby
58
+ gem 'json_expressions'
59
+ ```
60
+
61
+ Then add this to your test/spec helper file:
62
+
63
+ ```ruby
64
+ # MiniTest::Unit example
65
+ require 'json_expressions/minitest'
66
+ ```
67
+
68
+ Which allows you to do...
69
+
70
+ ```ruby
71
+ # MiniTest::Unit example
72
+ class UsersControllerTest < MiniTest::Unit::TestCase
73
+ def test_get_a_user
74
+ server_response = get '/users/chancancode.json'
75
+
76
+ # This is what we expect the returned JSON to look like
77
+ pattern = {
78
+ 'user' => {
79
+ 'id' => :user_id, # "Capture" this value for later
80
+ 'username' => 'chancancode', # Match this exact string
81
+ 'full_name' => 'Godfrey Chan',
82
+ 'email' => 'godfrey@example.com',
83
+ 'type' => 'Administrator',
84
+ 'points' => Fixnum, # Any integer value
85
+ 'homepage' => /\Ahttps?\:\/\/.*\z/i, # Let's get serious
86
+ 'created_at' => WILDCARD_MATCHER, # Don't care as long as it exists
87
+ 'updated_at' => WILDCARD_MATCHER,
88
+ 'posts' => [
89
+ {
90
+ 'id' => Fixnum,
91
+ 'subject' => 'Hello world!',
92
+ 'user_id' => :user_id # Match against the captured value
93
+ }.ignore_extra_keys!, # Skip the uninteresting stuff
94
+ {
95
+ 'id' => Fixnum,
96
+ 'subject' => 'An awesome blog post',
97
+ 'user_id' => :user_id
98
+ }.ignore_extra_keys!
99
+ ].ordered! # Ensure they are returned in this exact order
100
+ }
101
+ }
102
+
103
+ matcher = assert_json_match pattern, server_response.body # Returns the Matcher object
104
+
105
+ # You can use the captured values for other purposes
106
+ assert matcher.captures[:user_id] > 0
107
+ end
108
+ end
109
+ ```
110
+
111
+ ### Basic Matching
112
+
113
+ This pattern
114
+ ```ruby
115
+ {
116
+ 'integer' => 1,
117
+ 'float' => 1.1,
118
+ 'string' => 'Hello world!',
119
+ 'boolean' => true,
120
+ 'array' => [1,2,3],
121
+ 'object' => {'key1' => 'value1','key2' => 'value2'},
122
+ 'null' => nil,
123
+ }
124
+ ```
125
+ matches the JSON object
126
+ ```json
127
+ {
128
+ "integer": 1,
129
+ "float": 1.1,
130
+ "string": "Hello world!",
131
+ "boolean": true,
132
+ "array": [1,2,3],
133
+ "object": {"key1": "value1", "key2": "value2"},
134
+ "null": null
135
+ }
136
+ ```
137
+
138
+ ### Wildcard Matching
139
+
140
+ You can use the WILDCARD_MATCHER to ignore keys that you don't care about (other than the fact that the key is included in the JSON).
141
+
142
+ This pattern
143
+ ```ruby
144
+ [ WILDCARD_MATCHER, WILDCARD_MATCHER, WILDCARD_MATCHER, WILDCARD_MATCHER, WILDCARD_MATCHER, WILDCARD_MATCHER, WILDCARD_MATCHER ]
145
+ ```
146
+ matches the JSON array
147
+ ```json
148
+ [ 1, 1.1, "Hello world!", true, [1,2,3], {"key1": "value1","key2": "value2"}, null]
149
+ ```
150
+
151
+ Furthermore, because the pattern is expressed in plain old Ruby code, you can also write:
152
+ ```ruby
153
+ [ WILDCARD_MATCHER ] * 7
154
+ ```
155
+
156
+ ### Pattern Matching
157
+
158
+ When an object `.respond_to? :match`, `match` will be called to match against the corresponding value in the JSON. Notably, this includes `Regexp` objects, which means you can use regular expressions in your pattern:
159
+
160
+ ```ruby
161
+ { 'hex' => /\A0x[0-9a-f]+\z/i }
162
+ ```
163
+ matches
164
+ ```json
165
+ { "hex": "0xC0FFEE" }
166
+ ```
167
+ but not
168
+ ```json
169
+ { "hex": "Hello world!" }
170
+ ```
171
+
172
+ Sometimes this behavior is undesirable. For instance, String#match(other) converts `other` into a `Regexp` and use that to match against itself, which is probably not what you want (`'Hello wolrd!'.match '' => #<MatchData "">`!).
173
+
174
+ You can specific a list of classes/modules with undesirable `match` behavior, and json_expression will fall back to calling `===` on them instead (see the section below for `===` vs `==`).
175
+
176
+ ```ruby
177
+ # This is the default setting
178
+ JsonExpressions::Matcher.skip_match_on = [ String ]
179
+
180
+ # To add more modules/classes
181
+ # JsonExpressions::Matcher.skip_match_on << MyClass
182
+
183
+ # To turn this off completely
184
+ # JsonExpressions::Matcher.skip_match_on = [ BasicObject ]
185
+ ```
186
+
187
+ ### Type Matching
188
+
189
+ For objects that does not `respond_to? :match` or those you opt-ed out explicitly (such as `String`), `===` will be called to match against the corresponding value in the JSON. For most classes, this behaves identical to `==`. A notably exception would be `Module` (and by inheritance, `Class`) objects, which overrides `===` to mean `instance of`. You can exploit this behavior to do type matching:
190
+ ```ruby
191
+ {
192
+ 'integer' => Fixnum,
193
+ 'float' => Float,
194
+ 'string' => String,
195
+ 'boolean' => Boolean,
196
+ 'array' => Array,
197
+ 'object' => Hash,
198
+ 'null' => NilClass,
199
+ }
200
+ ```
201
+ matches the JSON object
202
+ ```json
203
+ {
204
+ "integer": 1,
205
+ "float": 1.1,
206
+ "string": "Hello world!",
207
+ "boolean": true,
208
+ "array": [1,2,3],
209
+ "object": {"key1": "value1", "key2": "value2"},
210
+ "null": null
211
+ }
212
+ ```
213
+
214
+ You can specific a list of classes/modules with undesirable `===` behavior, and json_expression will fall back to calling `==` on them instead.
215
+
216
+ ```ruby
217
+ # This is the default setting
218
+ JsonExpressions::Matcher.skip_triple_equal_on = [ ]
219
+
220
+ # To add more modules/classes
221
+ # JsonExpressions::Matcher.skip_triple_equal_on << MyClass
222
+
223
+ # To turn this off completely
224
+ # JsonExpressions::Matcher.skip_triple_equal_on = [ BasicObject ]
225
+ ```
226
+
227
+ ### Capturing
228
+
229
+ Similar to how "capturs" work in Regex, you can capture the value of certain keys for later use:
230
+ ```ruby
231
+ matcher = JsonExpressions::Matcher.new({
232
+ 'key1' => :key1,
233
+ 'key2' => :key2,
234
+ 'key3' => :key3
235
+ })
236
+
237
+ matcher =~ JSON.parse('{"key1":"value1", "key2":"value2", "key3":"value3"}') # => true
238
+
239
+ matcher.captures[:key1] # => "value1"
240
+ matcher.captures[:key2] # => "value2"
241
+ matcher.captures[:key3] # => "value3"
242
+ ```
243
+
244
+ If the same symbol is used multiple times, json_expression will make sure they agree. This pattern
245
+ ```ruby
246
+ {
247
+ 'key1' => :capture_me,
248
+ 'key2' => :capture_me,
249
+ 'key3' => :capture_me
250
+ }
251
+ ```
252
+ matches
253
+ ```json
254
+ {
255
+ "key1": "Hello world!",
256
+ "key2": "Hello world!",
257
+ "key3": "Hello world!"
258
+ }
259
+ ```
260
+ but not
261
+ ```json
262
+ {
263
+ "key1": "value1",
264
+ "key2": "value2",
265
+ "key3": "value3"
266
+ }
267
+ ```
268
+
269
+ ### Ordering
270
+
271
+ By default, all arrays and JSON objects (i.e. Ruby hashes) are assumed to be unordered. This means
272
+ ```ruby
273
+ [ 1, 2, 3, 4, 5 ]
274
+ ```
275
+ will match
276
+ ```json
277
+ [ 5, 3, 2, 1, 4 ]
278
+ ```
279
+ and
280
+ ```ruby
281
+ { 'key1' => 'value1', 'key2' => 'value2' }
282
+ ```
283
+ will match
284
+ ```json
285
+ { "key2": "value2", "key1": "value1" }
286
+ ```
287
+
288
+ You can change this behavior in a case-by-case manner:
289
+ ```ruby
290
+ {
291
+ "unordered_array" => [1,2,3,4,5].unordered!, # calling unordered! is optional as it's the default
292
+ "ordered_array" => [1,2,3,4,5].ordered!,
293
+ "unordered_hash" => {'a'=>1, 'b'=>2}.unordered!,
294
+ "ordered_hash" => {'a'=>1, 'b'=>2}.ordered!
295
+ }
296
+ ```
297
+
298
+ Or you can change the defaults:
299
+ ```ruby
300
+ # Default for these are true
301
+ JsonExpressions::Matcher.assume_unordered_arrays = false
302
+ JsonExpressions::Matcher.assume_unordered_hashes = false
303
+ ```
304
+
305
+ ### Strictness
306
+
307
+ By default, all arrays and JSON objects (i.e. Ruby hashes) are assumed to be "strict". This means any extra elements or keys in the JSON target will cause the match to fail:
308
+ ```ruby
309
+ [ 1, 2, 3, 4, 5 ]
310
+ ```
311
+ will not match
312
+ ```json
313
+ [ 1, 2, 3, 4, 5, 6 ]
314
+ ```
315
+ and
316
+ ```ruby
317
+ { 'key1' => 'value1', 'key2' => 'value2' }
318
+ ```
319
+ will not match
320
+ ```json
321
+ { "key1": "value1", "key2": "value2", "key3": "value3" }
322
+ ```
323
+
324
+ You can change this behavior in a case-by-case manner:
325
+ ```ruby
326
+ {
327
+ "strict_array" => [1,2,3,4,5].strict!, # calling strict! is optional as it's the default
328
+ "forgiving_array" => [1,2,3,4,5].forgiving!,
329
+ "strict_hash" => {'a'=>1, 'b'=>2}.strict!,
330
+ "forgiving_hash" => {'a'=>1, 'b'=>2}.forgiving!
331
+ }
332
+ ```
333
+
334
+ They also come with some more sensible aliases:
335
+ ```ruby
336
+ {
337
+ "strict_array" => [1,2,3,4,5].reject_extra_values!,
338
+ "forgiving_array" => [1,2,3,4,5].ignore_extra_values!,
339
+ "strict_hash" => {'a'=>1, 'b'=>2}.reject_extra_keys!,
340
+ "forgiving_hash" => {'a'=>1, 'b'=>2}.ignore_extra_keys!
341
+ }
342
+ ```
343
+
344
+ Or you can change the defaults:
345
+ ```ruby
346
+ # Default for these are true
347
+ JsonExpressions::Matcher.assume_strict_arrays = false
348
+ JsonExpressions::Matcher.assume_strict_hashes = false
349
+ ```
350
+
351
+ ## Testing Frameworks Support
352
+
353
+ The `Matcher` class itself is written in a testing-framework-agnostic manner. This allows you to easily write custom helpers/matchers for your favorite testing framework. Currently, the gem only ships with helpers for `MiniTest::Unit`. `MiniTest::Spec` and `RSpec` are on my TODO list, but it is not a high priority for me personally, as I currently don't use these frameworks actively in my projects. If you need this now, write it yourself (and submit a pull request) - it's really easy, I promise (see `lib/json_expressions/minitest/unit/helpers.rb` for inspiration).
354
+
355
+ ## Contributing
356
+
357
+ Please use the [GitHub issue tracker](https://github.com/chancancode/json_expressions/issues) for bugs and feature requests. If you could submit a pull request - that's even better!
358
+
359
+ ## License
360
+
361
+ This library is distributed under the MIT license. Please see the LICENSE file.
@@ -0,0 +1,68 @@
1
+ module JsonExpressions
2
+ module Strict; end
3
+ module Forgiving; end
4
+ module Ordered; end
5
+ module Unordered; end
6
+
7
+ module CoreExtensions
8
+ def ordered?
9
+ self.is_a? Ordered
10
+ end
11
+
12
+ def unordered?
13
+ self.is_a? Unordered
14
+ end
15
+
16
+ def ordered!
17
+ if self.unordered?
18
+ raise "cannot mark an unordered #{self.class} as ordered!"
19
+ else
20
+ self.extend Ordered
21
+ end
22
+ end
23
+
24
+ def unordered!
25
+ if self.ordered?
26
+ raise "cannot mark an ordered #{self.class} as unordered!"
27
+ else
28
+ self.extend Unordered
29
+ end
30
+ end
31
+
32
+ def strict?
33
+ self.is_a? Strict
34
+ end
35
+
36
+ def forgiving?
37
+ self.is_a? Forgiving
38
+ end
39
+
40
+ def strict!
41
+ if self.forgiving?
42
+ raise "cannot mark a forgiving #{self.class} as strict!"
43
+ else
44
+ self.extend Strict
45
+ end
46
+ end
47
+
48
+ def forgiving!
49
+ if self.strict?
50
+ raise "cannot mark a strict #{self.class} as forgiving!"
51
+ else
52
+ self.extend Forgiving
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ class Hash
59
+ include JsonExpressions::CoreExtensions
60
+ alias_method :reject_extra_keys!, :strict!
61
+ alias_method :ignore_extra_keys!, :forgiving!
62
+ end
63
+
64
+ class Array
65
+ include JsonExpressions::CoreExtensions
66
+ alias_method :reject_extra_values!, :strict!
67
+ alias_method :ignore_extra_values!, :forgiving!
68
+ end
@@ -0,0 +1,227 @@
1
+ require 'json_expressions'
2
+ require 'json_expressions/core_extensions'
3
+
4
+ module JsonExpressions
5
+ class Matcher
6
+ class << self
7
+ # JsonExpressions::Matcher.skip_match_on (Array)
8
+ # An array of classes and modules with undesirable `match` behavior
9
+ # Default: [ String ]
10
+ attr_accessor :skip_match_on
11
+ JsonExpressions::Matcher.skip_match_on = [ String ]
12
+
13
+ # JsonExpressions::Matcher.skip_triple_equal_on (Array)
14
+ # An array of classes and modules with undesirable `===` behavior
15
+ # Default: []
16
+ attr_accessor :skip_triple_equal_on
17
+ JsonExpressions::Matcher.skip_triple_equal_on = []
18
+
19
+ # JsonExpressions::Matcher.assume_unordered_arrays (Boolean)
20
+ # By default, assume arrays are unordered when not specified
21
+ # Default: true
22
+ attr_accessor :assume_unordered_arrays
23
+ JsonExpressions::Matcher.assume_unordered_arrays = true
24
+
25
+ # JsonExpressions::Matcher.assume_strict_arrays (Boolean)
26
+ # By default, reject arrays with extra elements when not specified
27
+ # Default: true
28
+ attr_accessor :assume_strict_arrays
29
+ JsonExpressions::Matcher.assume_strict_arrays = true
30
+
31
+ # JsonExpressions::Matcher.assume_unordered_hashes (Boolean)
32
+ # By default, assume hashes are unordered when not specified
33
+ # Default: true
34
+ attr_accessor :assume_unordered_hashes
35
+ JsonExpressions::Matcher.assume_unordered_hashes = true
36
+
37
+ # JsonExpressions::Matcher.assume_strict_hashes (Boolean)
38
+ # By default, reject hashes with extra keys when not specified
39
+ # Default: true
40
+ attr_accessor :assume_strict_hashes
41
+ JsonExpressions::Matcher.assume_strict_hashes = true
42
+ end
43
+
44
+ attr_reader :last_error
45
+ attr_reader :captures
46
+
47
+ def initialize(json, options = {})
48
+ defaults = {}
49
+ @json = json
50
+ @options = defaults.merge(options)
51
+ reset!
52
+ end
53
+
54
+ def =~(other)
55
+ reset!
56
+ match_json('(JSON ROOT)', @json, other)
57
+ end
58
+
59
+ def match(other)
60
+ self =~ other
61
+ end
62
+
63
+ def to_s
64
+ @json.to_s
65
+ end
66
+
67
+ private
68
+
69
+ def reset!
70
+ @last_errot = nil
71
+ @captures = {}
72
+ end
73
+
74
+ def match_json(path, matcher, other)
75
+ if matcher.is_a? Symbol
76
+ capture path, matcher, other
77
+ elsif matcher.is_a? Array
78
+ match_array path, matcher, other
79
+ elsif matcher.is_a? Hash
80
+ match_hash path, matcher, other
81
+ elsif matcher.respond_to?(:match) && matchable?(matcher)
82
+ match_obj path, matcher, other, :match
83
+ elsif triple_equable?(matcher)
84
+ match_obj path, matcher, other, :===
85
+ else
86
+ match_obj path, matcher, other, :==
87
+ end
88
+ end
89
+
90
+ def capture(path, name, value)
91
+ if @captures.key? name
92
+ if match_json nil, @captures[name], value
93
+ true
94
+ else
95
+ set_last_error path, "At %path%: expected capture with key #{name.inspect} and value #{@captures[name]} to match #{value.inspect}"
96
+ false
97
+ end
98
+ else
99
+ @captures[name] = value
100
+ true
101
+ end
102
+ end
103
+
104
+ def match_obj(path, matcher, other, meth)
105
+ if matcher.__send__ meth, other
106
+ true
107
+ else
108
+ set_last_error path, "At %path%: expected #{matcher.inspect} to match #{other.inspect}"
109
+ return false
110
+ end
111
+ end
112
+
113
+ def match_array(path, matcher, other)
114
+ unless other.is_a? Array
115
+ set_last_error path, "%path% is not an array"
116
+ return false
117
+ end
118
+
119
+ apply_array_defaults matcher
120
+
121
+ if matcher.size > other.size
122
+ set_last_error path, "%path% contains too few elements (#{matcher.size} expected but was #{other.size})"
123
+ return false
124
+ end
125
+
126
+ if matcher.strict? && matcher.size < other.size
127
+ set_last_error path, "%path% contains too many elements (#{matcher.size} expected but was #{other.size})"
128
+ return false
129
+ end
130
+
131
+ if matcher.ordered?
132
+ matcher.zip(other).each_with_index { |(v1,v2),i| return false unless match_json(make_path(path,i), v1, v2) }
133
+ else
134
+ other = other.clone
135
+
136
+ matcher.all? do |v1|
137
+ if i = other.find_index { |v2| match_json(nil, v1, v2) }
138
+ other.delete_at i
139
+ true
140
+ else
141
+ set_last_error path, "%path% does not contain an element matching #{v1.inspect}"
142
+ false
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def match_hash(path, matcher, other)
149
+ unless other.is_a? Hash
150
+ set_last_error path, "%path% is not a hash"
151
+ return false
152
+ end
153
+
154
+ apply_hash_defaults matcher
155
+
156
+ missing_keys = matcher.keys - other.keys
157
+ extra_keys = other.keys - matcher.keys
158
+
159
+ unless missing_keys.empty?
160
+ set_last_error path, "%path% does not contain the key #{missing_keys.first}"
161
+ return false
162
+ end
163
+
164
+ if matcher.strict? && ! extra_keys.empty?
165
+ set_last_error path, "%path% contains an extra key #{extra_keys.first}"
166
+ return false
167
+ end
168
+
169
+ if matcher.ordered? && matcher.keys != other.keys
170
+ set_last_error path, "Incorrect key-ordering at %path% (#{matcher.keys.inspect} expected but was #{other.keys.inspect})"
171
+ return false
172
+ end
173
+
174
+ matcher.keys.all? { |k| match_json make_path(path,k), matcher[k], other[k] }
175
+ end
176
+
177
+ def set_last_error(path, message)
178
+ @last_error = message.gsub('%path%',path) if path
179
+ end
180
+
181
+ def make_path(path, segment)
182
+ if path
183
+ if segment.is_a? Fixnum
184
+ path + "[#{segment}]"
185
+ else
186
+ path + ".#{segment}"
187
+ end
188
+ end
189
+ end
190
+
191
+ def apply_array_defaults(array)
192
+ if ! array.ordered? && ! array.unordered?
193
+ self.class.assume_unordered_arrays ? array.unordered! : array.ordered!
194
+ end
195
+
196
+ if ! array.strict? && ! array.forgiving?
197
+ self.class.assume_strict_arrays ? array.strict! : array.forgiving!
198
+ end
199
+ end
200
+
201
+ def apply_hash_defaults(hash)
202
+ if ! hash.ordered? && ! hash.unordered?
203
+ self.class.assume_unordered_hashes ? hash.unordered! : hash.ordered!
204
+ end
205
+
206
+ if ! hash.strict? && ! hash.forgiving?
207
+ self.class.assume_strict_hashes ? hash.strict! : hash.forgiving!
208
+ end
209
+ end
210
+
211
+ def matchable?(obj)
212
+ if self.class.skip_match_on.include? obj.class
213
+ false
214
+ else
215
+ self.class.skip_match_on.none? { |klass| obj.is_a? klass }
216
+ end
217
+ end
218
+
219
+ def triple_equable?(obj)
220
+ if self.class.skip_triple_equal_on.include? obj.class
221
+ false
222
+ else
223
+ self.class.skip_triple_equal_on.none? { |klass| obj.is_a? klass }
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,24 @@
1
+ require 'json'
2
+
3
+ module JsonExpressions
4
+ module MiniTest
5
+ module Unit
6
+ module Helpers
7
+ def assert_json_match(exp, act, msg = nil)
8
+ unless JsonExpressions::Matcher === exp
9
+ exp = JsonExpressions::Matcher.new(exp)
10
+ end
11
+
12
+ if String === act
13
+ assert act = JSON.parse(act), "Expected #{mu_pp(act)} to be valid JSON"
14
+ end
15
+
16
+ assert exp =~ act, ->{exp.last_error}
17
+
18
+ # Return the matcher
19
+ return exp
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ require 'json_expressions'
2
+ require 'json_expressions/minitest/unit/helpers'
3
+
4
+ class MiniTest::Unit::TestCase
5
+ include JsonExpressions::MiniTest::Unit::Helpers
6
+ WILDCARD_MATCHER = JsonExpressions::WILDCARD_MATCHER
7
+ end
@@ -0,0 +1,25 @@
1
+ require 'json_expressions/matcher'
2
+
3
+ module JsonExpressions
4
+ WILDCARD_MATCHER = Object.new
5
+
6
+ def WILDCARD_MATCHER.is_a?(klass)
7
+ false
8
+ end
9
+
10
+ def WILDCARD_MATCHER.==(other)
11
+ true
12
+ end
13
+
14
+ def WILDCARD_MATCHER.=~(other)
15
+ true
16
+ end
17
+
18
+ def WILDCARD_MATCHER.match(other)
19
+ true
20
+ end
21
+
22
+ def WILDCARD_MATCHER.to_s
23
+ 'WILDCARD_MATCHER'
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_expressions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Godfrey Chan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: JSON matchmaking for all your API testing needs.
15
+ email:
16
+ - godfreykfc@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/json_expressions/core_extensions.rb
22
+ - lib/json_expressions/matcher.rb
23
+ - lib/json_expressions/minitest/unit/helpers.rb
24
+ - lib/json_expressions/minitest.rb
25
+ - lib/json_expressions.rb
26
+ - README.md
27
+ - LICENSE
28
+ homepage: https://github.com/chancancode/json_expressions
29
+ licenses: []
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.3.6
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 1.8.24
49
+ signing_key:
50
+ specification_version: 3
51
+ summary: JSON Expressions
52
+ test_files: []