lutaml-model 0.3.5 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,8 +13,6 @@ require_relative "comparable_model"
13
13
  module Lutaml
14
14
  module Model
15
15
  module Serialize
16
- FORMATS = %i[xml json yaml toml].freeze
17
-
18
16
  include ComparableModel
19
17
 
20
18
  def self.included(base)
@@ -73,7 +71,7 @@ module Lutaml
73
71
  attr.options[:values].include?(value)
74
72
  end
75
73
 
76
- FORMATS.each do |format|
74
+ Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
77
75
  define_method(format) do |&block|
78
76
  klass = format == :xml ? XmlMapping : KeyValueMapping
79
77
  mappings[format] = klass.new
@@ -87,9 +85,8 @@ module Lutaml
87
85
  define_method(:"from_#{format}") do |data|
88
86
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
89
87
  doc = adapter.parse(data)
90
- mapped_attrs = apply_mappings(doc.to_h, format)
91
- # apply_content_mapping(doc, mapped_attrs) if format == :xml
92
- generate_model_object(self, mapped_attrs)
88
+
89
+ apply_mappings(doc.to_h, format)
93
90
  end
94
91
 
95
92
  define_method(:"to_#{format}") do |instance|
@@ -120,13 +117,14 @@ module Lutaml
120
117
  name = rule.to
121
118
  next if except&.include?(name) || (only && !only.include?(name))
122
119
 
123
- next handle_delegate(instance, rule, hash) if rule.delegate
120
+ next handle_delegate(instance, rule, hash, format) if rule.delegate
124
121
 
125
- value = if rule.custom_methods[:to]
126
- instance.send(rule.custom_methods[:to], instance, instance.send(name))
127
- else
128
- instance.send(name)
129
- end
122
+ if rule.custom_methods[:to]
123
+ next instance.send(rule.custom_methods[:to], instance,
124
+ hash)
125
+ end
126
+
127
+ value = instance.send(name)
130
128
 
131
129
  next if value.nil? && !rule.render_nil
132
130
 
@@ -134,68 +132,27 @@ module Lutaml
134
132
 
135
133
  hash[rule.from] = if rule.child_mappings
136
134
  generate_hash_from_child_mappings(value, rule.child_mappings)
137
- elsif value.is_a?(Array)
138
- value.map do |v|
139
- if attribute.type <= Serialize
140
- attribute.type.hash_representation(v, format, options)
141
- else
142
- attribute.type.serialize(v)
143
- end
144
- end
145
- elsif attribute.type <= Serialize
146
- attribute.type.hash_representation(value, format, options)
147
135
  else
148
- attribute.type.serialize(value)
136
+ attribute.serialize(value, format, options)
149
137
  end
150
138
  end
151
139
  end
152
140
 
153
- def handle_delegate(instance, rule, hash)
141
+ def handle_delegate(instance, rule, hash, format)
154
142
  name = rule.to
155
143
  value = instance.send(rule.delegate).send(name)
156
144
  return if value.nil? && !rule.render_nil
157
145
 
158
146
  attribute = instance.send(rule.delegate).class.attributes[name]
159
- hash[rule.from] = case value
160
- when Array
161
- value.map do |v|
162
- if v.is_a?(Serialize)
163
- hash_representation(v, format, options)
164
- else
165
- attribute.type.serialize(v)
166
- end
167
- end
168
- else
169
- if value.is_a?(Serialize)
170
- hash_representation(value, format, options)
171
- else
172
- attribute.type.serialize(value)
173
- end
174
- end
147
+ hash[rule.from] = attribute.serialize(value, format)
175
148
  end
176
149
 
177
150
  def mappings_for(format)
178
151
  mappings[format] || default_mappings(format)
179
152
  end
180
153
 
181
- def generate_model_object(type, mapped_attrs)
182
- return type.model.new(mapped_attrs) if self == model
183
-
184
- instance = type.model.new
185
-
186
- type.attributes.each do |name, attr|
187
- value = attr_value(mapped_attrs, name, attr)
188
-
189
- instance.send(:"#{name}=", ensure_utf8(value))
190
- end
191
-
192
- instance
193
- end
194
-
195
154
  def attr_value(attrs, name, attr_rule)
