json_schema_tools 0.3.2 → 0.3.3

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZjMyMjgzZTgyMzQ3OTBlOGVlNDEzNTI0OTMwYWQxNGFkYTViNzMwMA==
4
+ OWMzMmM0YmMzMDY4YmVhMjQ4MjI4Nzk1MGQwNTVjYzFiYTNlM2Q0ZQ==
5
5
  data.tar.gz: !binary |-
6
- YTE5ODAyMTI0YThkYzc1ZDFhYjFiMDA2ODVhZDJjZDI2MjRkMDAwNw==
6
+ NDU3MjkxYTUxNjNlMWNkOGE5MGJkZGNkOTUxOGE3OThjMDM3NDIzNQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Y2YwOWZhMWEyMmZlZDdjZjlmMDA5NDhkMDNiM2RmZTJhYWIwNjM2ZDM4NGQy
10
- OGE4ZGNhNjg2OGUwY2FkOWFkNmRhMWRkNzAyN2JkY2VmMmJhODliNTMxZWUz
11
- ZWI0MWRmYTQ1ZTEzYWM5NzRmMGY0OWRjZTJhZTVhMzUyMzgxOWY=
9
+ NDBhMzllODdmMzgxOWQ3OWExM2IzZjU4YjIwYWZhODgyMzk5ZDU3NjUwNmY1
10
+ Mjg3ODcxZjEzMGE3N2JkODY1ZmMxOWI1MDFkMDVlODc0ODRlNDU2Mjc4NTAw
11
+ OGQ0NDQxOThkZGNkZjliMTYwMGY2ZDQ4ZmY2NDM3NjQ2YjU0YmI=
12
12
  data.tar.gz: !binary |-
13
- MjRiYWQ5MTJkNmU2OGM3NGI0ODlkYzFjMzAzMjgyMzFmNzYzNmJjYjkwNjU1
14
- NzM1ODM4NjNlZGE5Yjg2ZDNmNzIyNGZjOTkwMjE5NzQzMTBlN2IyZTI5YTA3
15
- MThmODY2ZThiYjA4YzAxZDM4Mzk4NjJlY2ViMjdjM2EyZTY4YmQ=
13
+ NTNkOTU5YmU0OGQzNDMyYzAyZWQ1Mjc3ZGRkNWIzOGQ0MjU0NzcxOGRkODNj
14
+ NTcxNzEwZDAxNTAxMTk5Mzk4OGM1Y2U3NWEwMWY0NmY1NjZhZTQyNTA3ODYx
15
+ NDU1NWUxYzYzZWU4NTkyNjgwNzYwYzZjZDVmZGFkMjNjNWQyNjc=
data/CHANGELOG.md CHANGED
@@ -1,13 +1,17 @@
1
1
  # Changelog JSON Schema Tools
2
2
 
3
3
 
4
+ 2014-10
5
+
6
+ * handle circular dependencies in $ref arguments, so schema.to_h always resolves $refs properly
7
+
4
8
  2014-09
5
9
 
6
10
  * add Schema class to represent a single schema, replaces simple Hash usage. Potentially breaks usage if you've gone crazy with Hash methods in your client.
7
11
 
8
12
  2014-09
9
13
 
10
- * refacture Reader to de-reference all pointers when initialized
14
+ * refactor Reader to de-reference all pointers when initialized
11
15
  * remove :exclude_root option for object to schema_hash BREAKING change if you want nesting define your schema accordingly
12
16
  * add :links option for object to schema_hash, to include the links inline in an object
13
17
  * support items definition for array type properties - BREAKING you must change old simple property definitions
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'json'
4
+ require 'uri'
4
5
 
5
6
  module SchemaTools
6
7
  class RefResolver
@@ -13,7 +14,7 @@ module SchemaTools
13
14
  # http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
14
15
  #
15
16
  # @param [String] json_pointer the JSON Pointer expression to evaluate
16
- # @param [Hash] schema if the pointer refers to a local schema, this is this
17
+ # @param [Schema] relative_to if the pointer refers to a local schema, this is this
17
18
  # the hash to evaluate it against. If the pointer contains a uri to a
18
19
  # referenced schema, an attempt is made to load
19
20
  def self.load_json_pointer json_pointer, relative_to = nil
@@ -26,14 +27,25 @@ module SchemaTools
26
27
  uri = $1.strip
27
28
  pointer = $2
28
29
  schema = {}
29
- if ! uri.empty?
30
+ unless uri.empty?
30
31
  uri = URI.parse(uri)
31
32
  raise "must currently be a relative uri: #{json_pointer}" if uri.absolute?
32
33
  # TODO use locale tools instance or base path from global SchemaTools.schema_path
