lutaml-model 0.6.7 → 0.7.2

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos-todo.json +7 -0
  3. data/.github/workflows/dependent-repos.json +17 -9
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +51 -65
  6. data/Gemfile +4 -1
  7. data/README.adoc +5083 -2612
  8. data/RELEASE_NOTES.adoc +346 -0
  9. data/docs/custom_adapters.adoc +144 -0
  10. data/lib/lutaml/model/attribute.rb +101 -16
  11. data/lib/lutaml/model/choice.rb +7 -0
  12. data/lib/lutaml/model/comparable_model.rb +48 -9
  13. data/lib/lutaml/model/config.rb +48 -42
  14. data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
  15. data/lib/lutaml/model/error/polymorphic_error.rb +14 -0
  16. data/lib/lutaml/model/error.rb +1 -0
  17. data/lib/lutaml/model/format_registry.rb +41 -0
  18. data/lib/lutaml/model/hash/document.rb +11 -0
  19. data/lib/lutaml/model/hash/mapping.rb +19 -0
  20. data/lib/lutaml/model/hash/mapping_rule.rb +9 -0
  21. data/lib/lutaml/model/hash/standard_adapter.rb +17 -0
  22. data/lib/lutaml/model/hash/transform.rb +8 -0
  23. data/lib/lutaml/model/hash.rb +21 -0
  24. data/lib/lutaml/model/json/document.rb +11 -0
  25. data/lib/lutaml/model/json/mapping.rb +19 -0
  26. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  27. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  28. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  29. data/lib/lutaml/model/json/transform.rb +8 -0
  30. data/lib/lutaml/model/json.rb +21 -0
  31. data/lib/lutaml/model/key_value_document.rb +27 -0
  32. data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +64 -16
  33. data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
  34. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  35. data/lib/lutaml/model/mapping/mapping_rule.rb +300 -0
  36. data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
  37. data/lib/lutaml/model/sequence.rb +2 -2
  38. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  39. data/lib/lutaml/model/serialize.rb +219 -444
  40. data/lib/lutaml/model/services/logger.rb +54 -0
  41. data/lib/lutaml/model/services/transformer.rb +48 -0
  42. data/lib/lutaml/model/services.rb +2 -0
  43. data/lib/lutaml/model/toml/document.rb +11 -0
  44. data/lib/lutaml/model/toml/mapping.rb +27 -0
  45. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  47. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  48. data/lib/lutaml/model/toml/transform.rb +8 -0
  49. data/lib/lutaml/model/toml.rb +30 -0
  50. data/lib/lutaml/model/transform/key_value_transform.rb +291 -0
  51. data/lib/lutaml/model/transform/xml_transform.rb +239 -0
  52. data/lib/lutaml/model/transform.rb +78 -0
  53. data/lib/lutaml/model/type/date.rb +1 -1
  54. data/lib/lutaml/model/type/date_time.rb +2 -2
  55. data/lib/lutaml/model/type/hash.rb +1 -1
  56. data/lib/lutaml/model/type/time.rb +2 -2
  57. data/lib/lutaml/model/type/time_without_date.rb +2 -2
  58. data/lib/lutaml/model/type/value.rb +6 -9
  59. data/lib/lutaml/model/uninitialized_class.rb +64 -0
  60. data/lib/lutaml/model/utils.rb +44 -0
  61. data/lib/lutaml/model/validation.rb +1 -0
  62. data/lib/lutaml/model/version.rb +1 -1
  63. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  64. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  65. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  66. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +41 -21
  67. data/lib/lutaml/model/xml/element.rb +32 -0
  68. data/lib/lutaml/model/xml/mapping.rb +410 -0
  69. data/lib/lutaml/model/xml/mapping_rule.rb +141 -0
  70. data/lib/lutaml/model/xml/nokogiri_adapter.rb +232 -0
  71. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  72. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +3 -1
  73. data/lib/lutaml/model/xml/oga_adapter.rb +171 -0
  74. data/lib/lutaml/model/xml/ox_adapter.rb +215 -0
  75. data/lib/lutaml/model/xml/transform.rb +8 -0
  76. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  77. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +23 -10
  78. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  79. data/lib/lutaml/model/xml.rb +31 -0
  80. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  81. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  82. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  83. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  84. data/lib/lutaml/model/yaml/document.rb +10 -0
  85. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  86. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  87. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  88. data/lib/lutaml/model/yaml/transform.rb +8 -0
  89. data/lib/lutaml/model/yaml.rb +21 -0
  90. data/lib/lutaml/model.rb +40 -4
  91. data/lutaml-model.gemspec +0 -4
  92. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +7 -7
  93. data/spec/fixtures/person.rb +5 -5
  94. data/spec/lutaml/model/attribute_spec.rb +37 -1
  95. data/spec/lutaml/model/cdata_spec.rb +9 -9
  96. data/spec/lutaml/model/collection_spec.rb +50 -2
  97. data/spec/lutaml/model/comparable_model_spec.rb +92 -27
  98. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  99. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  100. data/spec/lutaml/model/defaults_spec.rb +1 -1
  101. data/spec/lutaml/model/enum_spec.rb +1 -1
  102. data/spec/lutaml/model/group_spec.rb +333 -20
  103. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  104. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  105. data/spec/lutaml/model/key_value_mapping_spec.rb +65 -3
  106. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  107. data/spec/lutaml/model/multiple_mapping_spec.rb +5 -5
  108. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  109. data/spec/lutaml/model/polymorphic_spec.rb +526 -0
  110. data/spec/lutaml/model/render_empty_spec.rb +194 -0
  111. data/spec/lutaml/model/render_nil_spec.rb +206 -22
  112. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  113. data/spec/lutaml/model/schema/xml_compiler_spec.rb +6 -6
  114. data/spec/lutaml/model/serializable_spec.rb +179 -103
  115. data/spec/lutaml/model/simple_model_spec.rb +9 -9
  116. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  117. data/spec/lutaml/model/toml_spec.rb +51 -0
  118. data/spec/lutaml/model/transformation_spec.rb +72 -15
  119. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  120. data/spec/lutaml/model/value_map_spec.rb +240 -0
  121. data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
  122. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  123. data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
  124. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  125. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  126. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  127. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  128. data/spec/lutaml/model/xml_adapter_spec.rb +6 -6
  129. data/spec/lutaml/model/xml_mapping_rule_spec.rb +105 -5
  130. data/spec/lutaml/model/xml_mapping_spec.rb +70 -16
  131. data/spec/lutaml/model/xml_spec.rb +63 -0
  132. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  133. data/spec/sample_model_spec.rb +3 -3
  134. data/spec/spec_helper.rb +3 -3
  135. metadata +76 -59
  136. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  137. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  138. data/lib/lutaml/model/loggable.rb +0 -15
  139. data/lib/lutaml/model/mapping_rule.rb +0 -109
  140. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  141. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  142. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  143. data/lib/lutaml/model/toml_adapter.rb +0 -6
  144. data/lib/lutaml/model/xml_mapping.rb +0 -307
  145. data/lib/lutaml/model/xml_mapping_rule.rb +0 -122
  146. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  147. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  148. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -1,214 +1,17 @@