196
- value = if attrs.key?(name)
197
- attrs[name]
198
- elsif attrs.key?(name.to_sym)
155
+ value = if attrs.key?(name.to_sym)
199
156
  attrs[name.to_sym]
200
157
  elsif attrs.key?(name.to_s)
201
158
  attrs[name.to_s]
@@ -213,8 +170,6 @@ module Lutaml
213
170
  Lutaml::Model::Type.cast(v, attr_rule.type)
214
171
  end
215
172
  end
216
- elsif value.is_a?(Hash) && attr_rule.type != Lutaml::Model::Type::Hash
217
- generate_model_object(attr_rule.type, value)
218
173
  else
219
174
  # TODO: This code is problematic because Type.cast does not know
220
175
  # about all the types.
@@ -281,11 +236,12 @@ module Lutaml
281
236
  hash
282
237
  end
283
238
 
284
- def apply_mappings(doc, format)
285
- return apply_xml_mapping(doc) if format == :xml
239
+ def apply_mappings(doc, format, options = {})
240
+ instance = options[:instance] || model.new
241
+ return apply_xml_mapping(doc, instance, options) if format == :xml
286
242
 
287
243
  mappings = mappings_for(format).mappings
288
- mappings.each_with_object(Lutaml::Model::MappingHash.new) do |rule, hash|
244
+ mappings.each do |rule|
289
245
  attr = if rule.delegate
290
246
  attributes[rule.delegate].type.attributes[rule.to]
291
247
  else
@@ -294,50 +250,51 @@ module Lutaml
294
250
 
295
251
  raise "Attribute '#{rule.to}' not found in #{self}" unless attr
296
252
 
297
- value = if rule.custom_methods[:from]
298
- new.send(rule.custom_methods[:from], hash, doc)
299
- elsif doc.key?(rule.name) || doc.key?(rule.name.to_sym)
253
+ value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym)
300
254
  doc[rule.name] || doc[rule.name.to_sym]
301
255
  else
302
256
  attr.default
303
257
  end
304
258
 
305
- value = apply_child_mappings(value, rule.child_mappings)
306
-
307
- if attr.collection?
308
- value = (value || []).map do |v|
309
- attr.type <= Serialize ? attr.type.apply_mappings(v, format) : v
310
- end
311
- elsif value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
312
- value = attr.type.apply_mappings(value, format)
259
+ if rule.custom_methods[:from]
260
+ value = new.send(rule.custom_methods[:from], instance, value)
261
+ next
313
262
  end
314
263
 
264
+ value = apply_child_mappings(value, rule.child_mappings)
265
+ value = attr.cast(value, format)
266
+
315
267
  if rule.delegate
316
- hash[rule.delegate] ||= {}
317
- hash[rule.delegate][rule.to] = value
268
+ if instance.public_send(rule.delegate).nil?
269
+ instance.public_send(:"#{rule.delegate}=",
270
+ attributes[rule.delegate].type.new)
271
+ end
272
+ instance.public_send(rule.delegate).public_send(:"#{rule.to}=",
273
+ value)
318
274
  else
319
- hash[rule.to] = value
275
+ instance.public_send(:"#{rule.to}=", value)
320
276
  end
321
277
  end
278
+
279
+ instance
322
280
  end
323
281
 
324
- def apply_xml_mapping(doc, caller_class: nil, mixed_content: false)
325
- return unless doc
282
+ def apply_xml_mapping(doc, instance, options = {})
283
+ return instance unless doc
326
284
 
327
285
  mappings = mappings_for(:xml).mappings
328
286
 
329
287
  if doc.is_a?(Array)
330
288
  raise "May be `collection: true` is" \
331
- "missing for #{self} in #{caller_class}"
289
+ "missing for #{self} in #{options[:caller_class]}"
332
290
  end
333
291
 
