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.
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,