1
- require "ox"
2
- require_relative "xml_document"
3
- require_relative "builder/ox"
1
+ # require "ox"
2
+ # require_relative "xml_document"
3
+ # require_relative "builder/ox"
4
+
5
+ require_relative "../xml/ox_adapter"
4
6
 
5
7
  module Lutaml
6
8
  module Model
7
9
  module XmlAdapter
8
- class OxAdapter < XmlDocument
9
- def self.parse(xml, options = {})
10
- Ox.default_options = Ox.default_options.merge(encoding: encoding(xml, options))
11
-
12
- parsed = Ox.parse(xml)
13
- @root = OxElement.new(parsed)
14
- new(@root, Ox.default_options[:encoding])
15
- end
16
-
17
- def to_xml(options = {})
18
- builder_options = { version: options[:version] }
19
-
20
- builder_options[:encoding] = if options.key?(:encoding)
21
- options[:encoding] unless options[:encoding].nil?
22
- elsif options.key?(:parse_encoding)
23
- options[:parse_encoding]
24
- else
25
- "UTF-8"
26
- end
27
-
28
- builder = Builder::Ox.build(builder_options)
29
- builder.xml.instruct(:xml, encoding: builder_options[:encoding])
30
-
31
- if @root.is_a?(Lutaml::Model::XmlAdapter::OxElement)
32
- @root.build_xml(builder)
33
- elsif ordered?(@root, options)
34
- build_ordered_element(builder, @root, options)
35
- else
36
- mapper_class = options[:mapper_class] || @root.class
37
- options[:xml_attributes] = build_namespace_attributes(mapper_class)
38
- build_element(builder, @root, options)
39
- end
40
-
41
- xml_data = builder.xml.to_s
42
- stripped_data = xml_data.lines.drop(1).join
43
- options[:declaration] ? declaration(options) + stripped_data : stripped_data
44
- end
45
-
46
- private
47
-
48
- def build_ordered_element(builder, element, options = {})
49
- mapper_class = options[:mapper_class] || element.class
50
- xml_mapping = mapper_class.mappings_for(:xml)
51
- return xml unless xml_mapping
52
-
53
- attributes = build_attributes(element, xml_mapping).compact
54
-
55
- tag_name = options[:tag_name] || xml_mapping.root_element
56
- builder.create_and_add_element(tag_name,
57
- attributes: attributes) do |el|
58
- index_hash = {}
59
- content = []
60
-
61
- element.element_order.each do |object|
62
- index_hash[object.name] ||= -1
63
- curr_index = index_hash[object.name] += 1
64
-
65
- element_rule = xml_mapping.find_by_name(object.name)
66
- next if element_rule.nil?
67
-
68
- attribute_def = attribute_definition_for(element, element_rule,
69
- mapper_class: mapper_class)
70
- value = attribute_value_for(element, element_rule)
71
-
72
- next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
73
-
74
- if element_rule == xml_mapping.content_mapping
75
- text = element.send(xml_mapping.content_mapping.to)
76
- text = text[curr_index] if text.is_a?(Array)
77
-
78
- next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
79
-
80
- content << text
81
- elsif !value.nil? || element_rule.render_nil?
82
- value = value[curr_index] if attribute_def.collection?
83
-
84
- add_to_xml(
85
- el,
86
- element,
87
- nil,
88
- value,
89
- options.merge(
90
- attribute: attribute_def,
91
- rule: element_rule,
92
- ),
93
- )
94
- end
95
- end
96
-
97
- el.add_text(el, content.join)
98
- end
99
- end
100
- end
101
-
102
- class OxElement < XmlElement
103
- def initialize(node, root_node: nil)
104
- case node
105
- when String
106
- super("text", {}, [], node, parent_document: root_node)
107
- when Ox::Comment
108
- super("comment", {}, [], node.value, parent_document: root_node)
109
- when Ox::CData
110
- super("#cdata-section", {}, [], node.value, parent_document: root_node)
111
- else
112
- namespace_attributes(node.attributes).each do |(name, value)|
113
- if root_node
114
- root_node.add_namespace(XmlNamespace.new(value, name))
115
- else
116
- add_namespace(XmlNamespace.new(value, name))
117
- end
118
- end
119
-
120
- attributes = node.attributes.each_with_object({}) do |(name, value), hash|
121
- next if attribute_is_namespace?(name)
122
-
123
- namespace_prefix = name.to_s.split(":").first
124
- if (n = name.to_s.split(":")).length > 1
125
- namespace = (root_node || self).namespaces[namespace_prefix]&.uri
126
- namespace ||= XML_NAMESPACE_URI
127
- prefix = n.first
128
- end
129
-
130
- hash[name.to_s] = XmlAttribute.new(
131
- name.to_s,
132
- value,
133
- namespace: namespace,
134
- namespace_prefix: prefix,
135
- )
136
- end
137
-
138
- super(
139
- node,
140
- attributes,
141
- parse_children(node, root_node: root_node || self),
142
- node.text,
143
- parent_document: root_node,
144
- )
145
- end
146
- end
147
-
148
- def to_xml
149
- return text if text?
150
-
151
- build_xml.xml.to_s
152
- end
153
-
154
- def inner_xml
155
- # Ox builder by default, adds a newline at the end, so `chomp` is used
156
- children.map { |child| child.to_xml.chomp }.join
157
- end
158
-
159
- def build_xml(builder = nil)
160
- builder ||= Builder::Ox.build
161
- attrs = build_attributes(self)
162
-
163
- if text?
164
- builder.add_text(builder, text)
165
- else
166
- builder.create_and_add_element(name, attributes: attrs) do |el|
167
- children.each { |child| child.build_xml(el) }
168
- end
169
- end
170
-
171
- builder
172
- end
173
-
174
- def namespace_attributes(attributes)
175
- attributes.select { |attr| attribute_is_namespace?(attr) }
176
- end
177
-
178
- def text?
179
- # false
180
- children.empty? && text&.length&.positive?
181
- end
182
-
183
- def build_attributes(node)
184
- attrs = node.attributes.transform_values(&:value)
185
-
186
- node.own_namespaces.each_value do |namespace|
187
- attrs[namespace.attr_name] = namespace.uri
188
- end
189
-
190
- attrs
191
- end
192
-
193
- def nodes
194
- children
195
- end
196
-
197
- def cdata
198
- super || cdata_children.first&.text
199
- end
200
-
201
- def text
202
- super || cdata
203
- end
204
-
205
- private
206
-
207
- def parse_children(node, root_node: nil)
208
- node.nodes.map do |child|
209
- OxElement.new(child, root_node: root_node)
210
- end
211
- end
10
+ class OxAdapter < ::Lutaml::Model::Xml::OxAdapter
11
+ Logger.warn_future_deprication(
12
+ old: "Lutaml::Model::XmlAdapter::OxAdapter",
13
+ replacement: "Lutaml::Model::Xml::OxAdapter",
14
+ )
212
15
  end
