lutaml-model 0.8.8 → 0.8.10

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +12 -32
  3. data/lib/lutaml/key_value/transform.rb +5 -5
  4. data/lib/lutaml/key_value/transformation/collection_serializer.rb +25 -11
  5. data/lib/lutaml/key_value/transformation/value_serializer.rb +7 -7
  6. data/lib/lutaml/key_value/transformation.rb +27 -17
  7. data/lib/lutaml/model/adapter_resolver.rb +4 -6
  8. data/lib/lutaml/model/attribute.rb +26 -23
  9. data/lib/lutaml/model/cached_type_resolver.rb +10 -9
  10. data/lib/lutaml/model/cli.rb +1 -1
  11. data/lib/lutaml/model/collection.rb +4 -4
  12. data/lib/lutaml/model/comparable_model.rb +11 -11
  13. data/lib/lutaml/model/config.rb +1 -1
  14. data/lib/lutaml/model/consolidation/dispatcher.rb +1 -1
  15. data/lib/lutaml/model/consolidation/pattern_chunker.rb +3 -3
  16. data/lib/lutaml/model/format_registry.rb +6 -4
  17. data/lib/lutaml/model/global_context.rb +2 -2
  18. data/lib/lutaml/model/global_register.rb +1 -1
  19. data/lib/lutaml/model/instrumentation.rb +1 -1
  20. data/lib/lutaml/model/mapping/mapping_rule.rb +3 -3
  21. data/lib/lutaml/model/mapping/model_mapping.rb +1 -1
  22. data/lib/lutaml/model/mapping/model_mapping_rule.rb +1 -1
  23. data/lib/lutaml/model/register.rb +3 -3
  24. data/lib/lutaml/model/render_policy.rb +11 -17
  25. data/lib/lutaml/model/runtime_compatibility.rb +0 -1
  26. data/lib/lutaml/model/schema/xml_compiler/group.rb +1 -1
  27. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +1 -1
  28. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -2
  29. data/lib/lutaml/model/schema/xml_compiler.rb +14 -14
  30. data/lib/lutaml/model/serialize/attribute_definition.rb +1 -1
  31. data/lib/lutaml/model/serialize/deserialization_context.rb +50 -0
  32. data/lib/lutaml/model/serialize/format_conversion.rb +2 -2
  33. data/lib/lutaml/model/serialize/initialization.rb +44 -7
  34. data/lib/lutaml/model/serialize/model_import.rb +1 -1
  35. data/lib/lutaml/model/serialize.rb +8 -1
  36. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -1
  37. data/lib/lutaml/model/store.rb +27 -21
  38. data/lib/lutaml/model/transformation_registry.rb +1 -1
  39. data/lib/lutaml/model/type_context.rb +7 -1
  40. data/lib/lutaml/model/type_resolver.rb +1 -6
  41. data/lib/lutaml/model/utils.rb +19 -6
  42. data/lib/lutaml/model/validation_framework.rb +1 -1
  43. data/lib/lutaml/model/value_transformer.rb +2 -2
  44. data/lib/lutaml/model/version.rb +1 -1
  45. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -1
  46. data/lib/lutaml/xml/adapter/base_adapter.rb +10 -14
  47. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +3 -3
  48. data/lib/lutaml/xml/adapter/plan_based_builder.rb +14 -14
  49. data/lib/lutaml/xml/adapter/xml_serializer.rb +3 -3
  50. data/lib/lutaml/xml/configurable.rb +2 -1
  51. data/lib/lutaml/xml/data_model.rb +2 -2
  52. data/lib/lutaml/xml/decisions/decision_context.rb +3 -3
  53. data/lib/lutaml/xml/decisions/rules/element_form_default_unqualified_rule.rb +1 -1
  54. data/lib/lutaml/xml/decisions/rules/element_form_option_rule.rb +1 -1
  55. data/lib/lutaml/xml/decisions/rules/used_prefix_rule.rb +1 -1
  56. data/lib/lutaml/xml/declaration_plan.rb +2 -2
  57. data/lib/lutaml/xml/declaration_planner.rb +12 -13
  58. data/lib/lutaml/xml/document.rb +13 -13
  59. data/lib/lutaml/xml/format_chooser.rb +3 -3
  60. data/lib/lutaml/xml/hoisting_algorithm.rb +1 -1
  61. data/lib/lutaml/xml/mapping.rb +2 -2
  62. data/lib/lutaml/xml/mapping_rule.rb +16 -3
  63. data/lib/lutaml/xml/model_transform.rb +17 -19
  64. data/lib/lutaml/xml/namespace_collector.rb +10 -10
  65. data/lib/lutaml/xml/namespace_declaration.rb +2 -2
  66. data/lib/lutaml/xml/namespace_declaration_data.rb +5 -8
  67. data/lib/lutaml/xml/namespace_scope_config.rb +3 -2
  68. data/lib/lutaml/xml/namespace_type_resolver.rb +4 -4
  69. data/lib/lutaml/xml/nokogiri/element.rb +2 -2
  70. data/lib/lutaml/xml/polymorphic_value_handler.rb +1 -1
  71. data/lib/lutaml/xml/schema/xsd/base.rb +7 -7
  72. data/lib/lutaml/xml/schema/xsd/choice.rb +2 -2
  73. data/lib/lutaml/xml/schema/xsd/complex_type.rb +5 -5
  74. data/lib/lutaml/xml/schema/xsd/errors/message_builder.rb +3 -3
  75. data/lib/lutaml/xml/schema/xsd/group.rb +2 -2
  76. data/lib/lutaml/xml/schema/xsd/schema_validator.rb +3 -0
  77. data/lib/lutaml/xml/schema/xsd/sequence.rb +2 -2
  78. data/lib/lutaml/xml/schema/xsd_schema.rb +5 -5
  79. data/lib/lutaml/xml/serialization/format_conversion.rb +4 -3
  80. data/lib/lutaml/xml/transformation/element_builder.rb +4 -2
  81. data/lib/lutaml/xml/transformation/rule_applier.rb +2 -2
  82. data/lib/lutaml/xml/transformation/value_serializer.rb +4 -6
  83. data/lib/lutaml/xml/transformation.rb +4 -4
  84. data/lib/lutaml/xml/type/configurable.rb +0 -4
  85. data/lib/lutaml/xml/xml_element.rb +21 -13
  86. data/lutaml-model.gemspec +1 -1
  87. data/spec/lutaml/model/cached_type_resolver_spec.rb +3 -3
  88. data/spec/lutaml/model/optimization_spec.rb +228 -0
  89. data/spec/lutaml/model/store_spec.rb +41 -0
  90. data/spec/lutaml/xml/data_model_spec.rb +10 -28
  91. metadata +6 -4
