lutaml-model 0.3.5 → 0.3.7

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