213
16
  end
214
17
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Yaml
6
+ # Base class for YAML documents
7
+ class Document < Lutaml::Model::KeyValueDocument; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "../mapping/key_value_mapping"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Yaml
6
+ class Mapping < Lutaml::Model::KeyValueMapping
7
+ def initialize
8
+ super(:yaml)
9
+ end
10
+
11
+ def deep_dup
12
+ self.class.new.tap do |new_mapping|
13
+ new_mapping.instance_variable_set(:@mappings, duplicate_mappings)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "../mapping/key_value_mapping_rule"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Yaml
6
+ class MappingRule < Lutaml::Model::KeyValueMappingRule; end
7
+ end
8
+ end
9
+ end
@@ -1,10 +1,11 @@
1
1
  require "yaml"
2
- require_relative "yaml_document"
2
+ require_relative "document"
3
3
 
4
4
  module Lutaml
5
5
  module Model
6
- module YamlAdapter
7
- class StandardYamlAdapter < YamlDocument
6
+ module Yaml
7
+ class StandardAdapter < Document
8
+ FORMAT_SYMBOL = :yaml
8
9
  PERMITTED_CLASSES_BASE = [Date, Time, DateTime, Symbol, Hash,
9
10
  Array].freeze
10
11
 
@@ -0,0 +1,8 @@
1
+ module Lutaml
2
+ module Model
3
+ module Yaml
4
+ class Transform < Lutaml::Model::KeyValueTransform
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Yaml
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative "yaml/standard_adapter"
11
+ require_relative "yaml/document"
12
+ require_relative "yaml/mapping"
13
+ require_relative "yaml/mapping_rule"
14
+ require_relative "yaml/transform"
15
+
16
+ Lutaml::Model::FormatRegistry.register(
17
+ :yaml,
18
+ mapping_class: Lutaml::Model::Yaml::Mapping,
19
+ adapter_class: Lutaml::Model::Yaml::StandardAdapter,
20
+ transformer: Lutaml::Model::Yaml::Transform,
21
+ )
data/lib/lutaml/model.rb CHANGED
@@ -1,17 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "moxml"
4
+ require_relative "model/uninitialized_class"
5
+ require_relative "model/services"
4
6
  require_relative "model/version"
