seomoz-json-schema 1.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.
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