33
- if relative_to
34
- path = relative_to.absolute_dir + "/" + uri.path
35
- else
36
- path = SchemaTools.schema_path + "/" + uri.path
34
+ base_dir = relative_to ? relative_to.absolute_dir : SchemaTools.schema_path
35
+ path = File.join(base_dir, uri.path)
36
+ unless File.exist?(path)
37
+ # try to find in main-dir and subdirs of global schema path and if
38
+ # present a schema's absolute dir
39
+ filename = uri.path.split('/').last
40
+ search_dirs = [File.join(SchemaTools.schema_path, filename),
41
+ File.join(SchemaTools.schema_path, '**/*', filename)]
42
+ if relative_to
43
+ search_dirs += [ File.join(relative_to.absolute_dir, filename),
44
+ File.join(relative_to.absolute_dir, '**/*', filename) ]
45
+ end
46
+ recursive_search = Dir.glob(search_dirs)[0]
47
+ # if still not found keep orig path to throw error on open
48
+ path = recursive_search || path
37
49
  end
38
50
  open (path) {|f| schema = JSON.parse(f.read) }
39
51
  end
@@ -1,10 +1,20 @@
1
+ module SchemaTools
1
2
 
3
+ class CircularReferenceException < ::Exception
4
+ def initialize fn, offending_ref, stack
5
+ @fn = fn
6
+ @stack = stack
7
+ @offending_ref = offending_ref
8
+ end
9
+
10
+ def to_s
11
+ "circular reference: #{@fn} $ref: #{@offending_ref} refers to itself:\n #{@stack.join("\n")}"
12
+ end
13
+ end
2
14
 
3
- module SchemaTools
4
15
  # Internal representation of a Schema. This is basically a wrapper around a
5
16
  # HashWithIndifferentAccess ( for historical purposes ) as well as information
6
17
  # concerning where the Schema was loaded from in order to resolve relative paths.
7
-
8
18
  class Schema
9
19
  #@param [String|Hash] name_or_hash Schema may be initialized with either a filename or a hash
10
20
  def initialize(name_or_hash)
@@ -115,31 +125,47 @@ module SchemaTools
115
125
  # "$ref" param and resolve it. Other params are checked for nested hashes
116
126
  # and those are processed.
117
127
  # @param [HashWithIndifferentAccess] schema - single schema
118
- def resolve_refs schema = nil
128
+ def resolve_refs schema = nil, stack = []
119
129
  schema ||= @hash
120
-
121
- def resolve_reference hash
122
- json_pointer = hash["$ref"]
123
- values_from_pointer = RefResolver.load_json_pointer json_pointer, self
124
- hash.merge!(values_from_pointer) { |key, old, new| old }
125
- hash.delete("$ref")
126
- end
127
-
128
130
  keys = schema.keys # in case you are wondering: RuntimeError: can't add a new key into hash during iteration
129
131
  keys.each do |k|
130
132
  v = schema[k]
131
133
  if k == "$ref"
132
- resolve_reference schema
133
- elsif v.is_a?(ActiveSupport::HashWithIndifferentAccess)
134
- resolve_refs v
134
+ resolve_reference schema, stack
135
+ #stack.clear # ref resolved, reset stack
136
+ elsif v.is_a?(::Hash) || v.is_a?(ActiveSupport::HashWithIndifferentAccess)
137
+ resolve_refs v, stack
135
138
  elsif v.is_a?(Array)
136
- v.each do |i|
137
- resolve_refs(i) if i.is_a?(ActiveSupport::HashWithIndifferentAccess)
139
+ v.each do |element|
140
+ case element
141
+ when ::Hash, ActiveSupport::HashWithIndifferentAccess, ::Array
142
+ resolve_refs element, stack
143
+ end
138
144
  end
139
145
  end
140
146
  end
147
+ schema
148
+ end
141
149
 
150
+ #
151
+ # @param [Hash] hash schema
152
+ def resolve_reference(hash, stack=[])
153
+ json_pointer = hash["$ref"]
154
+ if stack.include? json_pointer
155
+ # we should probably also have a "too many levels of $ref exception or something ..."
156
+ raise CircularReferenceException.new( absolute_filename, json_pointer, stack )
157
+ else
158
+ stack.push json_pointer
159
+ end
160
+ values_from_pointer = RefResolver.load_json_pointer(json_pointer, self)
161
+ # recurse to resolve possible refs in object properties
162
+ resolve_refs(values_from_pointer, stack)
163
+
164
+ hash.merge!(values_from_pointer) { |key, old, new| old }
165
+ hash.delete("$ref")
166
+ stack.pop
142
167
  end
168
+
143
169
  end
144
170
  end
145
171
 
@@ -1,3 +1,3 @@
1
1
  module SchemaTools