5
- require_relative "model/loggable"
6
7
  require_relative "model/type"
7
8
  require_relative "model/utils"
8
9
  require_relative "model/serializable"
9
- require_relative "model/json_adapter/standard_json_adapter"
10
- require_relative "model/yaml_adapter/standard_yaml_adapter"
11
10
  require_relative "model/xml_adapter"
12
- require_relative "model/toml_adapter"
13
11
  require_relative "model/error"
14
12
  require_relative "model/constants"
13
+ require_relative "model/config"
14
+ require_relative "model/format_registry"
15
+
16
+ require_relative "model/key_value_document"
17
+ require_relative "model/yaml"
18
+ require_relative "model/json"
19
+ require_relative "model/toml"
20
+ require_relative "model/hash"
21
+ require_relative "model/xml"
15
22
 
16
23
  module Lutaml
17
24
  module Model
@@ -23,5 +30,34 @@ module Lutaml
23
30
 
24
31
  class BaseModel < Serializable
25
32
  end
33
+
34
+ # Register default adapters
35
+ # Lutaml::Model::FormatRegistry.register(
36
+ # :json,
37
+ # mapping_class: KeyValueMapping,
38
+ # adapter_class: JsonAdapter::StandardJsonAdapter,
39
+ # transformer: Lutaml::Model::KeyValueTransform,
40
+ # )
41
+
42
+ # Lutaml::Model::FormatRegistry.register(
43
+ # :yaml,
44
+ # mapping_class: KeyValueMapping,
45
+ # adapter_class: YamlAdapter::StandardYamlAdapter,
46
+ # transformer: Lutaml::Model::KeyValueTransform,
47
+ # )
48
+
49
+ # Lutaml::Model::FormatRegistry.register(
50
+ # :toml,
51
+ # mapping_class: KeyValueMapping,
52
+ # adapter_class: nil,
53
+ # transformer: Lutaml::Model::KeyValueTransform,
54
+ # )
55
+
56
+ # Lutaml::Model::FormatRegistry.register(
57
+ # :xml,
58
+ # mapping_class: XmlMapping,
59
+ # adapter_class: nil,
60
+ # transformer: Lutaml::Model::XmlTransform,
61
+ # )
26
62
  end
