muskox 0.0.1

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,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