shale 0.2.2 → 0.4.0
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 +4 -4
- data/CHANGELOG.md +33 -0
- data/README.md +366 -8
- data/exe/shaleb +123 -0
- data/lib/shale/adapter/json.rb +7 -2
- data/lib/shale/adapter/nokogiri.rb +48 -12
- data/lib/shale/adapter/ox.rb +28 -4
- data/lib/shale/adapter/rexml.rb +56 -13
- data/lib/shale/attribute.rb +7 -1
- data/lib/shale/error.rb +12 -0
- data/lib/shale/mapper.rb +17 -15
- data/lib/shale/mapping/descriptor/dict.rb +57 -0
- data/lib/shale/mapping/descriptor/xml.rb +43 -0
- data/lib/shale/mapping/descriptor/xml_namespace.rb +37 -0
- data/lib/shale/mapping/{key_value.rb → dict.rb} +8 -6
- data/lib/shale/mapping/validator.rb +51 -0
- data/lib/shale/mapping/xml.rb +86 -15
- data/lib/shale/schema/json_compiler/boolean.rb +21 -0
- data/lib/shale/schema/json_compiler/date.rb +21 -0
- data/lib/shale/schema/json_compiler/float.rb +21 -0
- data/lib/shale/schema/json_compiler/integer.rb +21 -0
- data/lib/shale/schema/json_compiler/object.rb +85 -0
- data/lib/shale/schema/json_compiler/property.rb +70 -0
- data/lib/shale/schema/json_compiler/string.rb +21 -0
- data/lib/shale/schema/json_compiler/time.rb +21 -0
- data/lib/shale/schema/json_compiler/utils.rb +52 -0
- data/lib/shale/schema/json_compiler/value.rb +13 -0
- data/lib/shale/schema/json_compiler.rb +333 -0
- data/lib/shale/schema/json_generator/base.rb +41 -0
- data/lib/shale/schema/json_generator/boolean.rb +23 -0
- data/lib/shale/schema/json_generator/collection.rb +39 -0
- data/lib/shale/schema/json_generator/date.rb +23 -0
- data/lib/shale/schema/json_generator/float.rb +23 -0
- data/lib/shale/schema/json_generator/integer.rb +23 -0
- data/lib/shale/schema/json_generator/object.rb +40 -0
- data/lib/shale/schema/json_generator/ref.rb +28 -0
- data/lib/shale/schema/json_generator/schema.rb +59 -0
- data/lib/shale/schema/json_generator/string.rb +23 -0
- data/lib/shale/schema/json_generator/time.rb +23 -0
- data/lib/shale/schema/json_generator/value.rb +23 -0
- data/lib/shale/schema/json_generator.rb +165 -0
- data/lib/shale/schema/xml_generator/attribute.rb +41 -0
- data/lib/shale/schema/xml_generator/complex_type.rb +70 -0
- data/lib/shale/schema/xml_generator/element.rb +55 -0
- data/lib/shale/schema/xml_generator/import.rb +46 -0
- data/lib/shale/schema/xml_generator/ref_attribute.rb +37 -0
- data/lib/shale/schema/xml_generator/ref_element.rb +39 -0
- data/lib/shale/schema/xml_generator/schema.rb +121 -0
- data/lib/shale/schema/xml_generator/typed_attribute.rb +46 -0
- data/lib/shale/schema/xml_generator/typed_element.rb +46 -0
- data/lib/shale/schema/xml_generator.rb +315 -0
- data/lib/shale/schema.rb +70 -0
- data/lib/shale/type/boolean.rb +2 -2
- data/lib/shale/type/composite.rb +78 -72
- data/lib/shale/type/date.rb +35 -2
- data/lib/shale/type/float.rb +2 -2
- data/lib/shale/type/integer.rb +2 -2
- data/lib/shale/type/string.rb +2 -2
- data/lib/shale/type/time.rb +35 -2
- data/lib/shale/type/{base.rb → value.rb} +18 -7
- data/lib/shale/utils.rb +18 -2
- data/lib/shale/version.rb +1 -1
- data/lib/shale.rb +10 -10
- data/shale.gemspec +6 -2
- metadata +53 -13
- data/lib/shale/mapping/base.rb +0 -32
@@ -0,0 +1,333 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
require_relative '../../shale'
|
7
|
+
require_relative 'json_compiler/boolean'
|
8
|
+
require_relative 'json_compiler/date'
|
9
|
+
require_relative 'json_compiler/float'
|
10
|
+
require_relative 'json_compiler/integer'
|
11
|
+
require_relative 'json_compiler/object'
|
12
|
+
require_relative 'json_compiler/property'
|
13
|
+
require_relative 'json_compiler/string'
|
14
|
+
require_relative 'json_compiler/time'
|
15
|
+
require_relative 'json_compiler/value'
|
16
|
+
|
17
|
+
module Shale
|
18
|
+
module Schema
|
19
|
+
# Class for compiling JSON schema into Ruby data model
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
class JSONCompiler
|
23
|
+
# Default root type name
|
24
|
+
# @api private
|
25
|
+
DEFAULT_ROOT_NAME = 'root'
|
26
|
+
|
27
|
+
# Shale model template
|
28
|
+
# @api private
|
29
|
+
MODEL_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: '-')
|
30
|
+
require 'shale'
|
31
|
+
<%- unless type.references.empty? -%>
|
32
|
+
|
33
|
+
<%- type.references.each do |property| -%>
|
34
|
+
require_relative '<%= property.type.file_name %>'
|
35
|
+
<%- end -%>
|
36
|
+
<%- end -%>
|
37
|
+
|
38
|
+
class <%= type.name %> < Shale::Mapper
|
39
|
+
<%- type.properties.each do |property| -%>
|
40
|
+
attribute :<%= property.attribute_name %>, <%= property.type.name -%>
|
41
|
+
<%- if property.collection? %>, collection: true<% end -%>
|
42
|
+
<%- unless property.default.nil? %>, default: -> { <%= property.default %> }<% end %>
|
43
|
+
<%- end -%>
|
44
|
+
|
45
|
+
json do
|
46
|
+
<%- type.properties.each do |property| -%>
|
47
|
+
map '<%= property.property_name %>', to: :<%= property.attribute_name %>
|
48
|
+
<%- end -%>
|
49
|
+
end
|
50
|
+
end
|
51
|
+
TEMPLATE
|
52
|
+
|
53
|
+
# Generate Shale models from JSON Schema and return them as a Ruby Array od objects
|
54
|
+
#
|
55
|
+
# @param [Array<String>] schemas
|
56
|
+
# @param [String, nil] root_name
|
57
|
+
#
|
58
|
+
# @raise [JSONSchemaError] when JSON Schema has errors
|
59
|
+
#
|
60
|
+
# @return [Array<Shale::Schema::JSONCompiler::Object>]
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# Shale::Schema::JSONCompiler.new.as_models([schema1, schema2])
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def as_models(schemas, root_name: nil)
|
67
|
+
schemas = schemas.map do |schema|
|
68
|
+
Shale.json_adapter.load(schema)
|
69
|
+
end
|
70
|
+
|
71
|
+
@root_name = root_name
|
72
|
+
@schema_repository = {}
|
73
|
+
@types = []
|
74
|
+
|
75
|
+
schemas.each do |schema|
|
76
|
+
disassemble_schema(schema)
|
77
|
+
end
|
78
|
+
|
79
|
+
compile(schemas[0], true)
|
80
|
+
|
81
|
+
total_duplicates = Hash.new(0)
|
82
|
+
duplicates = Hash.new(0)
|
83
|
+
|
84
|
+
# rubocop:disable Style/CombinableLoops
|
85
|
+
@types.each do |type|
|
86
|
+
total_duplicates[type.name] += 1
|
87
|
+
end
|
88
|
+
|
89
|
+
@types.each do |type|
|
90
|
+
duplicates[type.name] += 1
|
91
|
+
|
92
|
+
if total_duplicates[type.name] > 1
|
93
|
+
type.name = format("#{type.name}%d", duplicates[type.name])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
# rubocop:enable Style/CombinableLoops
|
97
|
+
|
98
|
+
@types.reverse
|
99
|
+
end
|
100
|
+
|
101
|
+
# Generate Shale models from JSON Schema
|
102
|
+
#
|
103
|
+
# @param [Array<String>] schemas
|
104
|
+
# @param [String, nil] root_name
|
105
|
+
#
|
106
|
+
# @raise [JSONSchemaError] when JSON Schema has errors
|
107
|
+
#
|
108
|
+
# @return [Hash<String, String>]
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# Shale::Schema::JSONCompiler.new.to_models([schema1, schema2])
|
112
|
+
#
|
113
|
+
# @api public
|
114
|
+
def to_models(schemas, root_name: nil)
|
115
|
+
types = as_models(schemas, root_name: root_name)
|
116
|
+
|
117
|
+
types.to_h do |type|
|
118
|
+
[type.file_name, MODEL_TEMPLATE.result(binding)]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Generate JSON Schema id
|
125
|
+
#
|
126
|
+
# @param [String, nil] base_id
|
127
|
+
# @param [String, nil] id
|
128
|
+
#
|
129
|
+
# @return [String]
|
130
|
+
#
|
131
|
+
# @api private
|
132
|
+
def build_id(base_id, id)
|
133
|
+
return base_id unless id
|
134
|
+
|
135
|
+
base_uri = URI(base_id || '')
|
136
|
+
uri = URI(id)
|
137
|
+
|
138
|
+
if base_uri.relative? && uri.relative?
|
139
|
+
uri.to_s
|
140
|
+
else
|
141
|
+
base_uri.merge(uri).to_s
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Generate JSON pointer
|
146
|
+
#
|
147
|
+
# @param [String, nil] id
|
148
|
+
# @param [Array<String>] fragment
|
149
|
+
#
|
150
|
+
# @return [String]
|
151
|
+
#
|
152
|
+
# @api private
|
153
|
+
def build_pointer(id, fragment)
|
154
|
+
([id, ['#', *fragment].join('/')] - ['#']).join
|
155
|
+
end
|
156
|
+
|
157
|
+
# Resolve JSON Schem ref
|
158
|
+
#
|
159
|
+
# @param [String, nil] base_id
|
160
|
+
# @param [String, nil] ref
|
161
|
+
#
|
162
|
+
# @raise [JSONSchemaError] when ref can't be resolved
|
163
|
+
#
|
164
|
+
# @return [Hash, true, false]
|
165
|
+
#
|
166
|
+
# @api private
|
167
|
+
def resolve_ref(base_id, ref)
|
168
|
+
ref_id, fragment = (ref || '').split('#')
|
169
|
+
id = build_id(base_id, ref_id == '' ? nil : ref_id)
|
170
|
+
key = [id, fragment].compact.join('#')
|
171
|
+
|
172
|
+
entry = @schema_repository[key]
|
173
|
+
|
174
|
+
unless entry
|
175
|
+
raise JSONSchemaError, "can't resolve reference '#{key}'"
|
176
|
+
end
|
177
|
+
|
178
|
+
if entry[:schema].key?('$ref')
|
179
|
+
resolve_ref(id, entry[:schema]['$ref'])
|
180
|
+
else
|
181
|
+
entry
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Get Shale type from JSON Schema type
|
186
|
+
#
|
187
|
+
# @param [Hash, true, false, nil] schema
|
188
|
+
# @param [String] id
|
189
|
+
# @param [String] name
|
190
|
+
#
|
191
|
+
# @return [Shale::Schema::JSONCompiler::Type]
|
192
|
+
#
|
193
|
+
# @api private
|
194
|
+
def infer_type(schema, id, name)
|
195
|
+
return unless schema
|
196
|
+
return Value.new if schema == true
|
197
|
+
|
198
|
+
type = schema['type']
|
199
|
+
format = schema['format']
|
200
|
+
|
201
|
+
if type.is_a?(Array)
|
202
|
+
type -= ['null']
|
203
|
+
|
204
|
+
if type.length > 1
|
205
|
+
return Value.new
|
206
|
+
else
|
207
|
+
type = type[0]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
if type == 'object'
|
212
|
+
Object.new(id, name)
|
213
|
+
elsif type == 'string' && format == 'date'
|
214
|
+
Date.new
|
215
|
+
elsif type == 'string' && format == 'date-time'
|
216
|
+
Time.new
|
217
|
+
elsif type == 'string'
|
218
|
+
String.new
|
219
|
+
elsif type == 'number'
|
220
|
+
Float.new
|
221
|
+
elsif type == 'integer'
|
222
|
+
Integer.new
|
223
|
+
elsif type == 'boolean'
|
224
|
+
Boolean.new
|
225
|
+
else
|
226
|
+
Value.new
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Disassemble JSON schema into separate subschemas
|
231
|
+
#
|
232
|
+
# @param [String] schema
|
233
|
+
# @param [Array<String>] fragment
|
234
|
+
# @param [String] base_id
|
235
|
+
#
|
236
|
+
# @raise [JSONSchemaError] when there are problems with JSON schema
|
237
|
+
#
|
238
|
+
# @api private
|
239
|
+
def disassemble_schema(schema, fragment = [], base_id = '')
|
240
|
+
schema_key = fragment[-1]
|
241
|
+
|
242
|
+
if schema.is_a?(Hash) && schema.key?('$id')
|
243
|
+
id = build_id(base_id, schema['$id'])
|
244
|
+
fragment = []
|
245
|
+
else
|
246
|
+
id = base_id
|
247
|
+
end
|
248
|
+
|
249
|
+
pointer = build_pointer(id, fragment)
|
250
|
+
|
251
|
+
if @schema_repository.key?(pointer)
|
252
|
+
raise JSONSchemaError, "schema with id '#{pointer}' already exists"
|
253
|
+
else
|
254
|
+
@schema_repository[pointer] = {
|
255
|
+
id: pointer,
|
256
|
+
key: schema_key,
|
257
|
+
schema: schema,
|
258
|
+
}
|
259
|
+
end
|
260
|
+
|
261
|
+
return unless schema.is_a?(Hash)
|
262
|
+
|
263
|
+
['properties', '$defs'].each do |definitions|
|
264
|
+
(schema[definitions] || {}).each do |subschema_key, subschema|
|
265
|
+
disassemble_schema(subschema, [*fragment, definitions, subschema_key], id)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Compile JSON schema into Shale types
|
271
|
+
#
|
272
|
+
# @param [String] schema
|
273
|
+
# @param [true, false] is_root
|
274
|
+
# @param [String] base_id
|
275
|
+
# @param [Array<String>] fragment
|
276
|
+
#
|
277
|
+
# @return [Shale::Schema::JSONCompiler::Property, nil]
|
278
|
+
#
|
279
|
+
# @api private
|
280
|
+
def compile(schema, is_root, base_id = '', fragment = [])
|
281
|
+
entry = {}
|
282
|
+
key = fragment[-1]
|
283
|
+
collection = false
|
284
|
+
default = nil
|
285
|
+
|
286
|
+
if schema.is_a?(Hash) && schema.key?('$id')
|
287
|
+
id = build_id(base_id, schema['$id'])
|
288
|
+
fragment = []
|
289
|
+
else
|
290
|
+
id = base_id
|
291
|
+
end
|
292
|
+
|
293
|
+
if schema.is_a?(Hash) && schema['type'] == 'array'
|
294
|
+
collection = true
|
295
|
+
schema = schema['items']
|
296
|
+
schema ||= true
|
297
|
+
end
|
298
|
+
|
299
|
+
if schema.is_a?(Hash) && schema.key?('$ref')
|
300
|
+
entry = resolve_ref(id, schema['$ref'])
|
301
|
+
schema = entry[:schema]
|
302
|
+
fragment = entry[:id].split('/') - ['#']
|
303
|
+
end
|
304
|
+
|
305
|
+
pointer = entry[:id] || build_pointer(id, fragment)
|
306
|
+
|
307
|
+
if is_root
|
308
|
+
name = @root_name || entry[:key] || key || DEFAULT_ROOT_NAME
|
309
|
+
else
|
310
|
+
name = entry[:key] || key
|
311
|
+
end
|
312
|
+
|
313
|
+
type = @types.find { |e| e.id == pointer }
|
314
|
+
type ||= infer_type(schema, pointer, name)
|
315
|
+
|
316
|
+
if schema.is_a?(Hash) && schema.key?('default')
|
317
|
+
default = schema['default']
|
318
|
+
end
|
319
|
+
|
320
|
+
if type.is_a?(Object) && !@types.include?(type)
|
321
|
+
@types << type
|
322
|
+
|
323
|
+
(schema['properties'] || {}).each do |subschema_key, subschema|
|
324
|
+
property = compile(subschema, false, id, [*fragment, 'properties', subschema_key])
|
325
|
+
type.add_property(property) if property
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
Property.new(key, type, collection, default) if type
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shale
|
4
|
+
module Schema
|
5
|
+
class JSONGenerator
|
6
|
+
# Base class for JSON Schema types
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Base
|
10
|
+
# Return name
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# Return nullable
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
attr_writer :nullable
|
19
|
+
|
20
|
+
def initialize(name, default: nil)
|
21
|
+
@name = name.gsub('::', '_')
|
22
|
+
@default = default
|
23
|
+
@nullable = true
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return JSON Schema fragment as Ruby Hash
|
27
|
+
#
|
28
|
+
# @return [Hash]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def as_json
|
32
|
+
type = as_type
|
33
|
+
type['type'] = [*type['type'], 'null'] if type['type'] && @nullable
|
34
|
+
type['default'] = @default unless @default.nil?
|
35
|
+
|
36
|
+
type
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema boolean type
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Boolean < Base
|
12
|
+
# Return JSON Schema fragment as Ruby Hash
|
13
|
+
#
|
14
|
+
# @return [Hash]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def as_type
|
18
|
+
{ 'type' => 'boolean' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shale
|
4
|
+
module Schema
|
5
|
+
class JSONGenerator
|
6
|
+
# Class representing array type in JSON Schema
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Collection
|
10
|
+
# Initialize Collection object
|
11
|
+
#
|
12
|
+
# @param [Shale::Schema::JSONGenerator::Base] type
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
def initialize(type)
|
16
|
+
@type = type
|
17
|
+
end
|
18
|
+
|
19
|
+
# Delegate name to wrapped type object
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
def name
|
25
|
+
@type.name
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return JSON Schema fragment as Ruby Hash
|
29
|
+
#
|
30
|
+
# @return [Hash]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def as_json
|
34
|
+
{ 'type' => 'array', 'items' => @type.as_type }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema date type
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Date < Base
|
12
|
+
# Return JSON Schema fragment as Ruby Hash
|
13
|
+
#
|
14
|
+
# @return [Hash]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def as_type
|
18
|
+
{ 'type' => 'string', 'format' => 'date' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema float type
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Float < Base
|
12
|
+
# Return JSON Schema fragment as Ruby Hash
|
13
|
+
#
|
14
|
+
# @return [Hash]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def as_type
|
18
|
+
{ 'type' => 'number' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema integer type
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Integer < Base
|
12
|
+
# Return JSON Schema fragment as Ruby Hash
|
13
|
+
#
|
14
|
+
# @return [Hash]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def as_type
|
18
|
+
{ 'type' => 'integer' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema object type
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Object < Base
|
12
|
+
# Initialize object
|
13
|
+
#
|
14
|
+
# @param [String] name
|
15
|
+
# @param [
|
16
|
+
# Array<Shale::Schema::JSONGenerator::Base,
|
17
|
+
# Shale::Schema::JSONGenerator::Collection>
|
18
|
+
# ] properties
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
def initialize(name, properties)
|
22
|
+
super(name)
|
23
|
+
@properties = properties
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return JSON Schema fragment as Ruby Hash
|
27
|
+
#
|
28
|
+
# @return [Hash]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def as_type
|
32
|
+
{
|
33
|
+
'type' => 'object',
|
34
|
+
'properties' => @properties.to_h { |el| [el.name, el.as_json] },
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema reference
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Ref < Base
|
12
|
+
def initialize(name, type)
|
13
|
+
super(name)
|
14
|
+
@type = type.gsub('::', '_')
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return JSON Schema fragment as Ruby Hash
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def as_type
|
23
|
+
{ '$ref' => "#/$defs/#{@type}" }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shale
|
4
|
+
module Schema
|
5
|
+
class JSONGenerator
|
6
|
+
# Class representing JSON schema
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Schema
|
10
|
+
# JSON Schema dialect (aka version)
|
11
|
+
# @api private
|
12
|
+
DIALECT = 'https://json-schema.org/draft/2020-12/schema'
|
13
|
+
|
14
|
+
# Initialize Schema object
|
15
|
+
#
|
16
|
+
# @param [Array<Shale::Schema::JSONGenerator::Base>] types
|
17
|
+
# @param [String, nil] id
|
18
|
+
# @param [String, nil] description
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
def initialize(types, id: nil, title: nil, description: nil)
|
22
|
+
@types = types
|
23
|
+
@id = id
|
24
|
+
@title = title
|
25
|
+
@description = description
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return JSON schema as Ruby Hash
|
29
|
+
#
|
30
|
+
# @return [Hash]
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# Shale::Schema::JSONGenerator::Schema.new(types).as_json
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def as_json
|
37
|
+
schema = {
|
38
|
+
'$schema' => DIALECT,
|
39
|
+
'$id' => @id,
|
40
|
+
'title' => @title,
|
41
|
+
'description' => @description,
|
42
|
+
}
|
43
|
+
|
44
|
+
unless @types.empty?
|
45
|
+
root = @types.first
|
46
|
+
root.nullable = false
|
47
|
+
|
48
|
+
schema['$ref'] = "#/$defs/#{root.name}"
|
49
|
+
schema['$defs'] = @types
|
50
|
+
.sort { |a, b| a.name <=> b.name }
|
51
|
+
.to_h { |e| [e.name, e.as_json] }
|
52
|
+
end
|
53
|
+
|
54
|
+
schema.compact
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema string type
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class String < Base
|
12
|
+
# Return JSON Schema fragment as Ruby Hash
|
13
|
+
#
|
14
|
+
# @return [Hash]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def as_type
|
18
|
+
{ 'type' => 'string' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Schema
|
7
|
+
class JSONGenerator
|
8
|
+
# Class representing JSON Schema time type
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Time < Base
|
12
|
+
# Return JSON Schema fragment as Ruby Hash
|
13
|
+
#
|
14
|
+
# @return [Hash]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def as_type
|
18
|
+
{ 'type' => 'string', 'format' => 'date-time' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|