27
63
  end
data/lutaml-model.gemspec CHANGED
@@ -30,10 +30,6 @@ Gem::Specification.new do |spec|
30
30
  end
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
 
33
- # TODO: remove once https://github.com/Shopify/liquid/issues/1772 is fixed
34
- # needed for liquid with ruby 3.4
35
- spec.add_dependency "base64"
36
- spec.add_dependency "liquid", "~> 5"
37
33
  spec.add_dependency "moxml", ">= 0.1.2"
38
34
  spec.add_dependency "thor"
39
35
  spec.metadata["rubygems_mfa_required"] = "true"
@@ -1,11 +1,11 @@
1
1
  require "benchmark"
2
2
  require "benchmark/ips"
3
3
  require "lutaml/model"
4
- require "lutaml/model/xml_adapter/oga_adapter"
4
+ require "lutaml/model/xml/oga_adapter"
5
5
 
6
6
  RSpec.describe "LutaML Model Performance" do
7
7
  after do
8
- Lutaml::Model::Config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
8
+ Lutaml::Model::Config.xml_adapter = Lutaml::Model::Xml::NokogiriAdapter
9
9
  end
10
10
 
11
11
  let(:large_xml) do
@@ -48,12 +48,12 @@ RSpec.describe "LutaML Model Performance" do
48
48
  end
49
49
 
50
50
  x.report("Ox Adapter") do
51
- Lutaml::Model::Config.xml_adapter = Lutaml::Model::XmlAdapter::OxAdapter
51
+ Lutaml::Model::Config.xml_adapter = Lutaml::Model::Xml::OxAdapter
52
52
  Deserializer.from_xml(large_xml)
53
53
  end
54
54
 
55
55
  x.report("Oga Adapter") do
56
- Lutaml::Model::Config.xml_adapter = Lutaml::Model::XmlAdapter::OgaAdapter
56
+ Lutaml::Model::Config.xml_adapter = Lutaml::Model::Xml::OgaAdapter
57
57
  Deserializer.from_xml(large_xml)
58
58
  end
59
59
 
@@ -61,9 +61,9 @@ RSpec.describe "LutaML Model Performance" do
61
61
  end
62
62
 
63
63
  thresholds = {
64
- "Nokogiri Adapter" => 4,
65
- "Ox Adapter" => 10,
66
- "Oga Adapter" => 4,
64
+ "Nokogiri Adapter" => 3,
65
+ "Ox Adapter" => 7,
66
+ "Oga Adapter" => 3,
67
67
  }
68
68
 
69
69
  report.entries.each do |entry|
@@ -17,11 +17,11 @@ class Person < Lutaml::Model::Serializable
17
17
  map_element "FirstName",
18
18
  to: :first_name,