@@ -41,7 +41,7 @@ module Lutaml
41
41
  resolved_element_order&.each do |child|
42
42
  if child.is_a?(Element)
43
43
  array << child
44
- elsif child.respond_to?(:child_elements)
44
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
45
45
  child.child_elements(array)
46
46
  end
47
47
  end
@@ -54,7 +54,7 @@ module Lutaml
54
54
  resolved_element_order&.any? do |child|
55
55
  if child.is_a?(Element)
56
56
  reference_matches?(element_name, child.ref || child.name)
57
- elsif child.respond_to?(:find_elements_used)
57
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
58
58
  child.find_elements_used(element_name)
59
59
  end
60
60
  end || false
@@ -105,7 +105,7 @@ except: DIRECT_CHILD_ELEMENTS_EXCEPTION)
105
105
  resolved_element_order&.each do |child|
106
106
  if child.is_a?(Element)
107
107
  array << child
108
- elsif child.respond_to?(:child_elements)
108
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
109
109
  child.child_elements(array)
110
110
  end
111
111
  end
@@ -118,7 +118,7 @@ except: DIRECT_CHILD_ELEMENTS_EXCEPTION)
118
118
  resolved_element_order&.any? do |child|
119
119
  if child.is_a?(Element)
120
120
  reference_matches?(element_name, child.ref || child.name)
121
- elsif child.respond_to?(:find_elements_used)
121
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
122
122
  child.find_elements_used(element_name)
123
123
  end
124
124
  end || false
@@ -139,9 +139,9 @@ except: DIRECT_CHILD_ELEMENTS_EXCEPTION)
139
139
  # Get elements from the primary content model (sequence, choice, or all).
140
140
  # @return [Array<Element>] Elements exposed by the active content model
141
141
  def elements
