shale 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +366 -8
  4. data/exe/shaleb +123 -0
  5. data/lib/shale/adapter/json.rb +7 -2
  6. data/lib/shale/adapter/nokogiri.rb +48 -12
  7. data/lib/shale/adapter/ox.rb +28 -4
  8. data/lib/shale/adapter/rexml.rb +56 -13
  9. data/lib/shale/attribute.rb +7 -1
  10. data/lib/shale/error.rb +12 -0
  11. data/lib/shale/mapper.rb +17 -15
  12. data/lib/shale/mapping/descriptor/dict.rb +57 -0
  13. data/lib/shale/mapping/descriptor/xml.rb +43 -0
  14. data/lib/shale/mapping/descriptor/xml_namespace.rb +37 -0
  15. data/lib/shale/mapping/{key_value.rb → dict.rb} +8 -6
  16. data/lib/shale/mapping/validator.rb +51 -0
  17. data/lib/shale/mapping/xml.rb +86 -15
  18. data/lib/shale/schema/json_compiler/boolean.rb +21 -0
  19. data/lib/shale/schema/json_compiler/date.rb +21 -0
  20. data/lib/shale/schema/json_compiler/float.rb +21 -0
  21. data/lib/shale/schema/json_compiler/integer.rb +21 -0
  22. data/lib/shale/schema/json_compiler/object.rb +85 -0
  23. data/lib/shale/schema/json_compiler/property.rb +70 -0
  24. data/lib/shale/schema/json_compiler/string.rb +21 -0
  25. data/lib/shale/schema/json_compiler/time.rb +21 -0
  26. data/lib/shale/schema/json_compiler/utils.rb +52 -0
  27. data/lib/shale/schema/json_compiler/value.rb +13 -0
  28. data/lib/shale/schema/json_compiler.rb +333 -0
  29. data/lib/shale/schema/json_generator/base.rb +41 -0
  30. data/lib/shale/schema/json_generator/boolean.rb +23 -0
  31. data/lib/shale/schema/json_generator/collection.rb +39 -0
  32. data/lib/shale/schema/json_generator/date.rb +23 -0
  33. data/lib/shale/schema/json_generator/float.rb +23 -0
  34. data/lib/shale/schema/json_generator/integer.rb +23 -0
  35. data/lib/shale/schema/json_generator/object.rb +40 -0
  36. data/lib/shale/schema/json_generator/ref.rb +28 -0
  37. data/lib/shale/schema/json_generator/schema.rb +59 -0
  38. data/lib/shale/schema/json_generator/string.rb +23 -0
  39. data/lib/shale/schema/json_generator/time.rb +23 -0
  40. data/lib/shale/schema/json_generator/value.rb +23 -0
  41. data/lib/shale/schema/json_generator.rb +165 -0
  42. data/lib/shale/schema/xml_generator/attribute.rb +41 -0
  43. data/lib/shale/schema/xml_generator/complex_type.rb +70 -0
  44. data/lib/shale/schema/xml_generator/element.rb +55 -0
  45. data/lib/shale/schema/xml_generator/import.rb +46 -0
  46. data/lib/shale/schema/xml_generator/ref_attribute.rb +37 -0
  47. data/lib/shale/schema/xml_generator/ref_element.rb +39 -0
  48. data/lib/shale/schema/xml_generator/schema.rb +121 -0
  49. data/lib/shale/schema/xml_generator/typed_attribute.rb +46 -0
  50. data/lib/shale/schema/xml_generator/typed_element.rb +46 -0
  51. data/lib/shale/schema/xml_generator.rb +315 -0
  52. data/lib/shale/schema.rb +70 -0
  53. data/lib/shale/type/boolean.rb +2 -2
  54. data/lib/shale/type/composite.rb +78 -72
  55. data/lib/shale/type/date.rb +35 -2
  56. data/lib/shale/type/float.rb +2 -2
  57. data/lib/shale/type/integer.rb +2 -2
  58. data/lib/shale/type/string.rb +2 -2
  59. data/lib/shale/type/time.rb +35 -2
  60. data/lib/shale/type/{base.rb → value.rb} +18 -7
  61. data/lib/shale/utils.rb +18 -2
  62. data/lib/shale/version.rb +1 -1
  63. data/lib/shale.rb +10 -10
  64. data/shale.gemspec +6 -2
  65. metadata +53 -13
  66. data/lib/shale/mapping/base.rb +0 -32