19
19
  namespace: "http://example.com/nsp1",
20
- prefix: "nsp1"
20
+ prefix: "nsp1", render_empty: :omit
21
21
  map_element "LastName",
22
22
  to: :last_name,
23
23
  namespace: "http://example.com/nsp1",
24
- prefix: "nsp1"
24
+ prefix: "nsp1", render_empty: :as_blank
25
25
  map_element "Age", to: :age
26
26
  map_element "Height", to: :height
27
27
  map_element "Birthdate", to: :birthdate
@@ -31,9 +31,9 @@ class Person < Lutaml::Model::Serializable
31
31
  end
32
32
 
33
33
  json do
34
- map "firstName", to: :first_name
35
- map "lastName", to: :last_name
36
- map "age", to: :age
34
+ map "firstName", to: :first_name, render_empty: :omit
35
+ map "lastName", to: :last_name, render_empty: :as_empty
36
+ map "age", to: :age, render_empty: :as_nil
37
37
  map "height", to: :height
38
38
  map "birthdate", to: :birthdate
39
39
  map "lastLogin", to: :last_login
@@ -136,7 +136,9 @@ RSpec.describe Lutaml::Model::Attribute do
136
136
  context "when default is not set" do
137
137
  let(:attribute) { described_class.new("name", :string) }
138
138
 
139
- it { expect(attribute.default).to be_nil }
139
+ it "returns uninitialized" do
140
+ expect(attribute.default).to be(Lutaml::Model::UninitializedClass.instance)
141
+ end
140
142
  end
141
143
 
142
144
  context "when default is set as a proc" do
@@ -165,4 +167,38 @@ RSpec.describe Lutaml::Model::Attribute do
165
167
  end
166
168
  end
167
169
  end
170
+
171
+ describe "#deep_dup" do
172
+ let(:duplicate_attribute) { Lutaml::Model::Utils.deep_dup(attribute) }
173
+
174
+ context "when deep_dup method is not defined and instance is deep_duplicated" do
175
+ let(:attribute) { described_class.new("name", :string) }
176
+
177
+ before do
178
+ described_class.alias_method :orig_deep_dup, :deep_dup
179
+ described_class.undef_method :deep_dup
180
+ end
181
+
182
+ after do
183
+ described_class.alias_method :deep_dup, :orig_deep_dup
184
+ attribute.options.delete(:foo)
185
+ end
186
+
187
+ it "confirms that options values are linked of original and duplicate instances" do
188
+ duplicate_attribute
189
+ attribute.options[:foo] = "bar"
190
+ expect(duplicate_attribute.options).to include(:foo)
191
+ end
192
+ end
193
+
194
+ context "when deep_dup method is defined and instance is deep_duplicated" do
195
+ let(:attribute) { described_class.new("name", :string) }
196
+
197
+ it "confirms that options values are not linked of original and duplicate instances" do
198
+ duplicate_attribute
199
+ attribute.options[:foo] = "bar"
200
+ expect(duplicate_attribute.options).not_to include(:foo)
201
+ end
202
+ end
203
+ end
168
204
  end
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "lutaml/model"
3
- require "lutaml/model/xml_adapter/nokogiri_adapter"
4
- require "lutaml/model/xml_adapter/ox_adapter"
3
+ require "lutaml/model/xml/nokogiri_adapter"
4
+ require "lutaml/model/xml/ox_adapter"
5
5
 
6
6
  module CDATA
7
7
  class Beta < Lutaml::Model::Serializable
@@ -117,8 +117,8 @@ module CDATA
117
117
 
118
118
  def child_from_xml(model, value)
119
119
  model.child_mapper ||= CustomModelChild.new
120
- model.child_mapper.street = value.first.find_child_by_name("street").text
121
- model.child_mapper.city = value.first.find_child_by_name("city").text
120
+ model.child_mapper.street = value.first.find_child_by_name("street").cdata
121
+ model.child_mapper.city = value.first.find_child_by_name("city").cdata
122
122
  end
123
123
  end
124
124
 
@@ -350,7 +350,7 @@ RSpec.describe "CDATA" do
350
350
  instance = parent_mapper.from_xml(input_xml)
351
351
  result_xml = parent_mapper.to_xml(instance)
352
352
 
353
- expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
353
+ expected_output = adapter_class == Lutaml::Model::Xml::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
354
354
 