142
- return sequence.element if sequence.respond_to?(:element)
143
- return choice.element if choice.respond_to?(:element)
144
- return all.element if all.respond_to?(:element)
142
+ return sequence.element if sequence
143
+ return choice.element if choice
144
+ return all.element if all
145
145
 
146
146
  []
147
147
  end
@@ -28,8 +28,8 @@ module Lutaml
28
28
  parts = []
29
29
  parts << header
30
30
  parts << context_details if @error.context && !@error.context.to_h.empty?
31
- parts << suggestions_section if @error.respond_to?(:suggestions) && @error.suggestions.any?
32
- parts << troubleshooting_section if @error.respond_to?(:troubleshooting_tips) && @error.troubleshooting_tips.any?
31
+ parts << suggestions_section if @error.is_a?(EnhancedError) && @error.suggestions.any?
32
+ parts << troubleshooting_section if @error.is_a?(EnhancedError) && @error.troubleshooting_tips.any?
33
33
  parts.compact.join("\n\n")
34
34
  end
35
35
 
@@ -39,7 +39,7 @@ module Lutaml
39
39
  #
40
40
  # @return [String] Error header
41
41
  def header
42
- severity = @error.respond_to?(:severity) ? @error.severity.to_s.upcase : "ERROR"
42
+ severity = @error.is_a?(EnhancedError) ? @error.severity.to_s.upcase : "ERROR"
43
43
  "#{severity}: #{@error.message}"
44
44
  end
45
45
 
@@ -44,7 +44,7 @@ module Lutaml
44
44
  group.resolved_element_order&.each do |child|
45
45
  if child.is_a?(Element)
46
46
  array << child
47
- elsif child.respond_to?(:child_elements)
47
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
48
48
  child.child_elements(array)
49
49
  end
50
50
  end
@@ -57,7 +57,7 @@ module Lutaml
57
57
  resolved_element_order&.any? do |child|
58
58
  if child.is_a?(Element)
59
59
  reference_matches?(element_name, child.ref || child.name)
60
- elsif child.respond_to?(:find_elements_used)
60
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
61
61
  child.find_elements_used(element_name)
62
62
  end
63
63
  end || false
@@ -6,6 +6,9 @@ module Lutaml
6
6
  module Xml
7
7
  module Schema
8
8
  module Xsd
9
+ # Ensure error classes are loaded before SchemaValidator references them
10
+ Error # trigger autoload
11
+
9
12
  # Validates XSD schema documents before parsing
10
13
  #
11
14
  # This validator checks that XML content is a valid XSD schema document
@@ -41,7 +41,7 @@ module Lutaml
41
41
  resolved_element_order&.each do |child|
42
42
  if child.is_a?(Element)
43
43
  array << child
44
- elsif child.respond_to?(:child_elements)
44
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
45
45
  child.child_elements(array)
46
46
  end
47
47
  end
@@ -54,7 +54,7 @@ module Lutaml
54
54
  resolved_element_order&.any? do |child|
55
55
  if child.is_a?(Element)
56
56
  reference_matches?(element_name, child.ref || child.name)
57
- elsif child.respond_to?(:find_elements_used)
57
+ elsif child.is_a?(Group) || child.is_a?(Sequence) || child.is_a?(Choice) || child.is_a?(ComplexType)
58
58
  child.find_elements_used(element_name)
59
59
  end
60
60
  end || false
@@ -82,7 +82,7 @@ module Lutaml
82
82
  attr_type = attr.type(register)
83
83
 
84
84
  # Validate Type::Value xsd_type
85
- if attr_type.respond_to?(:xsd_type)
85
+ if attr_type.is_a?(Class) && attr_type < Lutaml::Model::Type::Value
86
86
  type_name = attr_type.xsd_type
87
87
  classification = classify_xsd_type(type_name, klass, register)
88
88
 
@@ -413,7 +413,7 @@ _mapping_rule = nil)
413
413
  return attr.options[:xsd_type] if attr.options[:xsd_type]
414
414
 
415
415
  # 2. Check if type has xsd_type method (Type-level)
416
- if attr_type.respond_to?(:xsd_type)
416
+ if attr_type.is_a?(Class) && attr_type < Lutaml::Model::Type::Value
417
417
  # Special handling for Reference type
418
418
  if attr_type == Lutaml::Model::Type::Reference
419
419
  target_xsd_type = get_target_xsd_type(attr, register)
@@ -466,7 +466,7 @@ _mapping_rule = nil)
466
466
 