2
- VERSION = '0.3.2'
2
+ VERSION = '0.3.3'
3
3
  end
@@ -0,0 +1,10 @@
1
+ {
2
+ "description" : "original impl borked when including the same ref twice in one file",
3
+ "type" : "object",
4
+ "one": {
5
+ "$ref":"./address.json#properties"
6
+ },
7
+ "two": {
8
+ "$ref":"./address.json#properties"
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "type" : "object",
3
+ "one": {
4
+ "$ref":"./circular_references.json#two"
5
+ },
6
+ "two": {
7
+ "$ref":"./circular_references.json#one"
8
+ }
9
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "type" : "object",
3
+ "one": {
4
+ "$ref":"./circular_references_multi.json#two"
5
+ },
6
+ "two": {
7
+ "$ref":"./circular_references_multi.json#three"
8
+ },
9
+ "three" : {
10
+ "something_else" : {
11
+ "$ref": "./circular_references_multi.json#one"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "type" : "object",
3
+ "one": {
4
+ "$ref":"./circular_references_multi_file_two.json#one"
5
+ },
6
+ "two": {
7
+ "$ref":"./circular_references_multi_file_two.json#two"
8
+ },
9
+ "three" : {
10
+ "something_else" : {
11
+ "$ref": "./circular_references_multi_file_two.json#three"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "type" : "object",
3
+ "one": {
4
+ "$ref":"./circular_references_multi_file_one.json#two"
5
+ },
6
+ "two": {
7
+ "$ref":"./circular_references_multi_file_one.json#three"
8
+ },
9
+ "three" : {
10
+ "something_else" : {
11
+ "$ref": "./circular_references_multi_file_one.json#one"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe SchemaTools::Reader do
4
+ BROKEN_SCHEMA_PATH = File.expand_path('../../fixtures_broken', __FILE__)
5
+ context 'circular references' do
6
+ it 'should raise exception for circular $refs' do
7
+ expect{
8
+ schema = SchemaTools::Schema.new("#{BROKEN_SCHEMA_PATH}/circular_references.json")
9
+ }.to raise_exception(SchemaTools::CircularReferenceException)
10
+ end
11
+ it 'should raise exception for more complex circular $refs' do
12
+ expect{
13
+ schema = SchemaTools::Schema.new("#{BROKEN_SCHEMA_PATH}/circular_references_multi.json")
14
+ }.to raise_exception(SchemaTools::CircularReferenceException)
15
+ end
16
+ it 'should raise exception for way multi file circular $refs' do
17
+ expect {
18
+ schema = SchemaTools::Schema.new("#{BROKEN_SCHEMA_PATH}/circular_references_multi_file_one.json")
19
+ }.to raise_exception
20
+ end
21
+ it 'should not raise exception if same $ref is used twice in one file' do
22
+ expect {
23
+ SchemaTools::Reader.read(:circular_references_twice)
24
+ }.to_not raise_exception
25
+ end
26
+ end
27
+ end
28
+
@@ -112,5 +112,6 @@ describe SchemaTools::Reader do
112
112
  expect { reader.read(:client, []) }.to raise_error ArgumentError
113
113
  end
114
114
  end
115
+
115
116
  end
116
117
 
@@ -48,7 +48,6 @@ describe SchemaTools::RefResolver do
48
48
  found.should eq 2
49
49
  end
50
50
  end
51
-
52
51
  it 'should throw an exception on an invalid path' do
53
52
  obj = {}
54
53
  expect {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_schema_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georg Leciejewski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-16 00:00:00.000000000 Z
11
+ date: 2014-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -98,6 +98,7 @@ files:
98
98
  - lib/schema_tools/version.rb
99
99
  - spec/fixtures/address.json
100
100
  - spec/fixtures/basic_definitions.json
101
+ - spec/fixtures/circular_references_twice.json
101
102
  - spec/fixtures/client.json
102
103
  - spec/fixtures/contact.json
103
104
  - spec/fixtures/includes_basic_definitions.json
@@ -107,6 +108,11 @@ files:
107
108
  - spec/fixtures/one_of_definition.json
108
109
  - spec/fixtures/page.json
109
110
  - spec/fixtures/relative_paths/includes_relative_path.json
111
+ - spec/fixtures_broken/circular_references.json
112
+ - spec/fixtures_broken/circular_references_multi.json
113
+ - spec/fixtures_broken/circular_references_multi_file_one.json
114
+ - spec/fixtures_broken/circular_references_multi_file_two.json
115
+ - spec/schema_tools/circuluar_ref_spec.rb
110
116
  - spec/schema_tools/cleaner_spec.rb
111
117
  - spec/schema_tools/hash_spec.rb
112
118
  - spec/schema_tools/klass_factory_spec.rb