json_schema_tools 0.2.6 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.gitignore +6 -0
- data/CHANGELOG.md +16 -0
- data/README.md +14 -0
- data/lib/schema_tools.rb +2 -0
- data/lib/schema_tools/modules/attributes.rb +6 -3
- data/lib/schema_tools/modules/hash.rb +47 -40
- data/lib/schema_tools/modules/read.rb +4 -32
- data/lib/schema_tools/ref_resolver.rb +9 -6
- data/lib/schema_tools/schema.rb +139 -0
- data/lib/schema_tools/version.rb +1 -1
- data/spec/fixtures/basic_definitions.json +7 -0
- data/spec/fixtures/client.json +6 -1
- data/spec/fixtures/contact.json +4 -0
- data/spec/fixtures/includes_deep_nested_refs.json +21 -0
- data/spec/fixtures/lead.json +1 -1
- data/spec/fixtures/page.json +8 -4
- data/spec/fixtures/relative_paths/includes_relative_path.json +6 -0
- data/spec/schema_tools/hash_spec.rb +22 -42
- data/spec/schema_tools/modules/as_schema_spec.rb +12 -17
- data/spec/schema_tools/reader_spec.rb +5 -0
- data/spec/schema_tools/ref_resolver_spec.rb +7 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MmNjZDJjNDRjZTMyNjFjM2U0ZjFhMTRkMTJhZGNkNWVjYmVhZmU2MQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NzI1OWY0OTU1ZDJmMDVmOTMyMTgzZTQ5ZjE3YjRmNDFjMzJmYzBjYg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OTFmODcwMmM2MDBmZmU0YWZlYjIyODMyNjlkM2EyZWY1NTliZWI1YjQwYTU0
|
10
|
+
NGNlYTI1MmE1Y2FlMWY5ZGIwOGQwNzI2OTc5OTNlYTliMDIxMTZkZGQ2ODlj
|
11
|
+
N2NiNjFlNWQ0ZGVmM2E3M2M1NTZiM2U1NzM2ZjNmMmE2MGQ1MjM=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MmMzNGU5NjkxYWQ1NzFkYzkzODcxNzI2NzY3ZTAwODE0OGY1YWUyOTY3ZTE1
|
14
|
+
ZGNjMGFhNGQ5YjU0YzNhZDJjYWIxNWUwN2M2NTNjMDZiMDI4ZmUwMTI0Njc1
|
15
|
+
ZDcyMzkzOTQ5Y2MzNzUwOTViODNkYjE5ZDIzNjY1NTJhNmFjMmI=
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
# Changelog JSON Schema Tools
|
2
2
|
|
3
3
|
|
4
|
+
2014-09
|
5
|
+
|
6
|
+
* 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
|
+
|
8
|
+
2014-09
|
9
|
+
|
10
|
+
* refacture Reader to de-reference all pointers when initialized
|
11
|
+
* remove :exclude_root option for object to schema_hash BREAKING change if you want nesting define your schema accordingly
|
12
|
+
* add :links option for object to schema_hash, to include the links inline in an object
|
13
|
+
* support items definition for array type properties - BREAKING you must change old simple property definitions
|
14
|
+
* support oneOf definitions
|
15
|
+
|
16
|
+
2014-08
|
17
|
+
|
18
|
+
* add $ref resolver to merge property definitions from another file
|
19
|
+
|
4
20
|
2013-10
|
5
21
|
* allow all object properties in link href placeholders => contacts/{id}/{number}
|
6
22
|
* add base_url option to schema hash creation. Prepends an url to all links of a rendered object
|
data/README.md
CHANGED
@@ -245,6 +245,20 @@ reader = SchemaTools::Reader.new
|
|
245
245
|
SchemaTools::KlassFactory.build reader: reader, path: HappyPdf::Schema.path
|
246
246
|
```
|
247
247
|
|
248
|
+
## $ref
|
249
|
+
|
250
|
+
Provides some basic support for JSON Pointer. JSON Pointer expressions must reference local
|
251
|
+
files and must contain a fragment identifier, i.e.
|
252
|
+
|
253
|
+
./some_include.json#properties
|
254
|
+
|
255
|
+
is a resolvable pointer, while
|
256
|
+
|
257
|
+
http://example.com/public_schema.json
|
258
|
+
|
259
|
+
is not.
|
260
|
+
|
261
|
+
|
248
262
|
## Real world examples
|
249
263
|
|
250
264
|
* [DocTag ruby gem](https://github.com/docTag/doctag_rb) and [DocTag json-schema](https://github.com/docTag/doctag_json_schema)
|
data/lib/schema_tools.rb
CHANGED
@@ -5,6 +5,7 @@ require 'schema_tools/modules/hash'
|
|
5
5
|
require 'schema_tools/modules/as_schema'
|
6
6
|
require 'schema_tools/modules/attributes'
|
7
7
|
require 'schema_tools/modules/validations'
|
8
|
+
require 'schema_tools/schema'
|
8
9
|
require 'schema_tools/reader'
|
9
10
|
require 'schema_tools/cleaner'
|
10
11
|
require 'schema_tools/hash'
|
@@ -13,6 +14,7 @@ require 'schema_tools/ref_resolver'
|
|
13
14
|
|
14
15
|
|
15
16
|
module SchemaTools
|
17
|
+
SCHEMA_BASE_TYPES = %w(string number integer boolean)
|
16
18
|
class << self
|
17
19
|
|
18
20
|
# @param [String] path to schema json files
|
@@ -34,7 +34,8 @@ module SchemaTools
|
|
34
34
|
reader = opts[:reader] || SchemaTools::Reader
|
35
35
|
schema_location = opts[:path] || opts[:schema]
|
36
36
|
# remember schema + name on class level
|
37
|
-
|
37
|
+
|
38
|
+
self.schema= reader.read(schema_name, schema_location)
|
38
39
|
self.schema_name(schema_name)
|
39
40
|
# make getter / setter
|
40
41
|
self.schema[:properties].each do |key, prop|
|
@@ -45,8 +46,10 @@ module SchemaTools
|
|
45
46
|
end
|
46
47
|
|
47
48
|
# @param [Hash] schema_hash
|
48
|
-
def schema
|
49
|
-
@schema = schema_hash
|
49
|
+
def schema= schema_hash
|
50
|
+
@schema = schema_hash
|
51
|
+
end
|
52
|
+
def schema
|
50
53
|
@schema
|
51
54
|
end
|
52
55
|
|
@@ -34,8 +34,8 @@ module SchemaTools
|
|
34
34
|
# properties are used.
|
35
35
|
# @options opts [String] :path of the schema files overriding global one
|
36
36
|
# @options opts [String] :base_url used in all links
|
37
|
-
# @options opts [Boolean] :
|
38
|
-
#
|
37
|
+
# @options opts [Boolean] :links if set the object hash gets its _links
|
38
|
+
# array inline.
|
39
39
|
#
|
40
40
|
# @return [Hash{String=>{String=>Mixed}}] The object as hash:
|
41
41
|
# { 'invoice' => {'title'=>'hello world', 'number'=>'4711' } }
|
@@ -47,31 +47,30 @@ module SchemaTools
|
|
47
47
|
class_name = opts[:class_name] || real_class_name
|
48
48
|
|
49
49
|
# get schema
|
50
|
-
|
50
|
+
inline_schema = opts.delete(:schema) if opts[:schema].present?
|
51
|
+
schema = inline_schema || SchemaTools::Reader.read(class_name, opts[:path])
|
52
|
+
|
51
53
|
# iterate over the defined schema fields
|
52
54
|
data = parse_properties(obj, schema, opts)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if opts[:exclude_root]
|
57
|
-
hsh = data
|
58
|
-
hsh['_class_name'] = "#{class_name}"
|
59
|
-
links && hsh['_links'] = links
|
60
|
-
else
|
61
|
-
hsh = { "#{class_name}" => data }
|
62
|
-
links && hsh['links'] = links
|
55
|
+
if opts[:links]
|
56
|
+
links = parse_links(obj, schema, opts)
|
57
|
+
links && data['_links'] = links
|
63
58
|
end
|
64
|
-
|
59
|
+
data
|
65
60
|
end
|
66
61
|
|
67
62
|
private
|
68
63
|
|
64
|
+
# @param [Object] obj from which to grab the properties
|
65
|
+
# @param [Hash] schema
|
66
|
+
# @param [Hash] opts
|
69
67
|
def parse_properties(obj, schema, opts)
|
70
68
|
fields = opts[:fields]
|
71
69
|
data = {}
|
72
70
|
schema['properties'].each do |field, prop|
|
73
71
|
next if fields && !fields.include?(field)
|
74
72
|
if prop['type'] == 'array'
|
73
|
+
# ensure the nested object gets its own class name
|
75
74
|
opts.delete(:class_name)
|
76
75
|
data[field] = parse_list(obj, field, prop, opts)
|
77
76
|
elsif prop['type'] == 'object' # a singular related object
|
@@ -121,16 +120,29 @@ module SchemaTools
|
|
121
120
|
# @return [Array<Hash{String=>String}>]
|
122
121
|
def parse_list(obj, field, prop, opts)
|
123
122
|
res = []
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
123
|
+
# TODO should we raise errors if one of those is missing?
|
124
|
+
return nil if !obj.respond_to?( field )
|
125
|
+
return nil if !prop['items']
|
126
|
+
|
127
|
+
rel_objects = obj.send( field )
|
128
|
+
# force an empty array if values are not present
|
129
|
+
return res if !rel_objects
|
130
|
+
|
131
|
+
if prop['items'].is_a?(Hash) || prop['items'].is_a?(ActiveSupport::HashWithIndifferentAccess)
|
132
|
+
# array of plain values e.g number, strings e.g
|
133
|
+
# "items": { "type": "string" },
|
134
|
+
# should we convert the values? according to the type?
|
135
|
+
if SCHEMA_BASE_TYPES.include?(prop['items']['type'])
|
136
|
+
res = rel_objects
|
137
|
+
elsif prop['items']['type'] == 'object'
|
138
|
+
rel_objects.each { |rel_obj| res << from_schema(rel_obj, opts) }
|
132
139
|
end
|
133
140
|
end
|
141
|
+
|
142
|
+
if prop['items'].is_a?(Array)
|
143
|
+
#TODO recurse
|
144
|
+
# res << rel_objects.each { |rel_obj| parse_list(rel_obj, field, prop opts) }
|
145
|
+
end
|
134
146
|
res
|
135
147
|
end
|
136
148
|
|
@@ -141,24 +153,19 @@ module SchemaTools
|
|
141
153
|
# @param [Hash] opts to_schema options
|
142
154
|
# @return [Array<Hash{String=>String}>]
|
143
155
|
def parse_object(obj, field, prop, opts)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
prop['properties'].each do |fld, prp|
|
158
|
-
res[fld] = rel_obj.send(fld) if rel_obj.respond_to?(fld)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
156
|
+
return if !obj.respond_to?( field )
|
157
|
+
rel_obj = obj.send( field )
|
158
|
+
return if !rel_obj
|
159
|
+
|
160
|
+
res = if prop['properties']
|
161
|
+
opts[:schema] = prop
|
162
|
+
from_schema(rel_obj, opts)
|
163
|
+
elsif prop['oneOf']
|
164
|
+
# auto-detects which schema to use depending on the rel_object type
|
165
|
+
# Simpler than detecting the object type or $ref to use inside the
|
166
|
+
# oneOf array
|
167
|
+
from_schema(rel_obj, opts)
|
168
|
+
end
|
162
169
|
res
|
163
170
|
end
|
164
171
|
|
@@ -35,10 +35,9 @@ module SchemaTools
|
|
35
35
|
def read(schema_name, path_or_schema=nil)
|
36
36
|
schema_name = schema_name.to_sym
|
37
37
|
return registry[schema_name] if registry[schema_name]
|
38
|
-
|
38
|
+
|
39
39
|
if path_or_schema.is_a?(::Hash)
|
40
|
-
|
41
|
-
plain_data = path_or_schema.to_json
|
40
|
+
schema = Schema.new(path_or_schema)
|
42
41
|
elsif path_or_schema.is_a?(::String) || path_or_schema.nil?
|
43
42
|
path = path_or_schema
|
44
43
|
file_path = File.join(path || SchemaTools.schema_path, "#{schema_name}.json")
|
@@ -48,25 +47,16 @@ module SchemaTools
|
|
48
47
|
# use only if we found something, else keep path which will throw error on file.open later
|
49
48
|
file_path = recursive_search || file_path
|
50
49
|
end
|
50
|
+
schema = Schema.new(file_path)
|
51
51
|
else
|
52
52
|
raise ArgumentError, 'Second parameter must be a path or a schema!'
|
53
53
|
end
|
54
54
|
|
55
|
-
plain_data ||= File.open(file_path, 'r'){|f| f.read}
|
56
55
|
|
57
|
-
schema = ActiveSupport::JSON.decode(plain_data).with_indifferent_access
|
58
56
|
# only import object definitions, shared property definitions are handled separate
|
59
57
|
return unless schema[:type] == 'object'
|
60
|
-
if schema[:extends]
|
61
|
-
extends = schema[:extends].is_a?(Array) ? schema[:extends] : [ schema[:extends] ]
|
62
|
-
extends.each do |ext_name|
|
63
|
-
ext = read(ext_name, path)
|
64
|
-
# current schema props win
|
65
|
-
schema[:properties] = ext[:properties].merge(schema[:properties])
|
66
|
-
end
|
67
|
-
end
|
68
|
-
_handle_reference_properties schema
|
69
58
|
registry[ schema_name ] = schema
|
59
|
+
return schema
|
70
60
|
end
|
71
61
|
|
72
62
|
# Read all available schemas from a given path(folder +subfolders) and
|
@@ -93,24 +83,6 @@ module SchemaTools
|
|
93
83
|
schemas
|
94
84
|
end
|
95
85
|
|
96
|
-
# Merge referenced property definitions into the given schema.
|
97
|
-
# e.g. each object has an updated_at field which we define in a single
|
98
|
-
# location(external file) instead of repeating the property def in each
|
99
|
-
# schema.
|
100
|
-
# @param [HashWithIndifferentAccess] schema - single schema
|
101
|
-
def _handle_reference_properties(schema)
|
102
|
-
return unless schema["properties"]
|
103
|
-
schema["properties"].each { |key, value|
|
104
|
-
next unless value["$ref"]
|
105
|
-
|
106
|
-
json_pointer = value["$ref"]
|
107
|
-
values_from_pointer = RefResolver.load_json_pointer json_pointer
|
108
|
-
schema["properties"][key].merge!(values_from_pointer) {|key, old, new|
|
109
|
-
old
|
110
|
-
}
|
111
|
-
schema["properties"][key].delete("$ref")
|
112
|
-
}
|
113
|
-
end
|
114
86
|
end
|
115
87
|
end
|
116
88
|
end
|
@@ -16,7 +16,7 @@ module SchemaTools
|
|
16
16
|
# @param [Hash] schema if the pointer refers to a local schema, this is this
|
17
17
|
# the hash to evaluate it against. If the pointer contains a uri to a
|
18
18
|
# referenced schema, an attempt is made to load
|
19
|
-
def self.load_json_pointer json_pointer,
|
19
|
+
def self.load_json_pointer json_pointer, relative_to = nil
|
20
20
|
# JSON Pointer is a big, abandoned WIP and we're going to
|
21
21
|
# start by only implementing the part's we need ...
|
22
22
|
if nil == (json_pointer =~ /^(.*)#(.*)/ )
|
@@ -25,14 +25,17 @@ module SchemaTools
|
|
25
25
|
|
26
26
|
uri = $1.strip
|
27
27
|
pointer = $2
|
28
|
-
|
28
|
+
schema = {}
|
29
29
|
if ! uri.empty?
|
30
30
|
uri = URI.parse(uri)
|
31
31
|
raise "must currently be a relative uri: #{json_pointer}" if uri.absolute?
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
# 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
|
37
|
+
end
|
38
|
+
open (path) {|f| schema = JSON.parse(f.read) }
|
36
39
|
end
|
37
40
|
|
38
41
|
return self._retrieve_pointer_from_object pointer, schema
|
@@ -0,0 +1,139 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module SchemaTools
|
4
|
+
# Internal representation of a Schema. This is basically a wrapper around a
|
5
|
+
# HashWithIndifferentAccess ( for historical purposes ) as well as information
|
6
|
+
# concerning where the Schema was loaded from in order to resolve relative paths.
|
7
|
+
|
8
|
+
class Schema
|
9
|
+
#@param [String|Hash] name_or_hash Schema may be initialized with either a filename or a hash
|
10
|
+
def initialize(name_or_hash)
|
11
|
+
case name_or_hash
|
12
|
+
when (::Hash)
|
13
|
+
@hash = name_or_hash.with_indifferent_access
|
14
|
+
when (::String)
|
15
|
+
src = File.open(name_or_hash, 'r') { |f| f.read }
|
16
|
+
self.absolute_filename= name_or_hash
|
17
|
+
decode src
|
18
|
+
end
|
19
|
+
handle_extends
|
20
|
+
resolve_refs
|
21
|
+
end
|
22
|
+
|
23
|
+
##################################################################################
|
24
|
+
# Wrappers for internal Hash
|
25
|
+
##################################################################################
|
26
|
+
|
27
|
+
# @param [String|Symbol] key
|
28
|
+
def [](key)
|
29
|
+
@hash[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [String|Symbol] key
|
33
|
+
# @param [Mixed] value e.g String|Array|Hash
|
34
|
+
def []=(key, value)
|
35
|
+
@hash[key] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
@hash.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def keys
|
43
|
+
@hash.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
case other
|
48
|
+
when (::Hash)
|
49
|
+
return other.with_indifferent_access == hash
|
50
|
+
when (ActiveSupport::HashWithIndifferentAccess)
|
51
|
+
return other == hash
|
52
|
+
when (Schema)
|
53
|
+
return other.hash == hash
|
54
|
+
else
|
55
|
+
return false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##################################################################################
|
60
|
+
# /Wrappers for internal Hash
|
61
|
+
##################################################################################
|
62
|
+
|
63
|
+
|
64
|
+
# set the filename the Schema was loaded from
|
65
|
+
# @param [String] fn
|
66
|
+
def absolute_filename=(fn)
|
67
|
+
@absolute_filename = File.absolute_path(fn)
|
68
|
+
@absolute_dir = File.dirname (@absolute_filename)
|
69
|
+
end
|
70
|
+
|
71
|
+
# retrieve the filename the Schema was loaded from or nil if the Schema
|
72
|
+
# was constructed from a Hash
|
73
|
+
def absolute_filename
|
74
|
+
@absolute_filename || nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# retrieve the base directory against which refs should be resolved.
|
78
|
+
def absolute_dir
|
79
|
+
@absolute_dir || SchemaTools.schema_path
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def hash
|
87
|
+
@hash
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# @param [String] src schema json string
|
93
|
+
def decode(src)
|
94
|
+
@hash = ActiveSupport::JSON.decode(src).with_indifferent_access
|
95
|
+
end
|
96
|
+
|
97
|
+
def handle_extends
|
98
|
+
if self[:extends]
|
99
|
+
extends = self[:extends].is_a?(Array) ? self[:extends] : [ self[:extends] ]
|
100
|
+
extends.each do |ext_name|
|
101
|
+
ext = Reader.read(ext_name, absolute_dir)
|
102
|
+
# current schema props win
|
103
|
+
self[:properties] = ext[:properties].merge(self[:properties] || {})
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Merge referenced property definitions into the given schema.
|
109
|
+
# e.g. each object has an updated_at field which we define in a single
|
110
|
+
# location(external file) instead of repeating the property def in each
|
111
|
+
# schema.
|
112
|
+
# any hash found along the way is processed recursively, we look for a
|
113
|
+
# "$ref" param and resolve it. Other params are checked for nested hashes
|
114
|
+
# and those are processed.
|
115
|
+
# @param [HashWithIndifferentAccess] schema - single schema
|
116
|
+
def resolve_refs schema = nil
|
117
|
+
schema ||= @hash
|
118
|
+
|
119
|
+
def resolve_reference hash
|
120
|
+
json_pointer = hash["$ref"]
|
121
|
+
values_from_pointer = RefResolver.load_json_pointer json_pointer, self
|
122
|
+
hash.merge!(values_from_pointer) { |key, old, new| old }
|
123
|
+
hash.delete("$ref")
|
124
|
+
end
|
125
|
+
|
126
|
+
keys = schema.keys # in case you are wondering: RuntimeError: can't add a new key into hash during iteration
|
127
|
+
keys.each do |k|
|
128
|
+
v = schema[k]
|
129
|
+
if k == "$ref"
|
130
|
+
resolve_reference schema
|
131
|
+
elsif v.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
132
|
+
resolve_refs v
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
data/lib/schema_tools/version.rb
CHANGED
data/spec/fixtures/client.json
CHANGED
@@ -63,7 +63,12 @@
|
|
63
63
|
"addresses":{
|
64
64
|
"description": "A client can have many addresses, sorted by date descending(new first). Default address is the most recent one.",
|
65
65
|
"type":"array",
|
66
|
-
"
|
66
|
+
"items" : {
|
67
|
+
"type" : "object",
|
68
|
+
"properties":{
|
69
|
+
"$ref":"./address.json#properties"
|
70
|
+
}
|
71
|
+
}
|
67
72
|
},
|
68
73
|
"work_address":{
|
69
74
|
"description": "The work address as an example for a single nested/related object",
|
data/spec/fixtures/contact.json
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"type" : "object",
|
3
|
+
"description" : "deeply nested",
|
4
|
+
"properties" : {
|
5
|
+
"contains_nesting" : {
|
6
|
+
"type" : "array",
|
7
|
+
"items" : {
|
8
|
+
"type" : "object",
|
9
|
+
"properties" : {
|
10
|
+
"own_property" : { "type" : "string" },
|
11
|
+
"nested_ref" : {
|
12
|
+
"type" : "object",
|
13
|
+
"properties" : {
|
14
|
+
"$ref" : "./basic_definitions.json#definitions/nested_object/properties"
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
data/spec/fixtures/lead.json
CHANGED
data/spec/fixtures/page.json
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
"type": "object",
|
3
3
|
"title": "Page",
|
4
4
|
"name": "page",
|
5
|
-
"description": "A page of a PDF which can either belong to a PDF template or archived PDF.",
|
5
|
+
"description": "A page of a PDF which can either belong to a PDF template or archived PDF. Example taken from happyPDF",
|
6
6
|
"properties": {
|
7
7
|
"id": {
|
8
8
|
"description": "The object identifier.",
|
@@ -57,11 +57,15 @@
|
|
57
57
|
"type": "integer"
|
58
58
|
},
|
59
59
|
"blocks": {
|
60
|
-
"description": "Placeholder blocks on this page
|
60
|
+
"description": "Placeholder blocks on this page, if it belongs to a PDF Template",
|
61
61
|
"type": "array",
|
62
|
-
"
|
62
|
+
"items" : {
|
63
|
+
"type" : "object",
|
64
|
+
"properties":{
|
65
|
+
"$ref": "./basic_definitions.json#definitions/nested_object/properties"
|
66
|
+
}
|
67
|
+
}
|
63
68
|
}
|
64
|
-
|
65
69
|
},
|
66
70
|
"links": [
|
67
71
|
{
|
@@ -45,43 +45,33 @@ describe SchemaTools::Hash do
|
|
45
45
|
|
46
46
|
it 'should return hash' do
|
47
47
|
hash = SchemaTools::Hash.from_schema(contact)
|
48
|
-
hash['
|
48
|
+
hash['last_name'].should == 'Paul'
|
49
49
|
end
|
50
50
|
|
51
51
|
it 'should use custom schema path' do
|
52
52
|
custom_path = File.expand_path('../../fixtures', __FILE__)
|
53
53
|
hash = SchemaTools::Hash.from_schema(contact, path: custom_path)
|
54
|
-
hash['
|
54
|
+
hash['last_name'].should == 'Paul'
|
55
55
|
end
|
56
56
|
|
57
57
|
it 'should use custom schema' do
|
58
58
|
hash = SchemaTools::Hash.from_schema(contact, class_name: :client)
|
59
|
-
hash['
|
59
|
+
hash['last_name'].should == 'Paul'
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'should use only give fields' do
|
63
63
|
hash = SchemaTools::Hash.from_schema(contact, fields: ['id', 'last_name'])
|
64
|
-
hash
|
65
|
-
hash['
|
66
|
-
hash['
|
67
|
-
hash['
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'should exclude root' do
|
71
|
-
hash = SchemaTools::Hash.from_schema(contact, exclude_root: true)
|
72
|
-
hash['last_name'].should == 'Paul'
|
73
|
-
hash['_class_name'].should == 'contact'
|
64
|
+
hash.keys.length.should == 2
|
65
|
+
hash['last_name'].should == contact.last_name
|
66
|
+
hash['id'].should == contact.id
|
67
|
+
hash['first_name'].should be_nil
|
74
68
|
end
|
75
69
|
|
76
70
|
it 'has _links on object if exclude root' do
|
77
|
-
hash = SchemaTools::Hash.from_schema(contact,
|
71
|
+
hash = SchemaTools::Hash.from_schema(contact, links: true, class_name: :client)
|
78
72
|
hash['_links'].length.should == 7
|
79
73
|
end
|
80
74
|
|
81
|
-
it 'has _class_name on object if exclude root' do
|
82
|
-
hash = SchemaTools::Hash.from_schema(contact, exclude_root: true, class_name: :client)
|
83
|
-
hash['_class_name'].should == 'client'
|
84
|
-
end
|
85
75
|
end
|
86
76
|
|
87
77
|
context 'with nested values referencing a schema' do
|
@@ -90,12 +80,12 @@ describe SchemaTools::Hash do
|
|
90
80
|
|
91
81
|
it 'has an empty array if values are missing' do
|
92
82
|
hash = SchemaTools::Hash.from_schema(client)
|
93
|
-
hash['
|
83
|
+
hash['addresses'].should == []
|
94
84
|
end
|
95
85
|
|
96
86
|
it 'has nil if nested object is missing' do
|
97
87
|
hash = SchemaTools::Hash.from_schema(client)
|
98
|
-
hash['
|
88
|
+
hash['work_address'].should be_nil
|
99
89
|
end
|
100
90
|
|
101
91
|
it 'has nested array values' do
|
@@ -104,7 +94,7 @@ describe SchemaTools::Hash do
|
|
104
94
|
a1.zip = 50733
|
105
95
|
client.addresses = [a1]
|
106
96
|
hash = SchemaTools::Hash.from_schema(client)
|
107
|
-
hash['
|
97
|
+
hash['addresses'].should == [{"city"=>"Cologne", "zip"=>50733}]
|
108
98
|
end
|
109
99
|
|
110
100
|
it 'has nested array values without root' do
|
@@ -113,7 +103,7 @@ describe SchemaTools::Hash do
|
|
113
103
|
a1.zip = 50733
|
114
104
|
client.addresses = [a1]
|
115
105
|
hash = SchemaTools::Hash.from_schema(client, exclude_root: true)
|
116
|
-
hash['addresses'].should == [{"city"=>"Cologne", "zip"=>50733
|
106
|
+
hash['addresses'].should == [{"city"=>"Cologne", "zip"=>50733}]
|
117
107
|
end
|
118
108
|
|
119
109
|
it 'has nested object value' do
|
@@ -122,16 +112,7 @@ describe SchemaTools::Hash do
|
|
122
112
|
a1.zip = 50733
|
123
113
|
client.work_address = a1
|
124
114
|
hash = SchemaTools::Hash.from_schema(client)
|
125
|
-
hash['
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'has nested object value without root' do
|
129
|
-
a1 = Address.new
|
130
|
-
a1.city = 'Cologne'
|
131
|
-
a1.zip = 50733
|
132
|
-
client.work_address = a1
|
133
|
-
hash = SchemaTools::Hash.from_schema(client, exclude_root: true)
|
134
|
-
hash['work_address'].should == {"city"=>"Cologne", "zip"=>50733, "_class_name"=>"address"}
|
115
|
+
hash['work_address'].should == {"city"=>"Cologne", "zip"=>50733}
|
135
116
|
end
|
136
117
|
|
137
118
|
it 'has nested oneOf type object ' do
|
@@ -143,7 +124,6 @@ describe SchemaTools::Hash do
|
|
143
124
|
|
144
125
|
hash = SchemaTools::Hash.from_schema(i, exclude_root: true)
|
145
126
|
hash['person']['first_name'].should == 'Pit'
|
146
|
-
hash['person']['_class_name'].should == 'contact'
|
147
127
|
end
|
148
128
|
|
149
129
|
end
|
@@ -164,12 +144,12 @@ describe SchemaTools::Hash do
|
|
164
144
|
end
|
165
145
|
|
166
146
|
it 'should create array with values' do
|
167
|
-
@hash['
|
147
|
+
@hash['links_clicked'].should == lead.links_clicked
|
168
148
|
end
|
169
149
|
|
170
150
|
it 'should create object with values' do
|
171
|
-
@hash['
|
172
|
-
@hash['
|
151
|
+
@hash['conversion']['from'].should == lead.conversion.from
|
152
|
+
@hash['conversion']['to'].should == lead.conversion.to
|
173
153
|
end
|
174
154
|
|
175
155
|
end
|
@@ -185,19 +165,19 @@ describe SchemaTools::Hash do
|
|
185
165
|
end
|
186
166
|
|
187
167
|
it 'has links' do
|
188
|
-
hash = SchemaTools::Hash.from_schema(client)
|
189
|
-
hash['
|
168
|
+
hash = SchemaTools::Hash.from_schema(client, links: true)
|
169
|
+
hash['_links'].length.should == 7
|
190
170
|
end
|
191
171
|
|
192
172
|
it 'should prepend base_url' do
|
193
|
-
hash = SchemaTools::Hash.from_schema(client, base_url: 'http://json-hell.com')
|
194
|
-
hash['
|
173
|
+
hash = SchemaTools::Hash.from_schema(client, base_url: 'http://json-hell.com', links: true)
|
174
|
+
hash['_links'].first['href'].should include( 'http://json-hell.com')
|
195
175
|
end
|
196
176
|
|
197
177
|
it 'should replace placeholders' do
|
198
178
|
client.id = 123
|
199
|
-
hash = SchemaTools::Hash.from_schema(client, base_url: 'http://json-hell.com')
|
200
|
-
hash['
|
179
|
+
hash = SchemaTools::Hash.from_schema(client, base_url: 'http://json-hell.com', links: true)
|
180
|
+
hash['_links'].last['href'].should == 'http://json-hell.com/clients/123/Peter'
|
201
181
|
end
|
202
182
|
|
203
183
|
end
|
@@ -3,9 +3,11 @@ require 'spec_helper'
|
|
3
3
|
class ClassWithSchemaAttrs
|
4
4
|
include SchemaTools::Modules::Attributes
|
5
5
|
has_schema_attrs :client
|
6
|
+
# test override of schema name
|
7
|
+
attr_accessor :contact_source
|
6
8
|
end
|
7
9
|
|
8
|
-
class
|
10
|
+
class ClassWithSchemaNameLead
|
9
11
|
include SchemaTools::Modules::AsSchema
|
10
12
|
schema_name :lead
|
11
13
|
end
|
@@ -14,6 +16,7 @@ end
|
|
14
16
|
module Test
|
15
17
|
class Address
|
16
18
|
include SchemaTools::Modules::AsSchema
|
19
|
+
attr_accessor :id, :city
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
@@ -34,14 +37,14 @@ describe SchemaTools::Modules::AsSchema do
|
|
34
37
|
it 'should return hash' do
|
35
38
|
subject.last_name = 'Hogan'
|
36
39
|
hsh = subject.as_schema_hash
|
37
|
-
hsh['
|
40
|
+
hsh['last_name'].should == 'Hogan'
|
38
41
|
end
|
39
42
|
|
40
43
|
it 'should return json' do
|
41
44
|
subject.last_name = 'Hogan'
|
42
45
|
json_str = subject.as_schema_json
|
43
46
|
hsh = ActiveSupport::JSON.decode(json_str)
|
44
|
-
hsh['
|
47
|
+
hsh['last_name'].should == 'Hogan'
|
45
48
|
end
|
46
49
|
|
47
50
|
end
|
@@ -49,15 +52,15 @@ describe SchemaTools::Modules::AsSchema do
|
|
49
52
|
describe 'schema name detection' do
|
50
53
|
|
51
54
|
it 'should use name from has_schema_attrs' do
|
52
|
-
ClassWithSchemaAttrs.new.as_schema_hash.keys.should include('
|
55
|
+
ClassWithSchemaAttrs.new.as_schema_hash.keys.should include('phone_mobile', 'cash_discount')
|
53
56
|
end
|
54
57
|
|
55
58
|
it 'should use schema_name defined in class' do
|
56
|
-
|
59
|
+
ClassWithSchemaNameLead.new.as_schema_hash.keys.should include('links_clicked')
|
57
60
|
end
|
58
61
|
|
59
|
-
it 'should
|
60
|
-
Test::Address.new.as_schema_hash.keys.should include('
|
62
|
+
it 'should infer schema from class name' do
|
63
|
+
Test::Address.new.as_schema_hash.keys.should include('city')
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
@@ -65,19 +68,11 @@ describe SchemaTools::Modules::AsSchema do
|
|
65
68
|
subject { ClassWithSchemaAttrs.new }
|
66
69
|
|
67
70
|
it 'should override schema name from' do
|
68
|
-
subject.as_schema_hash(class_name:'contact').keys.should include('
|
71
|
+
subject.as_schema_hash(class_name:'contact').keys.should include('contact_source')
|
69
72
|
end
|
70
73
|
|
71
74
|
it 'should use fields' do
|
72
|
-
subject.as_schema_hash(fields:['id'])
|
73
|
-
end
|
74
|
-
|
75
|
-
it 'should exclude root' do
|
76
|
-
subject.as_schema_hash(exclude_root: true).keys.should include('id')
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'should use class name ' do
|
80
|
-
Test::Address.new.as_schema_hash.keys.should include('address')
|
75
|
+
subject.as_schema_hash(fields:['id']).keys.should == ['id']
|
81
76
|
end
|
82
77
|
end
|
83
78
|
|
@@ -56,6 +56,11 @@ describe SchemaTools::Reader do
|
|
56
56
|
schema[:properties][:id]["$ref"].should be_nil
|
57
57
|
end
|
58
58
|
|
59
|
+
it 'deals with nested referenced parameters properly' do
|
60
|
+
schema = SchemaTools::Reader.read(:includes_deep_nested_refs)
|
61
|
+
schema[:properties].should_not be_empty
|
62
|
+
end
|
63
|
+
|
59
64
|
it 'enforces correct parameter usage' do
|
60
65
|
expect { SchemaTools::Reader.read(:contact, []) }.to raise_error ArgumentError
|
61
66
|
end
|
@@ -66,6 +66,12 @@ describe SchemaTools::RefResolver do
|
|
66
66
|
it 'should load local ref' do
|
67
67
|
pointer = "./basic_definitions.json#definitions"
|
68
68
|
obj = SchemaTools::RefResolver.load_json_pointer(pointer)
|
69
|
-
obj.length.should eq
|
69
|
+
obj.length.should eq 3
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should load the entire document' do
|
73
|
+
pointer = "./basic_definitions.json#"
|
74
|
+
obj = SchemaTools::RefResolver.load_json_pointer(pointer)
|
75
|
+
obj["definitions"].keys.length.should eq 3
|
70
76
|
end
|
71
77
|
end
|
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.
|
4
|
+
version: 0.3.0
|
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-09
|
11
|
+
date: 2014-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -94,16 +94,19 @@ files:
|
|
94
94
|
- lib/schema_tools/modules/validations.rb
|
95
95
|
- lib/schema_tools/reader.rb
|
96
96
|
- lib/schema_tools/ref_resolver.rb
|
97
|
+
- lib/schema_tools/schema.rb
|
97
98
|
- lib/schema_tools/version.rb
|
98
99
|
- spec/fixtures/address.json
|
99
100
|
- spec/fixtures/basic_definitions.json
|
100
101
|
- spec/fixtures/client.json
|
101
102
|
- spec/fixtures/contact.json
|
102
103
|
- spec/fixtures/includes_basic_definitions.json
|
104
|
+
- spec/fixtures/includes_deep_nested_refs.json
|
103
105
|
- spec/fixtures/lead.json
|
104
106
|
- spec/fixtures/nested_schemas/person.json
|
105
107
|
- spec/fixtures/one_of_definition.json
|
106
108
|
- spec/fixtures/page.json
|
109
|
+
- spec/fixtures/relative_paths/includes_relative_path.json
|
107
110
|
- spec/schema_tools/cleaner_spec.rb
|
108
111
|
- spec/schema_tools/hash_spec.rb
|
109
112
|
- spec/schema_tools/klass_factory_spec.rb
|