467
467
  # Get namespace info from Model class (Serializable)
468
468
  def get_model_namespace_info(klass)
469
- mapping = klass.respond_to?(:mappings_for) ? klass.mappings_for(:xml) : nil
469
+ mapping = klass.is_a?(Class) && klass.include?(Lutaml::Model::Serialize) ? klass.mappings_for(:xml) : nil
470
470
  return {} unless mapping
471
471
 
472
472
  {
@@ -487,8 +487,8 @@ _mapping_rule = nil)
487
487
 
488
488
  # XmlNamespace class
489
489
  {
490
- uri: ns.respond_to?(:uri) ? ns.uri : nil,
491
- prefix: ns.respond_to?(:prefix_default) ? ns.prefix_default : nil,
490
+ uri: ns.is_a?(Class) && ns < Lutaml::Xml::Namespace ? ns.uri : nil,
491
+ prefix: ns.is_a?(Class) && ns < Lutaml::Xml::Namespace ? ns.prefix_default : nil,
492
492
  class: ns,
493
493
  }
494
494
  end
@@ -97,7 +97,7 @@ module Lutaml
97
97
  raise Lutaml::Model::NoRootMappingError.new(self) unless valid
98
98
 
99
99
  options[:encoding] = doc.encoding
100
- if doc.respond_to?(:doctype) && doc.doctype
100
+ if doc.is_a?(Lutaml::Xml::Document) && doc.doctype
101
101
  options[:doctype] = doc.doctype
102
102
  end
103
103
  end
@@ -214,7 +214,7 @@ module Lutaml
214
214
  def process_xml_mapping_class_inheritance(mapping_class, &block)
215
215
  # Start with a copy of the parent class's XML mapping (if any).
216
216
  parent_class = superclass_with_xml_mapping(self)
217
- parent_xml_mapping = if parent_class.respond_to?(:mappings)
217
+ parent_xml_mapping = if parent_class.is_a?(Class) && parent_class.include?(Lutaml::Model::Serialize)
218
218
  parent_class.mappings[:xml]
219
219
  end
220
220
  @xml_mapping = if parent_xml_mapping
@@ -224,7 +224,8 @@ module Lutaml
224
224
  end
225
225
 
226
226
  # Get the parent mapping instance (DSL already evaluated via xml_mapping_instance)
227
- parent_mapping = if mapping_class.respond_to?(:xml_mapping_instance) &&
227
+ parent_mapping = if mapping_class.is_a?(Class) &&
228
+ (mapping_class < Lutaml::Xml::Mapping || mapping_class.include?(Lutaml::Model::Serialize)) &&
228
229
  mapping_class.xml_mapping_instance
229
230
  mapping_class.xml_mapping_instance
230
231
  else
@@ -91,7 +91,7 @@ parent_element_form_default)
91
91
  if attr_type.is_a?(Class) && attr_type.include?(Lutaml::Model::Serialize)
92
92
  attr_mapping = attr_type.mappings_for(:xml)
93
93
  attr_ns = attr_mapping&.namespace_class
94
- attr_ns_param = attr_mapping&.send(:namespace_param)
94
+ attr_ns_param = attr_mapping&.namespace_param
95
95
  # Use child's explicit namespace if it differs from parent's
96
96
  # If child has no namespace declaration (nil) or explicit blank (:blank),
97
97
  # do NOT inherit parent's namespace - return nil
@@ -165,7 +165,9 @@ is_polymorphic_subtype)
165
165
  if polymorphic_config.is_a?(Hash)
166
166
  poly_attr = polymorphic_config[:attribute]
167
167
  poly_class_map = polymorphic_config[:class_map]
168
- poly_value = value.send(poly_attr) if poly_attr && value.respond_to?(poly_attr)
168
+ poly_value = if poly_attr && value.is_a?(Lutaml::Model::Serialize) && value.class.attributes.key?(poly_attr)
169
+ value.public_send(poly_attr)
170
+ end
169
171
  if poly_value && poly_class_map && (klass_name = poly_class_map[poly_value.to_s])
170
172
  Object.const_get(klass_name)
171
173
  else
@@ -241,8 +241,8 @@ register_id)
241
241
  def apply_custom_method(parent, rule, model_class, model_instance)
242
242
  wrapper = ::Lutaml::Xml::CustomMethodWrapper.new(parent)