@@ -0,0 +1,315 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../shale'
4
+ require_relative 'xml_generator/complex_type'
5
+ require_relative 'xml_generator/import'
6
+ require_relative 'xml_generator/ref_attribute'
7
+ require_relative 'xml_generator/ref_element'
8
+ require_relative 'xml_generator/schema'
9
+ require_relative 'xml_generator/typed_attribute'
10
+ require_relative 'xml_generator/typed_element'
11
+
12
+ module Shale
13
+ module Schema
14
+ # Class for generating XML schema
15
+ #
16
+ # @api public
17
+ class XMLGenerator
18
+ # XML Schema default name
19
+ # @api private
20
+ DEFAULT_SCHEMA_NAME = 'schema'
21
+
22
+ @xml_types = Hash.new('string')
23
+
24
+ # Register Shale to XML type mapping
25
+ #
26
+ # @param [Shale::Type::Value] shale_type
27
+ # @param [String] xml_type
28
+ #
29
+ # @example
30
+ # Shale::Schema::XMLGenerator.register_xml_type(Shale::Type::String, 'myType')
31
+ #
32
+ # @api public
33
+ def self.register_xml_type(shale_type, xml_type)
34
+ @xml_types[shale_type] = xml_type
35
+ end
36
+
37
+ # Return XML type for given Shale type
38
+ #
39
+ # @param [Shale::Type::Value] shale_type
40
+ #
41
+ # @return [String]
42
+ #
43
+ # @example
44
+ # Shale::Schema::XMLGenerator.get_xml_type(Shale::Type::String)
45
+ # # => 'string'
46
+ #
47
+ # @api private
48
+ def self.get_xml_type(shale_type)
49
+ @xml_types[shale_type]
50
+ end
51
+
52
+ register_xml_type(Shale::Type::Boolean, 'boolean')
53
+ register_xml_type(Shale::Type::Date, 'date')
54
+ register_xml_type(Shale::Type::Float, 'decimal')
55
+ register_xml_type(Shale::Type::Integer, 'integer')
56
+ register_xml_type(Shale::Type::Time, 'dateTime')
57
+ register_xml_type(Shale::Type::Value, 'anyType')
58
+
59
+ # Generate XML Schema from Shale model and return
60
+ # it as a Shale::Schema::XMLGenerator::Schema array
61
+ #
62
+ # @param [Shale::Mapper] klass
63
+ # @param [String, nil] base_name
64
+ #
65
+ # @raise [NotAShaleMapperError] when attribute is not a Shale model
66
+ #
67
+ # @return [Array<Shale::Schema::XMLGenerator::Schema>]
68
+ #
69
+ # @example
70
+ # Shale::Schema::XMLGenerator.new.as_schemas(Person)
71
+ #
72
+ # @api public
73
+ def as_schemas(klass, base_name = nil)
74
+ unless mapper_type?(klass)
75
+ raise NotAShaleMapperError, "XML Shema can't be generated for '#{klass}' type"
76
+ end
77
+
78
+ schemas = Hash.new do |hash, key|
79
+ name = "#{base_name || DEFAULT_SCHEMA_NAME}#{hash.keys.length}.xsd"
80
+ hash[key] = Schema.new(name, key)
81
+ end
82
+
83
+ default_namespace = klass.xml_mapping.default_namespace
84
+
85
+ root_element = TypedElement.new(
86
+ name: klass.xml_mapping.unprefixed_root,
87
+ type: [default_namespace.prefix, schematize(klass.name)].compact.join(':'),
88
+ required: true
89
+ )
90
+
91
+ schemas[default_namespace.name].add_namespace(
92
+ default_namespace.prefix,
93
+ default_namespace.name
94
+ )
95
+ schemas[default_namespace.name].add_child(root_element)
96
+
97
+ composites = []
98
+ collect_composite_types(composites, klass, klass.xml_mapping.default_namespace.name)
99
+
100
+ composites.each do |composite|
101
+ type = composite[:type]
102
+ namespace = composite[:namespace]
103
+ children = []
104
+
105
+ type.xml_mapping.elements.values.each do |mapping|
106
+ attribute = type.attributes[mapping.attribute]
107
+ next unless attribute
108
+
109
+ xml_type = get_xml_type_for_attribute(attribute.type, mapping.namespace)
110
+ default = get_default_for_attribute(attribute)
111
+
112
+ if namespace == mapping.namespace.name
113
+ children << TypedElement.new(
114
+ name: mapping.name,
115
+ type: xml_type,
116
+ collection: attribute.collection?,
117
+ default: default
118
+ )
119
+ else
120
+ target_namespace = mapping.namespace
121
+ target_schema = schemas[target_namespace.name]
122
+
123
+ children << RefElement.new(
124
+ ref: mapping.prefixed_name,
125
+ collection: attribute.collection?,
126
+ default: default
127
+ )
128
+
129
+ target_element = TypedElement.new(
130
+ name: mapping.name,
131
+ type: xml_type,
132
+ required: true
133
+ )
134
+
135
+ import_element = Import.new(
136
+ target_namespace.name,
137
+ target_schema.name
138
+ )
139
+
140
+ target_schema.add_namespace(
141
+ target_namespace.prefix,
142
+ target_namespace.name
143
+ )
144
+ target_schema.add_child(target_element)
145
+
146
+ schemas[namespace].add_namespace(
147
+ target_namespace.prefix,
148
+ target_namespace.name
149
+ )
150
+ schemas[namespace].add_child(import_element)
151
+ end
152
+ end
153
+
154
+ type.xml_mapping.attributes.values.each do |mapping|
155
+ attribute = type.attributes[mapping.attribute]
156
+ next unless attribute
157
+
158
+ xml_type = get_xml_type_for_attribute(attribute.type, mapping.namespace)
159
+ default = get_default_for_attribute(attribute)
160
+
161
+ if namespace == mapping.namespace.name
162
+ children << TypedAttribute.new(
163
+ name: mapping.name,
164
+ type: xml_type,
165
+ default: default
166
+ )
167
+ else
168
+ target_namespace = mapping.namespace
169
+ target_schema = schemas[target_namespace.name]
170
+
171
+ children << RefAttribute.new(
172
+ ref: mapping.prefixed_name,
173
+ default: default
174
+ )
175
+
176
+ target_element = TypedAttribute.new(name: mapping.name, type: xml_type)
177
+
178
+ import_element = Import.new(
179
+ target_namespace.name,
180
+ target_schema.name
181
+ )
182
+
183
+ target_schema.add_namespace(
184
+ target_namespace.prefix,
185
+ target_namespace.name
186
+ )
187
+ target_schema.add_child(target_element)
188
+
189
+ schemas[namespace].add_namespace(
190
+ target_namespace.prefix,
191
+ target_namespace.name
192
+ )
193
+ schemas[namespace].add_child(import_element)
194
+ end
195
+ end
196
+
197
+ complex = ComplexType.new(
198
+ schematize(type.name),
199
+ children,
200
+ mixed: !type.xml_mapping.content.nil?
201
+ )
202
+
203
+ schemas[namespace].add_child(complex)
204
+ end
205
+
206
+ schemas.values
207
+ end
208
+
209
+ # Generate XML Schema from Shale model
210
+ #
211
+ # @param [Shale::Mapper] klass
212
+ # @param [String, nil] base_name
213
+ # @param [true, false] pretty
214
+ # @param [true, false] declaration
215
+ #
216
+ # @return [Hash<String, String>]
217
+ #
218
+ # @example
219
+ # Shale::Schema::XMLGenerator.new.to_schemas(Person)
220
+ #
221
+ # @api public
222
+ def to_schemas(klass, base_name = nil, pretty: false, declaration: false)
223
+ schemas = as_schemas(klass, base_name)
224
+
225
+ options = [
226
+ pretty ? :pretty : nil,
227
+ declaration ? :declaration : nil,
228
+ ]
229
+
230
+ schemas.to_h do |schema|
231
+ [schema.name, Shale.xml_adapter.dump(schema.as_xml, *options)]
232
+ end
233
+ end
234
+
235
+ private
236
+
237
+ # Check it type inherits from Shale::Mapper
238
+ #
239
+ # @param [Class] type
240
+ #
241
+ # @return [true, false]
242
+ #
243
+ # @api private
244
+ def mapper_type?(type)
245
+ type < Shale::Mapper
246
+ end
247
+
248
+ # Collect recursively Shale::Mapper types
249
+ #
250
+ # @param [Array<Shale::Mapper>] types
251
+ # @param [Shale::Mapper] type
252
+ # @param [String, nil] namespace
253
+ #
254
+ # @api private
255
+ def collect_composite_types(types, type, namespace)
256
+ types << { type: type, namespace: namespace }
257
+
258
+ type.xml_mapping.elements.values.each do |mapping|
259
+ attribute = type.attributes[mapping.attribute]
260
+ next unless attribute
261
+
262
+ is_mapper = mapper_type?(attribute.type)
263
+ is_included = types.include?({ type: attribute.type, namespace: namespace })
264
+
265
+ if is_mapper && !is_included
266
+ collect_composite_types(types, attribute.type, mapping.namespace.name)
267
+ end
268
+ end
269
+ end
270
+
271
+ # Convert Ruby class name to XML Schema name
272
+ #
273
+ # @param [String] word
274
+ #
275
+ # @return [String]
276
+ #
277
+ # @api private
278
+ def schematize(word)
279
+ word.gsub('::', '_')
280
+ end
281
+
282
+ # Return XML Schema type for given Shale::Type::Value
283
+ #
284
+ # @param [Shale::Type::Value] type
285
+ # @param [String] namespace
286
+ #
287
+ # @return [String]
288
+ #
289
+ # @api private
290
+ def get_xml_type_for_attribute(type, namespace)
291
+ if mapper_type?(type)
292
+ [namespace.prefix, schematize(type.name)].compact.join(':')
293
+ else
294
+ "xs:#{self.class.get_xml_type(type)}"
295
+ end
296
+ end
297
+
298
+ # Return default value for given Shale::Attribute
299
+ #
300
+ # @param [Shale::Attribute] attribute
301
+ #
302
+ # @return [String, nil]
303
+ #
304
+ # @api private
305
+ def get_default_for_attribute(attribute)
306
+ return unless attribute.default
307
+ return if attribute.collection?
308
+ return if mapper_type?(attribute.type)
309
+
310
+ value = attribute.type.cast(attribute.default.call)
311
+ attribute.type.as_xml_value(value)
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'schema/json_generator'
4
+ require_relative 'schema/json_compiler'
5
+ require_relative 'schema/xml_generator'
6
+
7
+ module Shale
8
+ # Module for handling JSON and XML schema
9
+ #
10
+ # @api private
11
+ module Schema
12
+ # Generate JSON Schema from Shale model
13
+ #
14
+ # @param [Shale::Mapper] klass
15
+ # @param [String, nil] id
16
+ # @param [String, nil] description
17
+ # @param [true, false] pretty
18
+ #
19
+ # @return [String]
20
+ #
21
+ # @example
22
+ # Shale::Schema.to_json(Person, id: 'My ID', description: 'My description', pretty: true)
23
+ # # => JSON schema
24
+ #
25
+ # @api public
26
+ def self.to_json(klass, id: nil, title: nil, description: nil, pretty: false)
27
+ JSONGenerator.new.to_schema(
28
+ klass,
29
+ id: id,
30
+ title: title,
31
+ description: description,
32
+ pretty: pretty
33
+ )
34
+ end
35
+
36
+ # Generate Shale model from JSON Schema
37
+ #
38
+ # @param [Array<String>] schemas
39
+ # @param [String, nil] root_name
40
+ #
41
+ # @return [Array<String>]
42
+ #
43
+ # @example
44
+ # Shale::Schema.from_json([json_schema1, json_schema2], root_name: 'foobar')
45
+ # # => [model1, model2, model3]
46
+ #
47
+ # @api public
48
+ def self.from_json(schemas, root_name: nil)
49
+ JSONCompiler.new.to_models(schemas, root_name: root_name)
50
+ end
51
+
52
+ # Generate XML Schema from Shale model
53
+ #
54
+ # @param [Shale::Mapper] klass
55
+ # @param [String, nil] base_name
56
+ # @param [true, false] pretty
57
+ # @param [true, false] declaration
58
+ #
59
+ # @return [Hash<String, String>]
60
+ #
61
+ # @example
62
+ # Shale::Schema.to_xml(Person, pretty: true, declaration: true)
63
+ # # => { 'schema0.xsd' => <JSON schema>, 'schema1.xsd' => <JSON schema> }
64
+ #
65
+ # @api public
66
+ def self.to_xml(klass, base_name = nil, pretty: false, declaration: false)
67
+ XMLGenerator.new.to_schemas(klass, base_name, pretty: pretty, declaration: declaration)
68
+ end
69
+ end
70
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
3
+ require_relative 'value'
4
4
 
5
5
  module Shale
6
6
  module Type
7
7
  # Cast value to Boolean
8
8
  #
9
9
  # @api public
10
- class Boolean < Base
10
+ class Boolean < Value
11
11
  FALSE_VALUES = [
12
12
  false,
13
13
  0,