334
- mapping_hash = Lutaml::Model::MappingHash.new
335
- mapping_hash.item_order = doc.item_order
336
- mapping_hash.ordered = mappings_for(:xml).mixed_content? || mixed_content
337
-
338
- mapping_from = []
292
+ if instance.respond_to?(:ordered=)
293
+ instance.element_order = doc.item_order
294
+ instance.ordered = mappings_for(:xml).mixed_content? || options[:mixed_content]
295
+ end
339
296
 
340
- mappings.each_with_object(mapping_hash) do |rule, hash|
297
+ mappings.each do |rule|
341
298
  attr = attributes[rule.to]
342
299
  raise "Attribute '#{rule.to}' not found in #{self}" unless attr
343
300
 
@@ -348,52 +305,38 @@ module Lutaml
348
305
  doc[rule.name.to_s] || doc[rule.name.to_sym]
349
306
  end
350
307
 
351
- if attr.collection?
352
- if value && !value.is_a?(Array)
353
- value = [value]
308
+ if value.is_a?(Array)
309
+ value = value.map do |v|
310
+ v.is_a?(Hash) && !(attr.type <= Serialize) ? v["text"] : v
354
311
  end
355
-
356
- value = (value || []).map do |v|
357
- if attr.type <= Serialize
358
- attr.type.apply_xml_mapping(v, caller_class: self, mixed_content: rule.mixed_content)
359
- elsif v.is_a?(Hash)
360
- v["text"]
361
- else
362
- v
363
- end
364
- end
365
- elsif attr.type <= Serialize
366
- value = attr.type.apply_xml_mapping(value, caller_class: self, mixed_content: rule.mixed_content)
367
- else
368
- if value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
369
- value = value["text"]
370
- end
371
-
372
- value = attr.type.cast(value) unless is_content_mapping
312
+ elsif !(attr.type <= Serialize) && value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
313
+ value = value["text"]
373
314
  end
374
315
 
375
- mapping_from << rule if rule.custom_methods[:from]
376
-
377
- hash[rule.to] = value
378
- end
379
-
380
- mapping_from.each do |rule|
381
- value = if rule.name.nil?
382
- mapping_hash[rule.to].join("\n").strip
383
- else
384
- mapping_hash[rule.to]
385
- end
316
+ unless is_content_mapping
317
+ value = attr.cast(
318
+ value,
319
+ :xml,
320
+ caller_class: self,
321
+ mixed_content: rule.mixed_content,
322
+ )
323
+ end
386
324
 
387
- mapping_hash[rule.to] = new.send(rule.custom_methods[:from], mapping_hash, value)
325
+ if rule.custom_methods[:from]
326
+ new.send(rule.custom_methods[:from], instance, value)
327
+ else
328
+ instance.public_send(:"#{rule.to}=", value)
329
+ end
388
330
  end
389
331
 
390
- mapping_hash
332
+ instance
391
333
  end
392
334
 
393
335
  def ensure_utf8(value)
394
336
  case value
395
337
  when String
396
- value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
338
+ value.encode("UTF-8", invalid: :replace, undef: :replace,
339
+ replace: "")
397
340
  when Array
398
341
  value.map { |v| ensure_utf8(v) }
399
342
  when Hash
@@ -408,7 +351,7 @@ module Lutaml
408
351
  end
409
352
  end
410
353
 
411
- attr_reader :element_order
354
+ attr_accessor :element_order
412
355
 
413
356
  def initialize(attrs = {})
414
357
  return unless self.class.attributes
@@ -431,22 +374,27 @@ module Lutaml
431
374
  @ordered
432
375
  end
433
376
 
377
+ def ordered=(ordered)
378
+ @ordered = ordered
379
+ end
380
+
434
381
  def key_exist?(hash, key)
435
- hash.key?(key) || hash.key?(key.to_sym) || hash.key?(key.to_s)
382
+ hash.key?(key.to_sym) || hash.key?(key.to_s)
436
383
  end
437
384
 
438
385
  def key_value(hash, key)
439
- hash[key] || hash[key.to_sym] || hash[key.to_s]
386
+ hash[key.to_sym] || hash[key.to_s]
440
387
  end
441
388
 
442
- FORMATS.each do |format|
389
+ Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
443
390
  define_method(:"to_#{format}") do |options = {}|
