seomoz-json-schema 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README.textile +216 -0
  2. data/lib/json-schema.rb +13 -0
  3. data/lib/json-schema/attributes/additionalitems.rb +23 -0
  4. data/lib/json-schema/attributes/additionalproperties.rb +39 -0
  5. data/lib/json-schema/attributes/dependencies.rb +30 -0
  6. data/lib/json-schema/attributes/disallow.rb +11 -0
  7. data/lib/json-schema/attributes/divisibleby.rb +16 -0
  8. data/lib/json-schema/attributes/enum.rb +24 -0
  9. data/lib/json-schema/attributes/extends.rb +14 -0
  10. data/lib/json-schema/attributes/format.rb +113 -0
  11. data/lib/json-schema/attributes/items.rb +25 -0
  12. data/lib/json-schema/attributes/maxdecimal.rb +15 -0
  13. data/lib/json-schema/attributes/maximum.rb +15 -0
  14. data/lib/json-schema/attributes/maximum_inclusive.rb +15 -0
  15. data/lib/json-schema/attributes/maxitems.rb +12 -0
  16. data/lib/json-schema/attributes/maxlength.rb +14 -0
  17. data/lib/json-schema/attributes/minimum.rb +15 -0
  18. data/lib/json-schema/attributes/minimum_inclusive.rb +15 -0
  19. data/lib/json-schema/attributes/minitems.rb +12 -0
  20. data/lib/json-schema/attributes/minlength.rb +14 -0
  21. data/lib/json-schema/attributes/pattern.rb +15 -0
  22. data/lib/json-schema/attributes/patternproperties.rb +23 -0
  23. data/lib/json-schema/attributes/properties.rb +23 -0
  24. data/lib/json-schema/attributes/properties_optional.rb +23 -0
  25. data/lib/json-schema/attributes/ref.rb +55 -0
  26. data/lib/json-schema/attributes/type.rb +71 -0
  27. data/lib/json-schema/attributes/uniqueitems.rb +16 -0
  28. data/lib/json-schema/schema.rb +50 -0
  29. data/lib/json-schema/uri/file.rb +32 -0
  30. data/lib/json-schema/uri/uuid.rb +285 -0
  31. data/lib/json-schema/validator.rb +425 -0
  32. data/lib/json-schema/validators/draft1.rb +32 -0
  33. data/lib/json-schema/validators/draft2.rb +33 -0
  34. data/lib/json-schema/validators/draft3.rb +38 -0
  35. data/resources/draft-01.json +155 -0
  36. data/resources/draft-02.json +166 -0
  37. data/resources/draft-03.json +174 -0
  38. data/test/data/bad_data_1.json +3 -0
  39. data/test/data/good_data_1.json +3 -0
  40. data/test/schemas/good_schema_1.json +10 -0
  41. data/test/schemas/good_schema_2.json +10 -0
  42. data/test/test_extended_schema.rb +68 -0
  43. data/test/test_files.rb +35 -0
  44. data/test/test_full_validation.rb +38 -0
  45. data/test/test_jsonschema_draft1.rb +703 -0
  46. data/test/test_jsonschema_draft2.rb +775 -0
  47. data/test/test_jsonschema_draft3.rb +972 -0
  48. data/test/test_schema_validation.rb +43 -0
  49. metadata +137 -0