355
355
  expect(result_xml.strip).to eq(expected_output.strip)
356
356
  end
@@ -439,7 +439,7 @@ RSpec.describe "CDATA" do
439
439
 
440
440
  # due to the difference in capturing
441
441
  # newlines in ox and nokogiri adapters
442
- if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
442
+ if adapter_class == Lutaml::Model::Xml::OxAdapter
443
443
  expected_xml = expected_ox_xml
444
444
  end
445
445
 
@@ -496,7 +496,7 @@ RSpec.describe "CDATA" do
496
496
 
497
497
  # due to the difference in capturing
498
498
  # newlines in ox and nokogiri adapters
499
- if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
499
+ if adapter_class == Lutaml::Model::Xml::OxAdapter
500
500
  expected_xml = expected_ox_xml
501
501
  end
502
502
 
@@ -509,11 +509,11 @@ RSpec.describe "CDATA" do
509
509
  end
510
510
  end
511
511
 
512
- describe Lutaml::Model::XmlAdapter::NokogiriAdapter do
512
+ describe Lutaml::Model::Xml::NokogiriAdapter do
513
513
  it_behaves_like "cdata behavior", described_class
514
514
  end
515
515
 
516
- describe Lutaml::Model::XmlAdapter::OxAdapter do
516
+ describe Lutaml::Model::Xml::OxAdapter do
517
517
  it_behaves_like "cdata behavior", described_class
518
518
  end
519
519
  end
@@ -54,6 +54,16 @@ module CollectionTests
54
54
  doc.add_element(parent, "<city>#{model.city}</city>")
55
55
  end
56
56
  end
57
+
58
+ class ReturnNilTest < Lutaml::Model::Serializable
59
+ attribute :default_items, :string, collection: true
60
+ attribute :regular_items, :string, collection: true, initialize_empty: true
61
+
62
+ yaml do
63
+ map "default_items", to: :default_items
64
+ map "regular_items", to: :regular_items, render_default: true, render_empty: true
65
+ end
66
+ end
57
67
  end
58
68
 
59
69
  RSpec.describe CollectionTests do
@@ -95,8 +105,8 @@ RSpec.describe CollectionTests do
95
105
  it "initializes with default values" do
96
106
  default_model = CollectionTests::Kiln.new
97
107
  expect(default_model.brand).to be_nil
98
- expect(default_model.pots).to eq([])
99
- expect(default_model.temperatures).to eq([])
108
+ expect(default_model.pots).to be_nil
109
+ expect(default_model.temperatures).to be_nil
100
110
  expect(default_model.operators).to eq(["Default Operator"])
101
111
  expect(default_model.sensors).to eq(["Default Sensor"])
102
112
  end
@@ -280,4 +290,42 @@ RSpec.describe CollectionTests do
280
290
  end.not_to raise_error
281
291
  end
282
292
  end
293
+
294
+ context "when using initialize_empty option with collections" do
295
+ let(:parsed) { CollectionTests::ReturnNilTest.from_yaml(yaml) }
296
+ let(:model) { CollectionTests::ReturnNilTest.new }
297
+
298
+ let(:yaml) do
299
+ <<~YAML
300
+ ---
301
+ regular_items: ~
302
+ YAML
303
+ end
304
+
305
+ it "sets nil value when reading from YAML with nil value" do
306
+ expect(parsed.default_items).to be_nil
307
+ expect(parsed.regular_items).to be_nil
308
+ end
309
+
310
+ it "initializes with empty array when initialize_empty is true" do
311
+ expect(model.regular_items).to eq([])
312
+ end
313
+
314
+ it "preserves initialize_empty behavior when serializing and deserializing" do
315
+ expected_yaml = <<~YAML
316
+ ---
317
+ regular_items: []
318
+ YAML
319
+
320
+ expect(model.to_yaml).to eq(expected_yaml)
321
+ end
322
+
323
+ it "raises StandardError for initialize_empty without collection" do
324
+ expect do
325
+ Class.new(Lutaml::Model::Serializable) do
326
+ attribute :invalid_range, :string, initialize_empty: true
327
+ end
328
+ end.to raise_error(StandardError, /Invalid option `initialize_empty` given without `collection: true` option/)
329
+ end
330
+ end
283
331
  end