243
243
  mapper_instance = model_class.new
244
- mapper_instance.send(rule.custom_methods[:to], model_instance,
245
- parent, wrapper)
244
+ mapper_instance.public_send(rule.custom_methods[:to], model_instance,
245
+ parent, wrapper)
246
246
  end
247
247
 
248
248
  # Apply element value handling collections
@@ -54,7 +54,7 @@ module Lutaml
54
54
  end
55
55
 
56
56
  # Use type's serialization if available
57
- if rule.attribute_type.respond_to?(:serialize)
57
+ if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
58
58
  rule.attribute_type.serialize(value)
59
59
  else
60
60
  value.to_s
@@ -101,7 +101,7 @@ module Lutaml
101
101
  # @param attribute_type [Class, nil] The attribute type
102
102
  # @return [Boolean] true if custom Value type
103
103
  def custom_value_type?(attribute_type)
104
- attribute_type.respond_to?(:new) &&
104
+ attribute_type.is_a?(Class) &&
105
105
  attribute_type < Lutaml::Model::Type::Value
106
106
  end
107
107
 
@@ -112,14 +112,12 @@ module Lutaml
112
112
  # @return [String, nil] Serialized value or nil
113
113
  def serialize_custom_value(value, attribute_type)
114
114
  # Skip wrapping if value is already the correct type
115
- if value.is_a?(attribute_type) && value.respond_to?(:to_xml)
115
+ if value.is_a?(attribute_type)
116
116
  return value.to_xml
117
117
  end
118
118
 
119
119
  wrapped_value = attribute_type.new(value)
120
- if wrapped_value.respond_to?(:to_xml)
121
- wrapped_value.to_xml
122
- end
120
+ wrapped_value.to_xml
123
121
  end
124
122
 
125
123
  # Find attribute definition from model class
@@ -139,9 +139,9 @@ module Lutaml
139
139
  # @param model_instance [Object] The model instance
140
140
  def handle_schema_location(root, model_instance)
141
141
  # Case 1: SchemaLocation object (programmatic creation)
142
- if model_instance.respond_to?(:schema_location) && model_instance.schema_location
142
+ if model_instance.is_a?(Lutaml::Model::Serialize) && model_instance.schema_location
143
143
  schema_loc = model_instance.schema_location
144
- if schema_loc.respond_to?(:to_xml_attributes)
144
+ if schema_loc.is_a?(Lutaml::Xml::SchemaLocation)
145
145
  schema_attrs = schema_loc.to_xml_attributes
146
146
  schema_attrs.each do |attr_name, attr_value|