@@ -0,0 +1,32 @@
1
+ require 'uri'
2
+
3
+ module URI
4
+
5
+ # Ruby does not have built-in support for filesystem URIs, and definitely does not have built-in support for
6
+ # using open-uri with filesystem URIs
7
+ class File < Generic
8
+
9
+ COMPONENT = [
10
+ :scheme,
11
+ :path,
12
+ :fragment,
13
+ :host
14
+ ].freeze
15
+
16
+ def initialize(*arg)
17
+ arg[2] = ""
18
+ super(*arg)
19
+ end
20
+
21
+ def self.build(args)
22
+ tmp = Util::make_components_hash(self, args)
23
+ return super(tmp)
24
+ end
25
+
26
+ def open(*rest, &block)
27
+ ::File.open(self.path, *rest, &block)
28
+ end
29
+
30
+ @@schemes['FILE'] = File
31
+ end
32
+ end
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby
2
+ ### http://mput.dip.jp/mput/uuid.txt
3
+
4
+ # Copyright(c) 2005 URABE, Shyouhei.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this code, to deal in the code without restriction, including without
8
+ # limitation the rights to use, copy, modify, merge, publish, distribute,
9
+ # sublicense, and/or sell copies of the code, and to permit persons to whom the
10
+ # code is furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the code.
14
+ #
15
+ # THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE CODE OR THE USE OR OTHER DEALINGS IN THE
21
+ # CODE.
22
+ #
23
+ # 2009-02-20: Modified by Pablo Lorenzoni <pablo@propus.com.br> to correctly
24
+ # include the version in the raw_bytes.
25
+
26
+
27
+ require 'digest/md5'
28
+ require 'digest/sha1'
29
+ require 'tmpdir'
30
+
31
+ module JSON
32
+ module Util
33
+
34
+ # Pure ruby UUID generator, which is compatible with RFC4122
35
+ UUID = Struct.new :raw_bytes
36
+
37
+ class UUID
38
+ private_class_method :new
39
+
40
+ class << self
41
+ def mask19 v, str # :nodoc
42
+ nstr = str.bytes.to_a
43
+ version = [0, 16, 32, 48, 64, 80][v]
44
+ nstr[6] &= 0b00001111
45
+ nstr[6] |= version
46
+ # nstr[7] &= 0b00001111
47
+ # nstr[7] |= 0b01010000
48
+ nstr[8] &= 0b00111111
49
+ nstr[8] |= 0b10000000
50
+ str = ''
51
+ nstr.each { |s| str << s.chr }
52
+ str
53
+ end
54
+
55
+ def mask18 v, str # :nodoc
56
+ version = [0, 16, 32, 48, 64, 80][v]
57
+ str[6] &= 0b00001111
58
+ str[6] |= version
59
+ # str[7] &= 0b00001111
60
+ # str[7] |= 0b01010000
61
+ str[8] &= 0b00111111
62
+ str[8] |= 0b10000000
63
+ str
64
+ end
65
+
66
+ def mask v, str
67
+ if RUBY_VERSION >= "1.9.0"
68
+ return mask19 v, str
69
+ else
70
+ return mask18 v, str
71
+ end
72
+ end
73
+ private :mask, :mask18, :mask19
74
+
75
+ # UUID generation using SHA1. Recommended over create_md5.
76
+ # Namespace object is another UUID, some of them are pre-defined below.
77
+ def create_sha1 str, namespace
78
+ sha1 = Digest::SHA1.new
79
+ sha1.update namespace.raw_bytes
80
+ sha1.update str
81
+ sum = sha1.digest
82
+ raw = mask 5, sum[0..15]
83
+ ret = new raw
84
+ ret.freeze
85
+ ret
86
+ end
87
+ alias :create_v5 :create_sha1
88
+
89
+ # UUID generation using MD5 (for backward compat.)
90
+ def create_md5 str, namespace
91
+ md5 = Digest::MD5.new
92
+ md5.update namespace.raw_bytes
93
+ md5.update str
94
+ sum = md5.digest
95
+ raw = mask 3, sum[0..16]
96
+ ret = new raw
97
+ ret.freeze
98
+ ret
99
+ end
100
+ alias :create_v3 :create_md5
101
+
102
+ # UUID generation using random-number generator. From it's random
103
+ # nature, there's no warranty that the created ID is really universaly
104
+ # unique.
105
+ def create_random
106
+ rnd = [
107
+ rand(0x100000000),
108
+ rand(0x100000000),
109
+ rand(0x100000000),
110
+ rand(0x100000000),
111
+ ].pack "N4"
112
+ raw = mask 4, rnd
113
+ ret = new raw
114
+ ret.freeze
115
+ ret
116
+ end
117
+ alias :create_v4 :create_random
118
+
119
+ def read_state fp # :nodoc:
120
+ fp.rewind
121
+ Marshal.load fp.read
122
+ end
123
+
124
+ def write_state fp, c, m # :nodoc:
125
+ fp.rewind
126
+ str = Marshal.dump [c, m]
127
+ fp.write str
128
+ end
129
+
130
+ private :read_state, :write_state
131
+ STATE_FILE = 'ruby-uuid'
132
+
133
+ # create the "version 1" UUID with current system clock, current UTC
134
+ # timestamp, and the IEEE 802 address (so-called MAC address).
135
+ #
136
+ # Speed notice: it's slow. It writes some data into hard drive on every
137
+ # invokation. If you want to speed this up, try remounting tmpdir with a
138
+ # memory based filesystem (such as tmpfs). STILL slow? then no way but
139
+ # rewrite it with c :)
140
+ def create clock=nil, time=nil, mac_addr=nil
141
+ c = t = m = nil
142
+ Dir.chdir Dir.tmpdir do
143
+ unless FileTest.exist? STATE_FILE then
144
+ # Generate a pseudo MAC address because we have no pure-ruby way
145
+ # to know the MAC address of the NIC this system uses. Note
146
+ # that cheating with pseudo arresses here is completely legal:
147
+ # see Section 4.5 of RFC4122 for details.
148
+ sha1 = Digest::SHA1.new
149
+ 256.times do
150
+ r = [rand(0x100000000)].pack "N"
151
+ sha1.update r
152
+ end
153
+ str = sha1.digest
154
+ r = rand 14 # 20-6
155
+ node = str[r, 6] || str
156
+ if RUBY_VERSION >= "1.9.0"
157
+ nnode = node.bytes.to_a
158
+ nnode[0] |= 0x01
159
+ node = ''
160
+ nnode.each { |s| node << s.chr }
161
+ else
162
+ node[0] |= 0x01 # multicast bit
163
+ end
164
+ k = rand 0x40000
165
+ open STATE_FILE, 'w' do |fp|
166
+ fp.flock IO::LOCK_EX
167
+ write_state fp, k, node
168
+ fp.chmod 0o777 # must be world writable
169
+ end
170
+ end
171
+ open STATE_FILE, 'r+' do |fp|
172
+ fp.flock IO::LOCK_EX
173
+ c, m = read_state fp
174
+ c = clock % 0x4000 if clock
175
+ m = mac_addr if mac_addr
176
+ t = time
177
+ if t.nil? then
178
+ # UUID epoch is 1582/Oct/15
179
+ tt = Time.now
180
+ t = tt.to_i*10000000 + tt.tv_usec*10 + 0x01B21DD213814000
181
+ end
182
+ c = c.succ # important; increment here
183
+ write_state fp, c, m
184
+ end
185
+ end
186
+
187
+ tl = t & 0xFFFF_FFFF
188
+ tm = t >> 32
189
+ tm = tm & 0xFFFF
190
+ th = t >> 48
191
+ th = th & 0x0FFF
192
+ th = th | 0x1000
193
+ cl = c & 0xFF
194
+ ch = c & 0x3F00
195
+ ch = ch >> 8
196
+ ch = ch | 0x80
197
+ pack tl, tm, th, cl, ch, m
198
+ end
199
+ alias :create_v1 :create
200
+
201
+ # A simple GUID parser: just ignores unknown characters and convert
202
+ # hexadecimal dump into 16-octet object.
203
+ def parse obj
204
+ str = obj.to_s.sub %r/\Aurn:uuid:/, ''
205
+ str.gsub! %r/[^0-9A-Fa-f]/, ''
206
+ raw = str[0..31].lines.to_a.pack 'H*'
207
+ ret = new raw
208
+ ret.freeze
209
+ ret
210
+ end
211
+
212
+ # The 'primitive constructor' of this class
213
+ # Note UUID.pack(uuid.unpack) == uuid
214
+ def pack tl, tm, th, ch, cl, n
215
+ raw = [tl, tm, th, ch, cl, n].pack "NnnCCa6"
216
+ ret = new raw
217
+ ret.freeze
218
+ ret
219
+ end
220
+ end
221
+
222
+ # The 'primitive deconstructor', or the dual to pack.
223
+ # Note UUID.pack(uuid.unpack) == uuid
224
+ def unpack
225
+ raw_bytes.unpack "NnnCCa6"
226
+ end
227
+
228
+ # Generate the string representation (a.k.a GUID) of this UUID
229
+ def to_s
230
+ a = unpack
231
+ tmp = a[-1].unpack 'C*'
232
+ a[-1] = sprintf '%02x%02x%02x%02x%02x%02x', *tmp
233
+ "%08x-%04x-%04x-%02x%02x-%s" % a
234
+ end
235
+ alias guid to_s
236
+
237
+ # Convert into a RFC4122-comforming URN representation
238
+ def to_uri
239
+ "urn:uuid:" + self.to_s
240
+ end
241
+ alias urn to_uri
242
+
243
+ # Convert into 128-bit unsigned integer
244
+ # Typically a Bignum instance, but can be a Fixnum.
245
+ def to_int
246
+ tmp = self.raw_bytes.unpack "C*"
247
+ tmp.inject do |r, i|
248
+ r * 256 | i
249
+ end
250
+ end
251
+ alias to_i to_int
252
+
253
+ # Gets the version of this UUID
254
+ # returns nil if bad version
255
+ def version
256
+ a = unpack
257
+ v = (a[2] & 0xF000).to_s(16)[0].chr.to_i
258
+ return v if (1..5).include? v
259
+ return nil
260
+ end
261
+
262
+ # Two UUIDs are said to be equal if and only if their (byte-order
263
+ # canonicalized) integer representations are equivallent. Refer RFC4122 for
264
+ # details.
265
+ def == other
266
+ to_i == other.to_i
267
+ end
268
+
269
+ include Comparable
270
+ # UUIDs are comparable (don't know what benefits are there, though).
271
+ def <=> other
272
+ to_s <=> other.to_s
273
+ end
274
+
275
+ # Pre-defined UUID Namespaces described in RFC4122 Appendix C.
276
+ NameSpace_DNS = parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
277
+ NameSpace_URL = parse "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
278
+ NameSpace_OID = parse "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
279
+ NameSpace_X500 = parse "6ba7b814-9dad-11d1-80b4-00c04fd430c8"
280
+
281
+ # The Nil UUID in RFC4122 Section 4.1.7
282
+ Nil = parse "00000000-0000-0000-0000-000000000000"
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,425 @@
1
+ require 'uri'
2
+ require 'open-uri'
3
+ require 'pathname'
4
+ require 'bigdecimal'
5
+ require 'digest/sha1'
6
+ require 'date'
7
+
8
+ module JSON
9
+
10
+ class Schema
11
+ class ValidationError < StandardError
12
+ attr_reader :fragments, :schema
13
+
14
+ def initialize(message, fragments, schema)
15
+ @fragments = fragments
16
+ @schema = schema
17
+ message = "#{message} in schema #{schema.uri}"
18
+ super(message)
19
+ end
20
+ end
21
+
22
+ class SchemaError < StandardError
23
+ end
24
+
25
+ class JsonParseError < StandardError
26
+ end
27
+
28
+ class Attribute
29
+ def self.validate(current_schema, data, fragments, validator, options = {})
30
+ end
31
+
32
+ def self.build_fragment(fragments)
33
+ "#/#{fragments.join('/')}"
34
+ end
35
+
36
+ def self.validation_error(message, fragments, current_schema, record_errors)
37
+ error = ValidationError.new(message, fragments, current_schema)
38
+ if record_errors
39
+ ::JSON::Validator.validation_error(error.message)
40
+ else
41
+ raise error
42
+ end
43
+ end
44
+ end
45
+
46
+ class Validator
47
+ attr_accessor :attributes, :uri
48
+
49
+ def initialize()
50
+ @attributes = {}
51
+ @uri = nil
52
+ end
53
+
54
+ def extend_schema_definition(schema_uri)
55
+ u = URI.parse(schema_uri)
56
+ validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
57
+ if validator.nil?
58
+ raise SchemaError.new("Schema not found: #{u.scheme}://#{u.host}#{u.path}")
59
+ end
60
+ @attributes.merge!(validator.attributes)
61
+ end
62
+
63
+ def to_s
64
+ "#{@uri.scheme}://#{uri.host}#{uri.path}"
65
+ end
66
+
67
+ def validate(current_schema, data, fragments, options = {})
68
+ current_schema.schema.each do |attr_name,attribute|
69
+ if @attributes.has_key?(attr_name.to_s)
70
+ @attributes[attr_name.to_s].validate(current_schema, data, fragments, self, options)
71
+ end
72
+ end
73
+ data
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ class Validator
80
+
81
+ @@schemas = {}
82
+ @@cache_schemas = false
83
+ @@default_opts = {
84
+ :list => false,
85
+ :version => nil,
86
+ :validate_schema => false,
87
+ :record_errors => false
88
+ }
89
+ @@validators = {}
90
+ @@default_validator = nil
91
+ @@errors = []
92
+
93
+ def self.version_string_for(version)
94
+ # I'm not a fan of this, but it's quick and dirty to get it working for now
95
+ return "draft-03" unless version
96
+ case version.to_s
97
+ when "draft3"
98
+ "draft-03"
99
+ when "draft2"
100
+ "draft-02"
101
+ when "draft1"
102
+ "draft-01"
103
+ else
104
+ raise JSON::Schema::SchemaError.new("The requested JSON schema version is not supported")
105
+ end
106
+ end
107
+
108
+ def self.metaschema_for(version_string)
109
+ File.join(Pathname.new(File.dirname(__FILE__)).parent.parent, "resources", "#{version_string}.json").to_s
110
+ end
111
+
112
+ def initialize(schema_data, data, opts={})
113
+ @options = @@default_opts.clone.merge(opts)
114
+
115
+ # I'm not a fan of this, but it's quick and dirty to get it working for now
116
+ version_string = "draft-03"
117
+ if @options[:version]
118
+ version_string = @options[:version] = self.class.version_string_for(@options[:version])
119
+ u = URI.parse("http://json-schema.org/#{@options[:version]}/schema#")
120
+ validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
121
+ @options[:version] = validator
122
+ end
123
+
124
+ @validation_options = @options[:record_errors] ? {:record_errors => true} : {}
125
+
126
+ # validate the schema, if requested
127
+ if @options[:validate_schema]
128
+ begin
129
+ meta_validator = JSON::Validator.new(self.class.metaschema_for(version_string), schema_data)
130
+ meta_validator.validate
131
+ rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError
132
+ raise $!
133
+ end
134
+ end
135
+
136
+ @base_schema = initialize_schema(schema_data)
137
+ @data = initialize_data(data)
138
+ build_schemas(@base_schema)
139
+ end
140
+
141
+
142
+ # Run a simple true/false validation of data against a schema
143
+ def validate()
144
+ begin
145
+ Validator.clear_errors
146
+ @base_schema.validate(@data,[],@validation_options)
147
+ Validator.clear_cache
148
+ @@errors
149
+ rescue JSON::Schema::ValidationError
150
+ Validator.clear_cache
151
+ raise $!
152
+ end
153
+ end
154
+
155
+
156
+ def load_ref_schema(parent_schema,ref)
157
+ uri = URI.parse(ref)
158
+ if uri.relative?
159
+ uri = parent_schema.uri.clone
160
+
161
+ # Check for absolute path
162
+ path = ref.split("#")[0]
163
+
164
+ # This is a self reference and thus the schema does not need to be re-loaded
165
+ if path.nil? || path == ''
166
+ return
167
+ end
168
+
169
+ if path && path[0,1] == '/'
170
+ uri.path = Pathname.new(path).cleanpath.to_s
171
+ else
172
+ uri = parent_schema.uri.merge(path)
173
+ end
174
+ uri.fragment = ''
175
+ end
176
+
177
+ if Validator.schemas[uri.to_s].nil?
178
+ begin
179
+ schema = JSON::Schema.new(JSON::Validator.parse(open(uri.to_s).read), uri, @options[:version])
180
+ Validator.add_schema(schema)
181
+ build_schemas(schema)
182
+ rescue JSON::ParserError
183
+ # Don't rescue this error, we want JSON formatting issues to bubble up
184
+ raise $!
185
+ rescue Exception
186
+ # Failures will occur when this URI cannot be referenced yet. Don't worry about it,
187
+ # the proper error will fall out if the ref isn't ever defined
188
+ end
189
+ end
190
+ end
191
+
192
+
193
+ # Build all schemas with IDs, mapping out the namespace
194
+ def build_schemas(parent_schema)
195
+ # Build ref schemas if they exist
196
+ if parent_schema.schema["$ref"]
197
+ load_ref_schema(parent_schema, parent_schema.schema["$ref"])
198
+ end
199
+
200
+ # Check for schemas in union types
201
+ ["type", "disallow"].each do |key|
202
+ if parent_schema.schema[key] && parent_schema.schema[key].is_a?(Array)
203
+ parent_schema.schema[key].each_with_index do |type,i|
204
+ if type.is_a?(Hash)
205
+ handle_schema(parent_schema, type)
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ # All properties are schemas
212
+ if parent_schema.schema["properties"]
213
+ parent_schema.schema["properties"].each do |k,v|
214
+ handle_schema(parent_schema, v)
215
+ end
216
+ end
217
+
218
+ # Items are always schemas
219
+ if parent_schema.schema["items"]
220
+ items = parent_schema.schema["items"].clone
221
+ single = false
222
+ if !items.is_a?(Array)
223
+ items = [items]
224
+ single = true
225
+ end
226
+ items.each_with_index do |item,i|
227
+ handle_schema(parent_schema, item)
228
+ end
229
+ end
230
+
231
+ # Each of these might be schemas
232
+ ["additionalProperties", "additionalItems", "dependencies", "extends"].each do |key|
233
+ if parent_schema.schema[key].is_a?(Hash)
234
+ handle_schema(parent_schema, parent_schema.schema[key])
235
+ end
236
+ end
237
+
238
+ end
239
+
240
+ # Either load a reference schema or create a new schema
241
+ def handle_schema(parent_schema, obj)
242
+ schema_uri = parent_schema.uri.clone
243
+ schema = JSON::Schema.new(obj,schema_uri,@options[:version])
244
+ if obj['id']
245
+ Validator.add_schema(schema)
246
+ end
247
+ build_schemas(schema)
248
+ end
249
+
250
+
251
+ class << self
252
+ def validate(schema, data,opts={})
253
+ begin
254
+ validator = JSON::Validator.new(schema, data, opts)
255
+ validator.validate
256
+ return true
257
+ rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError
258
+ return false
259
+ end
260
+ end
261
+
262
+ def validate!(schema, data,opts={})
263
+ validator = JSON::Validator.new(schema, data, opts)
264
+ validator.validate
265
+ return true
266
+ end
267
+ alias_method 'validate2', 'validate!'
268
+
269
+
270
+ def fully_validate(schema, data, opts={})
271
+ opts[:record_errors] = true
272
+ validator = JSON::Validator.new(schema, data, opts)
273
+ validator.validate
274
+ end
275
+
276
+ def fully_validate_schema(schema, opts={})
277
+ data = schema
278
+ schema = metaschema_for(version_string_for(opts[:version]))
279
+ fully_validate(schema, data, opts)
280
+ end
281
+
282
+
283
+ def clear_cache
284
+ @@schemas = {} if @@cache_schemas == false
285
+ end
286
+
287
+ def clear_errors
288
+ @@errors = []
289
+ end
290
+
291
+ def validation_error(error)
292
+ @@errors.push(error)
293
+ end
294
+
295
+ def schemas
296
+ @@schemas
297
+ end
298
+
299
+ def add_schema(schema)
300
+ @@schemas[schema.uri.to_s] = schema if @@schemas[schema.uri.to_s].nil?
301
+ end
302
+
303
+ def cache_schemas=(val)
304
+ @@cache_schemas = val == true ? true : false
305
+ end
306
+
307
+ def validators
308
+ @@validators
309
+ end
310
+
311
+ def default_validator
312
+ @@default_validator
313
+ end
314
+
315
+ def register_validator(v)
316
+ @@validators[v.to_s] = v
317
+ end
318
+
319
+ def register_default_validator(v)
320
+ @@default_validator = v
321
+ end
322
+
323
+ def json_backend=(backend)
324
+ warn "json_backend= is deprecated. Use MultiJson.engine= instead"
325
+ end
326
+
327
+ def parse(s)
328
+ MultiJson.decode(s)
329
+ end
330
+ end
331
+
332
+ private
333
+
334
+ if begin
335
+ Gem::Specification::find_by_name('uuidtools')
336
+ rescue Gem::LoadError
337
+ false
338
+ rescue
339
+ Gem.available?('uuidtools')
340
+ end
341
+ require 'uuidtools'
342
+ @@fake_uri_generator = lambda{|s| UUIDTools::UUID.sha1_create(UUIDTools::UUID_URL_NAMESPACE, s).to_s }
343
+ else
344
+ require 'uri/uuid'
345
+ @@fake_uri_generator = lambda{|s| JSON::Util::UUID.create_v5(s,JSON::Util::UUID::Nil).to_s }
346
+ end
347
+
348
+ def serialize schema
349
+ MultiJson.encode(schema)
350
+ end
351
+
352
+ def fake_uri schema
353
+ @@fake_uri_generator.call(schema)
354
+ end
355
+
356
+ def initialize_schema(schema)
357
+ if schema.is_a?(String)
358
+ begin
359
+ # Build a fake URI for this
360
+ schema_uri = URI.parse(fake_uri(schema))
361
+ schema = JSON::Validator.parse(schema)
362
+ if @options[:list]
363
+ schema = {"type" => "array", "items" => schema}
364
+ end
365
+ schema = JSON::Schema.new(schema,schema_uri,@options[:version])
366
+ Validator.add_schema(schema)
367
+ rescue
368
+ # Build a uri for it
369
+ schema_uri = URI.parse(schema)
370
+ if schema_uri.relative?
371
+ # Check for absolute path
372
+ if schema[0,1] == '/'
373
+ schema_uri = URI.parse("file://#{schema}")
374
+ else
375
+ schema_uri = URI.parse("file://#{Dir.pwd}/#{schema}")
376
+ end
377
+ end
378
+ if Validator.schemas[schema_uri.to_s].nil?
379
+ schema = JSON::Validator.parse(open(schema_uri.to_s).read)
380
+ if @options[:list]
381
+ schema = {"type" => "array", "items" => schema}
382
+ end
383
+ schema = JSON::Schema.new(schema,schema_uri,@options[:version])
384
+ Validator.add_schema(schema)
385
+ else
386
+ schema = Validator.schemas[schema_uri.to_s]
387
+ end
388
+ end
389
+ elsif schema.is_a?(Hash)
390
+ if @options[:list]
391
+ schema = {"type" => "array", "items" => schema}
392
+ end
393
+ schema_uri = URI.parse(fake_uri(serialize(schema)))
394
+ schema = JSON::Schema.new(schema,schema_uri,@options[:version])
395
+ Validator.add_schema(schema)
396
+ else
397
+ raise "Invalid schema - must be either a string or a hash"
398
+ end
399
+
400
+ schema
401
+ end
402
+
403
+
404
+ def initialize_data(data)
405
+ # Parse the data, if any
406
+ if data.is_a?(String)
407
+ begin
408
+ data = JSON::Validator.parse(data)
409
+ rescue
410
+ json_uri = URI.parse(data)
411
+ if json_uri.relative?
412
+ if data[0,1] == '/'
413
+ schema_uri = URI.parse("file://#{data}")
414
+ else
415
+ schema_uri = URI.parse("file://#{Dir.pwd}/#{data}")
416
+ end
417
+ end
418
+ data = JSON::Validator.parse(open(json_uri.to_s).read)
419
+ end
420
+ end
421
+ data
422
+ end
423
+
424
+ end
425
+ end