444
391
  validate
445
392
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
446
393
  representation = if format == :xml
447
394
  self
448
395
  else
449
- self.class.hash_representation(self, format, options)
396
+ self.class.hash_representation(self, format,
397
+ options)
450
398
  end
451
399
 
452
400
  adapter.new(representation).public_send(:"to_#{format}", options)
@@ -457,7 +405,8 @@ module Lutaml
457
405
  self.class.attributes.each do |name, attr|
458
406
  value = send(name)
459
407
  unless self.class.attr_value_valid?(name, value)
460
- raise Lutaml::Model::InvalidValueError.new(name, value, attr.options[:values])
408
+ raise Lutaml::Model::InvalidValueError.new(name, value,
409
+ attr.options[:values])
461
410
  end
462
411
  end
463
412
  end
@@ -1,8 +1,5 @@
1
1
  require "date"
2
2
  require "bigdecimal"
3
- require "securerandom"
4
- require "uri"
5
- require "ipaddr"
6
3
 
7
4
  module Lutaml
8
5
  module Model
@@ -20,12 +17,6 @@ module Lutaml
20
17
  Boolean
21
18
  Decimal
22
19
  Hash
23
- Uuid
24
- Symbol
25
- Binary
26
- Url
27
- IpAddress
28
- Json
29
20
  ).each do |t|
30
21
  class_eval <<~HEREDOC, __FILE__, __LINE__ + 1
31
22
  class #{t} # class Integer
@@ -72,18 +63,6 @@ module Lutaml
72
63
  BigDecimal(value.to_s)
73
64
  when "Hash"
74
65
  normalize_hash(Hash(value))
75
- when "Uuid"
76
- UUID_REGEX.match?(value) ? value : SecureRandom.uuid
77
- when "Symbol"
78
- value.to_sym
79
- when "Binary"
80
- value.force_encoding("BINARY")
81
- when "Url"
82
- URI.parse(value.to_s)
83
- when "IpAddress"
84
- IPAddr.new(value.to_s)
85
- when "Json"
86
- Json.cast(value)
87
66
  else
88
67
  value
89
68
  end
@@ -107,8 +86,6 @@ module Lutaml
107
86
  value.to_s("F")
108
87
  when "Hash"
109
88
  Hash(value)
110
- when "Json"
111
- value.to_json
112
89
  else
113
90
  value.to_s
114
91
  end
@@ -145,4 +122,3 @@ end
145
122
 
146
123
  require_relative "type/time_without_date"
147
124
  require_relative "type/date_time"