147
147
  attr = ::Lutaml::Xml::DataModel::XmlAttribute.new(attr_name,
@@ -150,7 +150,7 @@ module Lutaml
150
150
  end
151
151
  end
152
152
  # Case 2: @raw_schema_location string (from parsing/round-trip)
153
- elsif model_instance.respond_to?(:raw_schema_location) && model_instance.raw_schema_location
153
+ elsif model_instance.is_a?(Lutaml::Model::Serialize) && model_instance.raw_schema_location
154
154
  raw_schema_loc = model_instance.raw_schema_location
155
155
  if raw_schema_loc && !raw_schema_loc.empty?
156
156
  add_raw_schema_location(root, raw_schema_loc)
@@ -292,7 +292,7 @@ module Lutaml
292
292
  # @param model_instance [Object] The model instance
293
293
  # @param options [Hash] Options
294
294
  def apply_standard_rules(root, model_instance, options)
295
- attr_order = model_instance.respond_to?(:attribute_order) &&
295
+ attr_order = model_instance.is_a?(Lutaml::Model::Serialize) &&
296
296
  model_instance.attribute_order
297
297
 
298
298
  rules = if attr_order
@@ -104,9 +104,7 @@ module Lutaml
104
104
  # @return [String, nil] parent's xsd_type if set
105
105
  def inherited_xsd_type
106
106
  return nil if superclass == Lutaml::Model::Type::Value
107
- return nil unless superclass.respond_to?(:xsd_type)
108
107
 
109
- # Get parent's @xsd_type directly (not default_xsd_type)
110
108
  parent_xsd = superclass.instance_variable_get(:@xsd_type)
111
109
  parent_xsd || superclass.inherited_xsd_type
112
110
  end
@@ -116,7 +114,6 @@ module Lutaml
116
114
  # @return [Class, Symbol, nil] parent's namespace class if set
117
115
  def inherited_namespace
118
116
  return nil if superclass == Lutaml::Model::Type::Value
119
- return nil unless superclass.respond_to?(:xml_mapping)
120
117
 
121
118
  parent_mapping = superclass.xml_mapping
122
119
  parent_mapping&.namespace_class || superclass.inherited_namespace
@@ -136,7 +133,6 @@ module Lutaml
136
133
  # @return [Lutaml::Xml::Type::ValueXmlMapping] the new XML mapping
137
134
  def inherit_xml_mapping_from_parent
138
135
  return create_xml_mapping if superclass == Lutaml::Model::Type::Value
139
- return create_xml_mapping unless superclass.respond_to?(:xml_mapping)
140
136
 
141
137
  parent_mapping = superclass.xml_mapping
142
138
  return create_xml_mapping unless parent_mapping
@@ -157,7 +157,7 @@ module Lutaml
157
157
  def name
158
158
  return @name unless namespace_prefix
159
159
 
160
- @prefixed_name ||= "#{namespace_prefix}:#{@name}" # rubocop:disable Naming/MemoizedInstanceVariableName
160
+ @prefixed_name ||= -"#{namespace_prefix}:#{@name}" # rubocop:disable Naming/MemoizedInstanceVariableName
161
161
  end
162
162
 
163
163
  def namespaced_name
@@ -174,15 +174,18 @@ module Lutaml
174
174
  # 3. Fall back to namespaces[nil] if exists
175
175
  # 4. Return unprefixed name
176
176
  ns = namespaces
177
- if namespace_prefix && ns[namespace_prefix]
178
- "#{ns[namespace_prefix].uri}:#{@name}"
179
- elsif @default_namespace
180
- "#{@default_namespace}:#{@name}"
181
- elsif ns[nil]
182
- "#{ns[nil].uri}:#{@name}"
183
- else
184
- @name
185
- end
177
+ raw = if namespace_prefix && ns[namespace_prefix]
178
+ "#{ns[namespace_prefix].uri}:#{@name}"
179
+ elsif @default_namespace
180
+ "#{@default_namespace}:#{@name}"
181
+ elsif ns[nil]
182
+ "#{ns[nil].uri}:#{@name}"
183
+ else
184
+ @name
185
+ end
186
+ # Deduplicate the string to reduce GC pressure from repeated
187
+ # namespace URIs across thousands of elements.
188
+ -raw
186
189
  end
187
190
  end
188
191
 
@@ -236,7 +239,8 @@ module Lutaml
236
239
 
237
240
  @namespace_uri = begin
238
241
  ns = namespace
239
- ns&.uri
242
+ uri = ns&.uri
243
+ uri ? -uri : nil
240
244
  end
241
245
  end
242
246
 
@@ -296,9 +300,13 @@ module Lutaml
296
300
 
297
301
  def text
298
302
  return @text if children.empty?
299
- return text_children.map(&:text) if content_bearing_children_count > 1
303
+ return @computed_text if defined?(@computed_text)
300
304
 
301
- text_children.map(&:text).join
305
+ @computed_text = if content_bearing_children_count > 1
306
+ text_children.map(&:text)
307
+ else
308
+ text_children.map(&:text).join
309
+ end
302
310
  end
303
311
 
304
312
  def cdata
data/lutaml-model.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency "canon"
37
37
  spec.add_dependency "concurrent-ruby"
38
38
  spec.add_dependency "liquid", ">= 4.0", "< 6.0"
39
- spec.add_dependency "moxml", ">= 0.1.16"
39
+ spec.add_dependency "moxml", ">= 0.1.20"
40
40
  spec.add_dependency "ostruct"
41
41
  spec.add_dependency "rubyzip", "~> 2.3"
42
42
  spec.add_dependency "thor"
@@ -129,14 +129,14 @@ RSpec.describe Lutaml::Model::CachedTypeResolver do
129
129
  expect(result2).to eq(result1)
130
130
  end
131
131
 
132
- it "passes through Class objects without caching" do
132
+ it "caches Class objects with substitution applied" do
133
133
  klass = Class.new
134
134
  result = resolver.resolve(klass, context)
135
135
  expect(result).to eq(klass)
136
136
 
137
- # Should not be cached
137
+ # Should be cached
138
138
  stats = resolver.cache_stats
139
- expect(stats[:size]).to eq(0)
139
+ expect(stats[:size]).to eq(1)
140
140
  end
141
141
 
142
142
  it "raises UnknownTypeError for unknown types" do
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Optimization behaviors" do
6
+ before do
7
+ Lutaml::Model::GlobalContext.clear_caches
8
+ Lutaml::Model::TransformationRegistry.instance.clear
9
+ Lutaml::Model::GlobalRegister.instance.reset
10
+ Lutaml::Model::Store.clear
11
+ end
12
+
13
+ after do
14
+ Lutaml::Model::GlobalContext.clear_caches
15
+ Lutaml::Model::TransformationRegistry.instance.clear
16
+ Lutaml::Model::GlobalRegister.instance.reset
17
+ Lutaml::Model::Store.clear
18
+ end
19
+
20
+ describe "DeserializationContext propagation" do
21
+ let(:propagation_keys) do
22
+ Lutaml::Model::Serialize::DeserializationContext::PROPAGATION_KEYS
23
+ end
24
+
25
+ it "propagates only whitelisted keys" do
26
+ options = {
27
+ lutaml_parent: "parent",
28
+ lutaml_root: "root",
29
+ default_namespace: "http://example.com",
30
+ namespace_uri: "http://internal.com",
31
+ resolved_type: String,
32
+ converted: true,
33
+ polymorphic: :by_type,
34
+ collection: true,
35
+ }
36
+
37
+ propagated = Lutaml::Model::Serialize::DeserializationContext.propagate(options)
38
+
39
+ expect(propagated).to eq({
40
+ lutaml_parent: "parent",
41
+ lutaml_root: "root",
42
+ default_namespace: "http://example.com",
43
+ polymorphic: :by_type,
44
+ collection: true,
45
+ })
46
+ end
47
+
48
+ it "excludes parent-internal keys" do
49
+ options = {
50
+ namespace_uri: "http://internal.com",
51
+ resolved_type: String,
52
+ converted: true,
53
+ mappings: double("mappings"),
54
+ }
55
+
56
+ propagated = Lutaml::Model::Serialize::DeserializationContext.propagate(options)
57
+
58
+ expect(propagated).to be_empty
59
+ end
60
+
61
+ it "CHILD_PROPAGATION_KEYS matches PROPAGATION_KEYS" do
62
+ expect(Lutaml::Model::Attribute::CHILD_PROPAGATION_KEYS).to eq(propagation_keys)
63
+ end
64
+ end
65
+
66
+ describe "conditional reference store registration" do
67
+ it "registers instances by default" do
68
+ klass = Class.new(Lutaml::Model::Serializable) do
69
+ attribute :id, :string
70
+ end
71
+
72
+ obj = klass.new(id: "test")
73
+ result = Lutaml::Model::Store.resolve(klass, :id, "test")
74
+ expect(result).to eq(obj)
75
+ end
76
+
77
+ it "skips registration when skip_reference_registration is declared" do
78
+ klass = Class.new(Lutaml::Model::Serializable) do
79
+ attribute :id, :string
80
+ skip_reference_registration
81
+ end
82
+
83
+ klass.new(id: "test")
84
+ result = Lutaml::Model::Store.resolve(klass, :id, "test")
85
+ expect(result).to be_nil
86
+ end
87
+
88
+ it "reference_resolvable? returns true by default" do
89
+ klass = Class.new(Lutaml::Model::Serializable) do
90
+ attribute :id, :string
91
+ end
92
+
93
+ expect(klass.reference_resolvable?).to be true
94
+ end
95
+
96
+ it "reference_resolvable? returns false after skip_reference_registration" do
97
+ klass = Class.new(Lutaml::Model::Serializable) do
98
+ attribute :id, :string
99
+ skip_reference_registration
100
+ end
101
+
102
+ expect(klass.reference_resolvable?).to be false
103
+ end
104
+
105
+ it "skips registration during allocate_for_deserialization" do
106
+ klass = Class.new(Lutaml::Model::Serializable) do
107
+ attribute :id, :string
108
+ skip_reference_registration
109
+ end
110
+
111
+ instance = klass.allocate_for_deserialization
112
+ instance.public_send(:id=, "deserialized")
113
+ result = Lutaml::Model::Store.resolve(klass, :id, "deserialized")
114
+ expect(result).to be_nil
115
+ end
116
+ end
117
+
118
+ describe "class_attributes accessor" do
119
+ it "returns raw attributes without register merging" do
120
+ klass = Class.new(Lutaml::Model::Serializable) do
121
+ attribute :name, :string
122
+ end
123
+
124
+ expect(klass.class_attributes).to be_a(Hash)
125
+ expect(klass.class_attributes.keys).to include(:name)
126
+ end
127
+ end
128
+
129
+ describe "MappingRule#static_namespace_option" do
130
+ it "returns frozen hash for rules with explicit namespace" do
131
+ rule = Lutaml::Xml::MappingRule.new(
132
+ "test",
133
+ to: :test,
134
+ namespace: ExampleComNamespace,
135
+ namespace_set: true,
136
+ )
137
+
138
+ result = rule.static_namespace_option
139
+ expect(result).to eq({ default_namespace: "http://example.com" })
140
+ expect(result).to be_frozen
141
+ end
142
+
143
+ it "returns the same object on repeated calls (cached)" do
144
+ rule = Lutaml::Xml::MappingRule.new(
145
+ "test",
146
+ to: :test,
147
+ namespace: ExampleComNamespace,
148
+ namespace_set: true,
149
+ )
150
+
151
+ first = rule.static_namespace_option
152
+ second = rule.static_namespace_option
153
+ expect(first).to equal(second) # same object identity
154
+ end
155
+
156
+ it "returns nil when no namespace is set" do
157
+ rule = Lutaml::Xml::MappingRule.new("test", to: :test)
158
+
159
+ expect(rule.static_namespace_option).to be_nil
160
+ end
161
+
162
+ it "returns nil when namespace is :inherit" do
163
+ rule = Lutaml::Xml::MappingRule.new(
164
+ "test",
165
+ to: :test,
166
+ namespace: :inherit,
167
+ )
168
+
169
+ expect(rule.static_namespace_option).to be_nil
170
+ end
171
+ end
172
+
173
+ describe "String deduplication in namespace URIs" do
174
+ it "XmlElement#namespace_uri deduplicates via unary minus" do
175
+ ns_instance = ExampleComNamespace.new
176
+ parent = Lutaml::Xml::XmlElement.new(nil, {}, [], nil,
177
+ name: "root",
178
+ parent_document: double(namespaces: { nil => ns_instance }))
179
+ uri = parent.namespace_uri
180
+
181
+ # The returned string should be the deduplicated version (-uri)
182
+ expect(uri).to eq("http://example.com")
183
+ expect(uri).to be_frozen
184
+ end
185
+
186
+ it "XmlElement#namespaced_name deduplicates the result string" do
187
+ ExampleComNamespace.new
188
+ parent = Lutaml::Xml::XmlElement.new(nil, {}, [], nil,
189
+ name: "root",
190
+ default_namespace: "http://example.com",
191
+ parent_document: double(namespaces: {}))
192
+ result = parent.namespaced_name
193
+
194
+ expect(result).to eq("http://example.com:root")
195
+ expect(result).to be_frozen
196
+ end
197
+ end
198
+
199
+ describe "value_set_for" do
200
+ it "transitions from nil (all defaults) to per-attribute tracking" do
201
+ klass = Class.new(Lutaml::Model::Serializable) do
202
+ attribute :name, :string
203
+ attribute :age, :integer
204
+ end
205
+
206
+ obj = klass.allocate_for_deserialization
207
+ expect(obj.using_default?(:name)).to be true
208
+ expect(obj.using_default?(:age)).to be true
209
+
210
+ obj.value_set_for(:name)
211
+ expect(obj.using_default?(:name)).to be false
212
+ expect(obj.using_default?(:age)).to be true
213
+ end
214
+
215
+ it "values_set_for sets multiple attributes at once" do
216
+ klass = Class.new(Lutaml::Model::Serializable) do
217
+ attribute :name, :string
218
+ attribute :age, :integer
219
+ end
220
+
221
+ obj = klass.allocate_for_deserialization
222
+ obj.values_set_for(%i[name age])
223
+
224
+ expect(obj.using_default?(:name)).to be false
225
+ expect(obj.using_default?(:age)).to be false
226
+ end
227
+ end
228
+ end