muskox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3510d3fe36c918d6bd4d03adcb3c0e7c2d97b483
4
+ data.tar.gz: c363d6428df50d83487ef2225f7a0406ee070a44
5
+ SHA512:
6
+ metadata.gz: aeed441cc11da57916011743180f57bbae7e558aed19e5b1580858674b379e0de610602299d611b345b658e169bc4a13b42f755be1ba7cbf47359179500a946a
7
+ data.tar.gz: 5a9120f14d48e687e2fd836bb85bd83bc93de4ef3bf5a90543f420056ba7e6129dbb33e597f3ad55719b49a8f2014d374627c1ae98a6b648ab4ece611e82def9
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,3 @@
1
+ [submodule "json_schema_test_suite"]
2
+ path = json_schema_test_suite
3
+ url = https://github.com/json-schema/JSON-Schema-Test-Suite.git
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in muskox.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nick Howard
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,62 @@
1
+ # Muskox
2
+
3
+ A JSON Parser-Generator that takes a json-schema and converts it into a parser.
4
+
5
+ ## Why?
6
+
7
+ Using a parser to handle inputs makes your app safe from attacks that rely on passing disallowed params, because disallowed params will either be ignored or rejected.
8
+
9
+ > Be definite about what you accept.(*)
10
+ >
11
+ > Treat inputs as a language, accept it with a matching computational
12
+ > power, generate its recognizer from its grammar.
13
+ >
14
+ > Treat input-handling computational power as privilege, and reduce it
15
+ > whenever possible.
16
+
17
+ http://www.cs.dartmouth.edu/~sergey/langsec/postel-principle-patch.txt
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ gem 'muskox'
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install muskox
32
+
33
+ ## Usage
34
+
35
+ ```ruby
36
+ # to generate a parser, call generate w/ a JSON-Schema
37
+ parser = Muskox.generate({
38
+ "title" => "Schema",
39
+ "type" => "object",
40
+ "properties" => {
41
+ "number" => {
42
+ "type" => "integer"
43
+ }
44
+ },
45
+ "required" => ["number"]
46
+ })
47
+
48
+ # then call parse with the string you want to have parsed
49
+ n = parser.parse "{\"number\": 1}"
50
+ # => {"number" => 1}
51
+
52
+ # invalid types are disallowed
53
+ parser.parse "{\"number\": true}" rescue puts $!
54
+ ```
55
+
56
+ ## Contributing
57
+
58
+ 1. Fork it
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create new Pull Request
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ Rake::TestTask.new :spec do |t|
4
+ t.libs << "spec"
5
+ t.test_files = FileList['spec/*_spec.rb']
6
+ t.verbose = true
7
+ end
8
+
9
+ task :default => :spec
@@ -0,0 +1,165 @@
1
+ require "muskox/version"
2
+ require "muskox/json_lexer"
3
+
4
+ module Muskox
5
+ def self.generate schema
6
+ Parser.new schema
7
+ end
8
+
9
+ class Parser
10
+ ROOT = nil
11
+ attr_reader :schema
12
+ def initialize schema
13
+ @schema = schema
14
+ end
15
+
16
+ def parse input
17
+ r = nil
18
+ schema_stack = [schema]
19
+ stack = [[ROOT, ROOT]]
20
+ lexer = Pure::Lexer.new input, :quirks_mode => true
21
+ lexer.lex do |type, value|
22
+ # puts "token #{type}: #{value}"
23
+ # puts "stack #{stack.inspect}"
24
+ # puts "schema stack #{schema_stack.last["type"]}"
25
+ case type
26
+ when :property
27
+ if schema_stack.last["properties"] && schema_stack.last["properties"].keys.include?(value)
28
+ stack.push [type, value]
29
+ else
30
+ raise ParserError, "Unexpected property: #{value}"
31
+ end
32
+ when :array_begin
33
+ case stack.last.first
34
+ when :property
35
+ last = stack.last
36
+ matching_type expected_type(schema_stack.last, last), "array" do
37
+ stack.push [:array, []]
38
+ schema_stack.push(schema["properties"][last.last])
39
+ end
40
+ when ROOT
41
+ stack.push [:array, []]
42
+ else
43
+ raise "unknown stack type #{stack.last}"
44
+ end
45
+ when :array_end
46
+ array_top = stack.pop
47
+
48
+ case stack.last.first
49
+ when :property
50
+ schema_stack.pop
51
+ last = stack.pop
52
+ matching_type expected_type(schema_stack.last, last), "array" do
53
+ stack.last.last[last.last] = array_top.last
54
+ end
55
+ when :array
56
+ matching_type expected_type(schema_stack.last, last), "array" do
57
+ stack.last.last << array_top.last
58
+ end
59
+ when ROOT
60
+ matching_type schema_stack.last["type"], "array" do
61
+ r = stack.last.last
62
+ end
63
+ else
64
+ raise "unknown stack type #{stack.last}"
65
+ end
66
+ when :object_begin
67
+ case stack.last.first
68
+ when :property
69
+ last = stack.last
70
+ matching_type expected_type(schema_stack.last, last), "object" do
71
+ stack.push [:object, {}]
72
+ schema_stack.push(schema["properties"][last.last])
73
+ end
74
+ when ROOT
75
+ stack.push [:object, {}]
76
+ end
77
+ when :object_end
78
+ object_top = stack.pop
79
+
80
+ if schema_stack.last["required"] && !(schema_stack.last["required"] - object_top.last.keys).empty?
81
+ raise ParserError
82
+ end
83
+
84
+ case stack.last.first
85
+ when :property
86
+ schema_stack.pop
87
+ last = stack.pop
88
+ matching_type expected_type(schema_stack.last, last), "object", stack.last.first == :object do
89
+ stack.last.last[last.last] = object_top.last
90
+ end
91
+ when ROOT
92
+ matching_type schema_stack.last["type"], "object" do
93
+ r = object_top.last
94
+ end
95
+ else
96
+ raise "unknown stack type #{stack.last.first}"
97
+ end
98
+ when :integer, :string, :float, :boolean, :null
99
+ case stack.last.first
100
+ when :property
101
+ last = stack.pop
102
+ matching_type expected_type(schema_stack.last, last), type, stack.last.first == :object do
103
+ stack.last.last[last.last] = value
104
+ end
105
+ when :array
106
+ case schema_stack.last["items"]
107
+ when Hash
108
+ matching_type schema_stack.last["items"]["type"], type do
109
+ stack.last.last << value
110
+ end
111
+ when Array
112
+ matching_type schema_stack.last["items"][stack.last.last.size]["type"], type do
113
+ stack.last.last << value
114
+ end
115
+ else
116
+ raise "Unexpected items type #{schema_stack.last["items"]}"
117
+ end
118
+ when ROOT
119
+ matching_type schema_stack.last["type"], type do
120
+ r = stack.last.last
121
+ end
122
+ else
123
+ raise "unknown stack type #{stack.last.inspect}"
124
+ end
125
+ else
126
+ raise "unhandled token type: #{type}: #{value}"
127
+ end
128
+ end
129
+ r
130
+ end
131
+
132
+ def expected_type schema, last
133
+ schema["properties"][last.last] && schema["properties"][last.last]["type"]
134
+ end
135
+
136
+ def matching_type expected, actual, opt=true
137
+ if is_type(expected, actual.to_s) && opt
138
+ yield
139
+ else
140
+ raise ParserError, "expected node of type #{expected} but was #{actual}"
141
+ end
142
+ end
143
+
144
+ TYPE_WIDENINGS = {
145
+ 'integer' => 'number',
146
+ 'float' => 'number'
147
+ }
148
+ def is_type expected, actual
149
+ case expected
150
+ when String
151
+ expected == actual || expected == TYPE_WIDENINGS[actual]
152
+ when Array
153
+ expected.any? {|e| is_type e, actual }
154
+ when nil
155
+ true # is this really what the spec wants? really?
156
+ else
157
+ raise "unexpected type comparison #{expected}, #{actual}"
158
+ end
159
+ end
160
+ end
161
+
162
+ class ParserError < StandardError
163
+ end
164
+
165
+ end
@@ -0,0 +1,336 @@
1
+ # comes from https://github.com/flori/json/blob/master/lib/json/pure/parser.rb + modifications to make it a lexer
2
+ # terrible, I know, but this is a hack afterall
3
+
4
+ require 'strscan'
5
+
6
+ module Muskox
7
+ module Pure
8
+ class Lexer < StringScanner
9
+ STRING = /" ((?:[^\x0-\x1f"\\] |
10
+ # escaped special characters:
11
+ \\["\\\/bfnrt] |
12
+ \\u[0-9a-fA-F]{4} |
13
+ # match all but escaped special characters:
14
+ \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
15
+ "/nx
16
+ INTEGER = /(-?0|-?[1-9]\d*)/
17
+ FLOAT = /(-?
18
+ (?:0|[1-9]\d*)
19
+ (?:
20
+ \.\d+(?i:e[+-]?\d+) |
21
+ \.\d+ |
22
+ (?i:e[+-]?\d+)
23
+ )
24
+ )/x
25
+ NAN = /NaN/
26
+ INFINITY = /Infinity/
27
+ MINUS_INFINITY = /-Infinity/
28
+ OBJECT_OPEN = /\{/
29
+ OBJECT_CLOSE = /\}/
30
+ ARRAY_OPEN = /\[/
31
+ ARRAY_CLOSE = /\]/
32
+ PAIR_DELIMITER = /:/
33
+ COLLECTION_DELIMITER = /,/
34
+ TRUE = /true/
35
+ FALSE = /false/
36
+ NULL = /null/
37
+ IGNORE = %r(
38
+ (?:
39
+ //[^\n\r]*[\n\r]| # line comments
40
+ /\* # c-style comments
41
+ (?:
42
+ [^*/]| # normal chars
43
+ /[^*]| # slashes that do not start a nested comment
44
+ \*[^/]| # asterisks that do not end this comment
45
+ /(?=\*/) # single slash before this comment's end
46
+ )*
47
+ \*/ # the End of this comment
48
+ |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
49
+ )+
50
+ )mx
51
+
52
+ UNPARSED = Object.new
53
+
54
+ # Creates a new JSON::Pure::Parser instance for the string _source_.
55
+ #
56
+ # It will be configured by the _opts_ hash. _opts_ can have the following
57
+ # keys:
58
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
59
+ # structures. Disable depth checking with :max_nesting => false|nil|0,
60
+ # it defaults to 100.
61
+ # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
62
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
63
+ # to false.
64
+ # * *symbolize_names*: If set to true, returns symbols for the names
65
+ # (keys) in a JSON object. Otherwise strings are returned, which is also
66
+ # the default.
67
+ # * *quirks_mode*: Enables quirks_mode for parser, that is for example
68
+ # parsing single JSON values instead of documents is possible.
69
+ def initialize(source, opts = {})
70
+ opts ||= {}
71
+ unless @quirks_mode = opts[:quirks_mode]
72
+ source = convert_encoding source
73
+ end
74
+ super source
75
+ if !opts.key?(:max_nesting) # defaults to 100
76
+ @max_nesting = 100
77
+ elsif opts[:max_nesting]
78
+ @max_nesting = opts[:max_nesting]
79
+ else
80
+ @max_nesting = 0
81
+ end
82
+ @allow_nan = !!opts[:allow_nan]
83
+ @symbolize_names = !!opts[:symbolize_names]
84
+ @match_string = opts[:match_string]
85
+ end
86
+
87
+ alias source string
88
+
89
+ def quirks_mode?
90
+ !!@quirks_mode
91
+ end
92
+
93
+ def reset
94
+ super
95
+ @current_nesting = 0
96
+ end
97
+
98
+ # Parses the current JSON string _source_ and returns the complete data
99
+ # structure as a result.
100
+ def lex &block
101
+ @callback = block
102
+ reset
103
+ if @quirks_mode
104
+ while !eos? && skip(IGNORE)
105
+ end
106
+ if eos?
107
+ raise ParserError, "source did not contain any JSON!"
108
+ else
109
+ obj = lex_value
110
+ obj == UNPARSED and raise ParserError, "source did not contain any JSON!"
111
+ end
112
+ else
113
+ until eos?
114
+ case
115
+ when scan(OBJECT_OPEN)
116
+ # obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
117
+ @current_nesting = 1
118
+ lex_object
119
+ when scan(ARRAY_OPEN)
120
+ # obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
121
+ @current_nesting = 1
122
+ lex_array
123
+ when skip(IGNORE)
124
+ ;
125
+ else
126
+ raise ParserError, "source '#{peek(20)}' not in JSON!"
127
+ end
128
+ end
129
+ # obj or raise ParserError, "source did not contain any JSON!"
130
+ end
131
+ # obj
132
+ end
133
+
134
+ private
135
+
136
+ def convert_encoding(source)
137
+ if source.respond_to?(:to_str)
138
+ source = source.to_str
139
+ else
140
+ raise TypeError, "#{source.inspect} is not like a string"
141
+ end
142
+ if defined?(::Encoding)
143
+ if source.encoding == ::Encoding::ASCII_8BIT
144
+ b = source[0, 4].bytes.to_a
145
+ source =
146
+ case
147
+ when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
148
+ source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8)
149
+ when b.size >= 4 && b[0] == 0 && b[2] == 0
150
+ source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8)
151
+ when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
152
+ source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8)
153
+ when b.size >= 4 && b[1] == 0 && b[3] == 0
154
+ source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8)
155
+ else
156
+ source.dup
157
+ end
158
+ else
159
+ source = source.encode(::Encoding::UTF_8)
160
+ end
161
+ source.force_encoding(::Encoding::ASCII_8BIT)
162
+ else
163
+ b = source
164
+ source =
165
+ case
166
+ when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
167
+ JSON.iconv('utf-8', 'utf-32be', b)
168
+ when b.size >= 4 && b[0] == 0 && b[2] == 0
169
+ JSON.iconv('utf-8', 'utf-16be', b)
170
+ when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
171
+ JSON.iconv('utf-8', 'utf-32le', b)
172
+ when b.size >= 4 && b[1] == 0 && b[3] == 0
173
+ JSON.iconv('utf-8', 'utf-16le', b)
174
+ else
175
+ b
176
+ end
177
+ end
178
+ source
179
+ end
180
+
181
+ # Unescape characters in strings.
182
+ UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
183
+ UNESCAPE_MAP.update({
184
+ ?" => '"',
185
+ ?\\ => '\\',
186
+ ?/ => '/',
187
+ ?b => "\b",
188
+ ?f => "\f",
189
+ ?n => "\n",
190
+ ?r => "\r",
191
+ ?t => "\t",
192
+ ?u => nil,
193
+ })
194
+
195
+ EMPTY_8BIT_STRING = ''
196
+ if ::String.method_defined?(:encode)
197
+ EMPTY_8BIT_STRING.force_encoding Encoding::ASCII_8BIT
198
+ end
199
+
200
+ def parse_string
201
+ if scan(STRING)
202
+ return '' if self[1].empty?
203
+ string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
204
+ if u = UNESCAPE_MAP[$&[1]]
205
+ u
206
+ else # \uXXXX
207
+ bytes = EMPTY_8BIT_STRING.dup
208
+ i = 0
209
+ while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
210
+ bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
211
+ i += 1
212
+ end
213
+ JSON.iconv('utf-8', 'utf-16be', bytes)
214
+ end
215
+ end
216
+ if string.respond_to?(:force_encoding)
217
+ string.force_encoding(::Encoding::UTF_8)
218
+ end
219
+ string
220
+ else
221
+ UNPARSED
222
+ end
223
+ rescue => e
224
+ raise ParserError, "Caught #{e.class} at '#{peek(20)}': #{e}"
225
+ end
226
+
227
+ def lex_value
228
+ case
229
+ when scan(FLOAT)
230
+ @callback.call :float, Float(self[1])
231
+ when scan(INTEGER)
232
+ @callback.call :integer, Integer(self[1])
233
+ when scan(TRUE)
234
+ @callback.call :boolean, true
235
+ when scan(FALSE)
236
+ @callback.call :boolean, false
237
+ when scan(NULL)
238
+ @callback.call :null, nil
239
+ when (string = parse_string) != UNPARSED
240
+ @callback.call :string, string
241
+ when scan(ARRAY_OPEN)
242
+ @current_nesting += 1
243
+ lex_array
244
+ @current_nesting -= 1
245
+ when scan(OBJECT_OPEN)
246
+ @current_nesting += 1
247
+ lex_object
248
+ @current_nesting -= 1
249
+ # when @allow_nan && scan(NAN)
250
+ # NaN
251
+ # when @allow_nan && scan(INFINITY)
252
+ # Infinity
253
+ # when @allow_nan && scan(MINUS_INFINITY)
254
+ # MinusInfinity
255
+ else
256
+ UNPARSED
257
+ end
258
+ end
259
+
260
+ def lex_array
261
+ raise NestingError, "nesting of #@current_nesting is too deep" if
262
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
263
+ @callback.call :array_begin, nil
264
+ delim = false
265
+ until eos?
266
+ case
267
+ when (value = lex_value) != UNPARSED
268
+ delim = false
269
+
270
+ skip(IGNORE)
271
+ if scan(COLLECTION_DELIMITER)
272
+ delim = true
273
+ elsif match?(ARRAY_CLOSE)
274
+ ;
275
+ else
276
+ raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
277
+ end
278
+ when scan(ARRAY_CLOSE)
279
+ if delim
280
+ raise ParserError, "expected next element in array at '#{peek(20)}'!"
281
+ end
282
+ break
283
+ when skip(IGNORE)
284
+ ;
285
+ else
286
+ raise ParserError, "unexpected token in array at '#{peek(20)}'!"
287
+ end
288
+ end
289
+ @callback.call :array_end, nil
290
+ end
291
+
292
+ def lex_object
293
+ raise NestingError, "nesting of #@current_nesting is too deep" if
294
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
295
+
296
+
297
+ @callback.call :object_begin, nil
298
+ delim = false
299
+ until eos?
300
+ case
301
+ when (string = parse_string) != UNPARSED
302
+ @callback.call :property, string
303
+ skip(IGNORE)
304
+ unless scan(PAIR_DELIMITER)
305
+ raise ParserError, "expected ':' in object at '#{peek(20)}'!"
306
+ end
307
+ skip(IGNORE)
308
+ unless (value = lex_value).equal? UNPARSED
309
+ delim = false
310
+ skip(IGNORE)
311
+ if scan(COLLECTION_DELIMITER)
312
+ delim = true
313
+ elsif match?(OBJECT_CLOSE)
314
+ ;
315
+ else
316
+ raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
317
+ end
318
+ else
319
+ raise ParserError, "expected value in object at '#{peek(20)}'!"
320
+ end
321
+ when scan(OBJECT_CLOSE)
322
+ if delim
323
+ raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
324
+ end
325
+ break
326
+ when skip(IGNORE)
327
+ ;
328
+ else
329
+ raise ParserError, "unexpected token in object at '#{peek(20)}'!"
330
+ end
331
+ end
332
+ @callback.call :object_end, nil
333
+ end
334
+ end
335
+ end
336
+ end
@@ -0,0 +1,3 @@
1
+ module Muskox
2
+ VERSION = "0.0.1"
3
+ end
@@ -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 'muskox/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "muskox"
8
+ spec.version = Muskox::VERSION
9
+ spec.authors = ["Nick Howard"]
10
+ spec.email = ["ndh@baroquebobcat.com"]
11
+ spec.description = %q{A JSON-Schema based Parser-Generator}
12
+ spec.summary = %q{A JSON-Schema based Parser-Generator}
13
+ spec.homepage = "https://github.com/baroquebobcat/muskox"
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,35 @@
1
+ require 'minitest/autorun'
2
+ require 'muskox'
3
+ require 'json'
4
+
5
+
6
+ to_skip = [
7
+ # Allows wrong type sometimes for some reason
8
+ ["ignores non-arrays", "draft4/items.json", "a schema given for items"]
9
+ ]
10
+
11
+ ['draft4/items.json', 'draft4/type.json'].each do |file|
12
+ json = JSON.parse(open("json_schema_test_suite/tests/#{file}").read)
13
+ describe file do
14
+ json.each do |t|
15
+ describe t["description"] do
16
+ before do
17
+ schema = t["schema"]
18
+ @parser = Muskox.generate schema
19
+ end
20
+ t["tests"].each do |test|
21
+ it test["description"] do
22
+ skip if to_skip.include? [test["description"], file, t["description"]]
23
+ if test["valid"]
24
+ @parser.parse test["data"].to_json
25
+ else
26
+ assert_raises Muskox::ParserError do
27
+ @parser.parse test["data"].to_json
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,278 @@
1
+ require 'minitest/autorun'
2
+ require 'muskox'
3
+
4
+ describe Muskox do
5
+ describe "simple object[number]=integer schema, error on extra property" do
6
+ before do
7
+ schema = {
8
+ "title" => "Schema",
9
+ "type" => "object",
10
+ "properties" => {
11
+ "number" => {
12
+ "type" => "integer"
13
+ }
14
+ },
15
+ "required" => ["number"]
16
+ }
17
+
18
+ @parser = Muskox.generate schema
19
+ end
20
+ it "parses successfully when passed a valid string" do
21
+ result = @parser.parse %!{"number": 1}!
22
+ assert_equal({"number" => 1 }, result)
23
+ end
24
+
25
+ it "parses successfully when passed a different valid string" do
26
+ result = @parser.parse %!{"number": 2}!
27
+ assert_equal({"number" => 2 }, result)
28
+ end
29
+
30
+ it "raises an error when there is an extra property" do
31
+ assert_raises Muskox::ParserError do
32
+ result = @parser.parse %!{"number": 2, "grug":[]}!
33
+ end
34
+ end
35
+ it "raises an error when there is an invalid type of property" do
36
+ assert_raises Muskox::ParserError do
37
+ result = @parser.parse %!{"number": "string-not-number"}!
38
+ end
39
+ end
40
+ it "raises an error when there is a missing property" do
41
+ assert_raises Muskox::ParserError do
42
+ result = @parser.parse %!{}!
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+
49
+ describe "simple object[string]=string schema, error on extra property" do
50
+ before do
51
+ schema = {
52
+ "title" => "Schema",
53
+ "type" => "object",
54
+ "properties" => {
55
+ "string" => {
56
+ "type" => "string"
57
+ }
58
+ },
59
+ "required" => ["string"]
60
+ }
61
+
62
+ @parser = Muskox.generate schema
63
+ end
64
+ it "parses successfully when passed a valid string" do
65
+ result = @parser.parse %!{"string": "one"}!
66
+ assert_equal({"string" => "one" }, result)
67
+ end
68
+
69
+ it "parses successfully when passed a different valid string" do
70
+ result = @parser.parse %!{"string": "two"}!
71
+ assert_equal({"string" => "two" }, result)
72
+ end
73
+
74
+ it "raises an error when there is an extra property" do
75
+ assert_raises Muskox::ParserError do
76
+ result = @parser.parse %!{"string": "two", "grug":[]}!
77
+ end
78
+ end
79
+ it "raises an error when there is an invalid type of property" do
80
+ assert_raises Muskox::ParserError do
81
+ result = @parser.parse %!{"string": 1701}!
82
+ end
83
+ end
84
+ end
85
+
86
+ describe " object[array]=array[string] schema, error on extra property" do
87
+ before do
88
+ schema = {
89
+ "title" => "Schema",
90
+ "type" => "object",
91
+ "properties" => {
92
+ "array" => {
93
+ "type" => "array",
94
+ "items" => {"type" => "string"}
95
+ }
96
+ },
97
+ "required" => ["array"]
98
+ }
99
+
100
+ @parser = Muskox.generate schema
101
+ end
102
+ it "parses successfully when passed a valid string" do
103
+ result = @parser.parse %!{"array": ["one"]}!
104
+ assert_equal({"array" => ["one"] }, result)
105
+ end
106
+
107
+ it "parses successfully when passed a different valid array" do
108
+ result = @parser.parse %!{"array": ["two"]}!
109
+ assert_equal({"array" => ["two"] }, result)
110
+ end
111
+
112
+ it "parses successfully when passed a valid array of size 2" do
113
+ result = @parser.parse %!{"array": ["two", "one"]}!
114
+ assert_equal({"array" => ["two", "one"] }, result)
115
+ end
116
+
117
+ it "raises an error when there is an extra property" do
118
+ assert_raises Muskox::ParserError do
119
+ result = @parser.parse %!{"array": ["two"], "grug":[]}!
120
+ end
121
+ end
122
+
123
+ it "raises an error when there is an invalid component type of property" do
124
+ assert_raises Muskox::ParserError do
125
+ result = @parser.parse %!{"array": [1701]}!
126
+ p result
127
+ end
128
+ end
129
+ end
130
+
131
+
132
+ describe "object[object]=object schema, error on extra property" do
133
+ before do
134
+ schema = {
135
+ "title" => "Schema",
136
+ "type" => "object",
137
+ "properties" => {
138
+ "object" => {
139
+ "type" => "object"
140
+ }
141
+ },
142
+ "required" => ["object"]
143
+ }
144
+
145
+ @parser = Muskox.generate schema
146
+ end
147
+ it "parses successfully when passed a valid string" do
148
+ result = @parser.parse %!{"object": {}}!
149
+ assert_equal({"object" => {} }, result)
150
+ end
151
+
152
+ it "parses successfully when passed a different valid string" do
153
+ result = @parser.parse %!{"object": {}}!
154
+ assert_equal({"object" => {} }, result)
155
+ end
156
+
157
+ it "raises an error when there is an extra property" do
158
+ assert_raises Muskox::ParserError do
159
+ result = @parser.parse %!{"object": {}, "grug":[]}!
160
+ end
161
+ end
162
+ it "raises an error when there is an invalid type of property" do
163
+ assert_raises Muskox::ParserError do
164
+ result = @parser.parse %!{"object": "string"}!
165
+ end
166
+ end
167
+ end
168
+
169
+ describe "object[object]=object[string]=string schema, error on extra property" do
170
+ before do
171
+ schema = {
172
+ "title" => "Schema",
173
+ "type" => "object",
174
+ "properties" => {
175
+ "object" => {
176
+ "type" => "object",
177
+ "properties" => {"string" => {"type" => "string"}},
178
+ "required" => ["string"]
179
+ }
180
+ },
181
+ "required" => ["object"]
182
+ }
183
+
184
+ @parser = Muskox.generate schema
185
+ end
186
+ it "parses successfully when passed a valid string" do
187
+ result = @parser.parse %!{"object": {"string":"a"}}!
188
+ assert_equal({"object" => {"string" => "a"} }, result)
189
+ end
190
+
191
+ it "parses successfully when passed a different valid string" do
192
+ result = @parser.parse %!{"object": {"string":"b"}}!
193
+ assert_equal({"object" => {"string" => "b"} }, result)
194
+ end
195
+
196
+ it "raises an error when there is an extra nested property" do
197
+ assert_raises Muskox::ParserError do
198
+ result = @parser.parse %!{"object": {"string":"a","grug":[]}, }!
199
+ end
200
+ end
201
+
202
+ it "raises an error when there is an invalid type of nested property" do
203
+ assert_raises Muskox::ParserError do
204
+ result = @parser.parse %!{"object": {"string":1}}!
205
+ end
206
+ end
207
+ end
208
+
209
+ describe "simple object[number]=float happy path" do
210
+ before do
211
+ schema = {
212
+ "title" => "Schema",
213
+ "type" => "object",
214
+ "properties" => {
215
+ "number" => {
216
+ "type" => "float"
217
+ }
218
+ },
219
+ "required" => ["number"]
220
+ }
221
+
222
+ @parser = Muskox.generate schema
223
+ end
224
+ it "parses successfully when passed a valid string" do
225
+ result = @parser.parse %!{"number": 1.0}!
226
+ assert_equal({"number" => 1.0 }, result)
227
+ end
228
+ end
229
+
230
+
231
+ describe "simple object[number]=boolean happy path" do
232
+ before do
233
+ schema = {
234
+ "title" => "Schema",
235
+ "type" => "object",
236
+ "properties" => {
237
+ "number" => {
238
+ "type" => "boolean"
239
+ }
240
+ },
241
+ "required" => ["number"]
242
+ }
243
+
244
+ @parser = Muskox.generate schema
245
+ end
246
+ it "parses successfully when passed a valid string" do
247
+ result = @parser.parse %!{"number": true}!
248
+ assert_equal({"number" => true }, result)
249
+ end
250
+ end
251
+
252
+ describe "malformed json handling" do
253
+ before do
254
+ schema = {
255
+ "type" => "object",
256
+ "properties" => {
257
+ "number" => {
258
+ "type" => "boolean"
259
+ }
260
+ },
261
+ "required" => ["number"]
262
+ }
263
+
264
+ @parser = Muskox.generate schema
265
+ end
266
+ it "raises an error when object unended" do
267
+ assert_raises Muskox::ParserError do
268
+ result = @parser.parse %!{"number": true!
269
+ end
270
+ end
271
+ end
272
+
273
+ #null
274
+ #array size limits
275
+ # bad JSON strings
276
+
277
+ end
278
+
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: muskox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Howard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A JSON-Schema based Parser-Generator
42
+ email:
43
+ - ndh@baroquebobcat.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gitignore
49
+ - .gitmodules
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - lib/muskox.rb
55
+ - lib/muskox/json_lexer.rb
56
+ - lib/muskox/version.rb
57
+ - muskox.gemspec
58
+ - spec/json_schema_spec.rb
59
+ - spec/muskox_spec.rb
60
+ homepage: https://github.com/baroquebobcat/muskox
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.0.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: A JSON-Schema based Parser-Generator
84
+ test_files:
85
+ - spec/json_schema_spec.rb
86
+ - spec/muskox_spec.rb