148
- require_relative "type/json"
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Utils
6
+ class << self
7
+ # Convert string to camel case
8
+ def camel_case(str)
9
+ return "" if str.nil? || str.empty?
10
+
11
+ str.split("/").map { |part| camelize_part(part) }.join("::")
12
+ end
13
+
14
+ # Convert string to class name
15
+ def classify(str)
16
+ str = str.to_s.delete(".")
17
+ str = str.sub(/^[a-z\d]*/) { |match| camel_case(match) || match }
18
+
19
+ str.gsub("::", "/").gsub(%r{(?:_|-|(/))([a-z\d]*)}i) do
20
+ word = Regexp.last_match(2)
21
+ substituted = camel_case(word) || word
22
+ Regexp.last_match(1) ? "::#{substituted}" : substituted
23
+ end
24
+ end
25
+
26
+ # Convert string to snake case
27
+ def snake_case(str)
28
+ str = str.to_s.tr(".", "_")
29
+ return str unless /[A-Z-]|::/.match?(str)
30
+
31
+ str.gsub("::", "/")
32
+ .gsub(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { "#{$1 || $2}_" }
33
+ .tr("-", "_")
34
+ .downcase
35
+ end
36
+
37
+ private
38
+
39
+ def camelize_part(part)
40
+ part.gsub(/(?:_|-|^)([a-z\d])/i) { $1.upcase }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.5"
5
+ VERSION = "0.3.7"
6
6
  end
7
7
  end
@@ -0,0 +1,73 @@
1
+ module Lutaml
2
+ module Model
3
+ module XmlAdapter
4
+ module Builder
5
+ class Nokogiri
6
+ def self.build(options = {})
7
+ if block_given?
8
+ ::Nokogiri::XML::Builder.new(options) do |xml|
9
+ yield(new(xml))
10
+ end
11
+ else
12
+ new(::Nokogiri::XML::Builder.new(options))
13
+ end
14
+ end
15
+
16
+ attr_reader :xml
17
+
18
+ def initialize(xml)
19
+ @xml = xml
20
+ end
21
+
22
+ def create_element(name, attributes = {})
23
+ xml.doc.create_element(name, attributes)
24
+ end
25
+
26
+ def add_element(element, child)
27
+ element.add_child(child)
28
+ end
29
+
30
+ def create_and_add_element(element_name, prefix: nil, attributes: {})
31
+ add_namespace_prefix(prefix) if prefix
32
+
33
+ if block_given?
34
+ public_send(element_name, attributes) do
35
+ yield(self)
36
+ end
37
+ else
38
+ public_send(element_name, attributes)
39
+ end
40
+ end
41
+
42
+ def add_text(element, text)
43
+ if element.is_a?(self.class)
44
+ element = element.xml.parent
45
+ end
46
+
47
+ add_element(element, ::Nokogiri::XML::Text.new(text.to_s, xml.doc))
48
+ end
49
+
50
+ def add_namespace_prefix(prefix)
51
+ xml[prefix] if prefix
52
+
53
+ self
54
+ end
55
+
56
+ def method_missing(method_name, *args)
57
+ if block_given?
58
+ xml.public_send(method_name, *args) do
59
+ yield(xml)
60
+ end
61
+ else
62
+ xml.public_send(method_name, *args)
63
+ end
64
+ end
65
+
66
+ def respond_to_missing?(method_name, include_private = false)
67
+ xml.respond_to?(method_name) || super
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,89 @@
1
+ module Lutaml
2
+ module Model
3
+ module XmlAdapter
4
+ module Builder
5
+ class Ox
6
+ def self.build(options = {})
7
+ if block_given?
8
+ ::Ox::Builder.new(options) do |xml|
9
+ yield(new(xml))
10
+ end
11
+ else
12
+ new(::Ox::Builder.new(options))
13
+ end
14
+ end
15
+
16
+ attr_reader :xml
17
+
18
+ def initialize(xml)
19
+ @xml = xml
20
+ end
21
+
22
+ def create_element(name, attributes = {})
23
+ if block_given?
24
+ xml.element(name, attributes) do |element|
25
+ yield(self.class.new(element))
26
+ end
27
+ else
28
+ xml.element(name, attributes)
29
+ end
30
+ end
31
+
32
+ def add_element(element, child)
33
+ element << child
34
+ end
35
+
36
+ def create_and_add_element(element_name, prefix: nil, attributes: {})
37
+ prefixed_name = if prefix
38
+ "#{prefix}:#{element_name}"
39
+ else
40
+ element_name
41
+ end
42
+
43
+ if block_given?
44
+ xml.element(prefixed_name, attributes) do |element|
45
+ yield(self.class.new(element))
46
+ end
47
+ else
48
+ xml.element(prefixed_name, attributes)
49
+ end
50
+ end
51
+
52
+ def <<(text)
53
+ xml.text(text)
54
+ end
55
+
56
+ def add_text(element, text)
57
+ element << text
58
+ end
59
+
60
+ # Add XML namespace to document
61
+ #
62
+ # Ox doesn't support XML namespaces so this method does nothing.
63
+ def add_namespace_prefix(_prefix)
64
+ # :noop:
65
+ self
66
+ end
67
+
68
+ def parent
69
+ xml
70
+ end
71
+
72
+ def method_missing(method_name, *args)
73
+ if block_given?
74
+ xml.public_send(method_name, *args) do
75
+ yield(xml)
76
+ end
77
+ else
78
+ xml.public_send(method_name, *args)
79
+ end
80
+ end
81
+
82
+ def respond_to_missing?(method_name, include_private = false)
83
+ xml.respond_to?(method_name) || super
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end