lutaml-model 0.8.17 → 0.8.19

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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/downstream-performance.yml +27 -1
  3. data/.github/workflows/js-pr-check.yml +102 -0
  4. data/.github/workflows/js-sync.yml +51 -0
  5. data/.github/workflows/opal.yml +26 -6
  6. data/.github/workflows/performance.yml +32 -7
  7. data/.github/workflows/rake.yml +78 -32
  8. data/.gitmodules +6 -0
  9. data/.rubocop_todo.yml +8 -0
  10. data/Gemfile +16 -1
  11. data/README.adoc +267 -0
  12. data/Rakefile +166 -4
  13. data/docs/_guides/builder-dsl.adoc +127 -0
  14. data/docs/_guides/schema-import.adoc +42 -0
  15. data/docs/_guides/xml-mapping.adoc +21 -0
  16. data/docs/_pages/validation.adoc +65 -0
  17. data/lib/compat/opal/generate_boot.rb +123 -0
  18. data/lib/compat/opal/js_bundle_entry.rb +42 -0
  19. data/lib/compat/opal/lutaml_model_boot.rb +497 -0
  20. data/lib/compat/opal/moxml_boot.rb +68 -0
  21. data/lib/compat/opal/yaml_compat.rb +32 -0
  22. data/lib/lutaml/json.rb +1 -1
  23. data/lib/lutaml/key_value/transform.rb +3 -8
  24. data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
  25. data/lib/lutaml/key_value/transformation.rb +17 -5
  26. data/lib/lutaml/model/adapter_resolver.rb +2 -2
  27. data/lib/lutaml/model/attribute.rb +88 -11
  28. data/lib/lutaml/model/cached_type_resolver.rb +1 -1
  29. data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
  30. data/lib/lutaml/model/global_context.rb +11 -8
  31. data/lib/lutaml/model/instrumentation.rb +2 -2
  32. data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
  33. data/lib/lutaml/model/register.rb +2 -2
  34. data/lib/lutaml/model/runtime_compatibility.rb +50 -0
  35. data/lib/lutaml/model/schema/class_loader.rb +58 -0
  36. data/lib/lutaml/model/schema/compiled_output.rb +83 -0
  37. data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
  38. data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
  39. data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
  40. data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
  41. data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
  42. data/lib/lutaml/model/schema/definitions/model.rb +40 -0
  43. data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
  44. data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
  45. data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
  46. data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
  47. data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
  48. data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
  49. data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
  50. data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
  51. data/lib/lutaml/model/schema/definitions.rb +24 -0
  52. data/lib/lutaml/model/schema/file_writer.rb +70 -0
  53. data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
  54. data/lib/lutaml/model/schema/generator/property.rb +21 -4
  55. data/lib/lutaml/model/schema/module_nesting.rb +31 -0
  56. data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
  57. data/lib/lutaml/model/schema/registry_generator.rb +115 -0
  58. data/lib/lutaml/model/schema/renderers/base.rb +40 -0
  59. data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
  60. data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
  61. data/lib/lutaml/model/schema/renderers/model.rb +121 -0
  62. data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
  63. data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
  64. data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
  65. data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
  66. data/lib/lutaml/model/schema/renderers/union.rb +83 -0
  67. data/lib/lutaml/model/schema/renderers.rb +19 -0
  68. data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
  69. data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
  70. data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
  71. data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
  72. data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
  73. data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
  74. data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
  75. data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
  76. data/lib/lutaml/model/schema/templates.rb +162 -0
  77. data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
  78. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
  79. data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
  80. data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
  81. data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
  82. data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
  83. data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
  84. data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
  85. data/lib/lutaml/model/schema.rb +19 -0
  86. data/lib/lutaml/model/serialize/attribute_definition.rb +23 -11
  87. data/lib/lutaml/model/serialize/builder.rb +70 -2
  88. data/lib/lutaml/model/serialize/initialization.rb +3 -2
  89. data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
  90. data/lib/lutaml/model/serialize.rb +6 -9
  91. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
  92. data/lib/lutaml/model/services.rb +0 -2
  93. data/lib/lutaml/model/store.rb +2 -1
  94. data/lib/lutaml/model/toml.rb +1 -1
  95. data/lib/lutaml/model/transform.rb +17 -39
  96. data/lib/lutaml/model/transformation_registry.rb +1 -1
  97. data/lib/lutaml/model/type.rb +1 -0
  98. data/lib/lutaml/model/union.rb +320 -0
  99. data/lib/lutaml/model/utils.rb +6 -0
  100. data/lib/lutaml/model/validation.rb +1 -4
  101. data/lib/lutaml/model/version.rb +1 -1
  102. data/lib/lutaml/model.rb +18 -0
  103. data/lib/lutaml/toml.rb +1 -1
  104. data/lib/lutaml/xml/adapter.rb +1 -1
  105. data/lib/lutaml/xml/adapter_loader.rb +1 -1
  106. data/lib/lutaml/xml/builder/base.rb +1 -1
  107. data/lib/lutaml/xml/builder.rb +1 -1
  108. data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
  109. data/lib/lutaml/xml/model_transform.rb +5 -1
  110. data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
  111. data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
  112. data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
  113. data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
  114. data/lib/lutaml/xml/transformation/element_builder.rb +21 -8
  115. data/lib/lutaml/xml/transformation/ordered_applier.rb +104 -4
  116. data/lib/lutaml/xml/xsd_validator.rb +66 -0
  117. data/lib/lutaml/xml.rb +53 -12
  118. data/lutaml-model.gemspec +4 -2
  119. data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
  120. data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
  121. data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
  122. data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
  123. data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
  124. data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
  125. data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
  126. data/spec/fixtures/xml/schema/rng/book.rng +27 -0
  127. data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
  128. data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
  129. data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
  130. data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
  131. data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
  132. data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
  133. data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
  134. data/spec/fixtures/xml/schema/rng/person.rng +23 -0
  135. data/spec/fixtures/xml/schema/rng/union.rng +14 -0
  136. data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
  137. data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
  138. data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
  139. data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
  140. data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
  141. data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
  142. data/spec/lutaml/model/attribute_spec.rb +169 -12
  143. data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
  144. data/spec/lutaml/model/collection_spec.rb +18 -0
  145. data/spec/lutaml/model/mixed_content_spec.rb +171 -0
  146. data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
  147. data/spec/lutaml/model/ordered_content_spec.rb +41 -0
  148. data/spec/lutaml/model/raw_element_spec.rb +125 -0
  149. data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
  150. data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
  151. data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
  152. data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
  153. data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
  154. data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
  155. data/spec/lutaml/model/serialize/builder_spec.rb +358 -0
  156. data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
  157. data/spec/lutaml/model/union_attribute_spec.rb +658 -0
  158. data/spec/lutaml/model/union_spec.rb +257 -0
  159. data/spec/lutaml/model/utils_spec.rb +14 -0
  160. data/spec/lutaml/xml/opal_xml_spec.rb +22 -20
  161. data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
  162. data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
  163. data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
  164. data/spec/lutaml/xml/validate_xml_with_constructs_spec.rb +222 -0
  165. data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
  166. data/spec/spec_helper.rb +28 -11
  167. data/spec/support/opal.rb +5 -2
  168. metadata +99 -31
  169. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
  170. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
  171. data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
  172. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
  173. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
  174. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
  175. data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
  176. data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
  177. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
  178. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
  179. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
  180. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
  181. data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
  182. data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
  183. data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
  184. data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
  185. data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
  186. data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
  187. data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
  188. data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
  189. data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
  190. data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
  191. data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
  192. data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
  193. data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
  194. data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
  195. data/spec/lutaml/xml/schema/compiler/simple_type_spec.rb +0 -184
@@ -108,31 +108,34 @@ module Lutaml
108
108
  current = instance_variable_get(:"@#{name}") || []
109
109
  new_value = current.is_a?(Array) ? current + [value] : value
110
110
  instance_variable_set(:"@#{name}", new_value)
111
- # Track order for mixed_content serialization
112
- track_order(name, value, nil) if @__order_tracking__
111
+ record_mutation(name, value)
113
112
  value
114
113
  end
115
114
  end
116
115
  else
117
116
  # For non-collection attributes, getter accepts optional argument
118
- # for builder-style syntax: g.description(value) sets the value
117
+ # for builder-style syntax: g.description(value) sets the value.
118
+ # Tracking happens inside the setter, so no duplicate call here.
119
119
  define_method(name) do |*args|
120
120
  if args.empty?
121
121
  instance_variable_get(:"@#{name}")
122
122
  else
123
- # Builder-style: g.description(value) sets the value
124
- value = args.first
125
- public_send(:"#{name}=", value)
126
- # Track order for mixed_content serialization
127
- track_order(name, value, nil) if @__order_tracking__
128
- value
123
+ public_send(:"#{name}=", args.first)
124
+ args.first
129
125
  end
130
126
  end
131
127
  end
132
128
 
133
129
  enum_shorthand_names = instance_variable_get(:@__enum_shorthand_names__) || Set.new
134
- unless method_defined?(:"#{name}=",
135
- false) && !enum_shorthand_names.include?(name.to_s)
130
+ # Opal's Module#method_defined? only accepts 1 arg (MRI accepts
131
+ # the optional `inherit` flag). Skip the flag there — the
132
+ # difference only matters for inherited-method filtering.
133
+ setter_defined = if Lutaml::Model.opal?
134
+ method_defined?(:"#{name}=")
135
+ else
136
+ method_defined?(:"#{name}=", false)
137
+ end
138
+ unless setter_defined && !enum_shorthand_names.include?(name.to_s)
136
139
  if attr.collection?
137
140
  define_method(:"#{name}=") do |value|
138
141
  value_set_for(name)
@@ -149,12 +152,16 @@ module Lutaml
149
152
  else
150
153
  instance_variable_set(:"@#{name}", value)
151
154
  end
155
+ # Track one entry per item so element_order reflects the
156
+ # number of <name> elements that will be emitted.
157
+ record_mutation_collection(name, value)
152
158
  end
153
159
  else
154
160
  define_method(:"#{name}=") do |value|
155
161
  value_set_for(name)
156
162
  value = attr.cast_value(value, lutaml_register)
157
163
  instance_variable_set(:"@#{name}", value)
164
+ record_mutation(name, value)
158
165
  end
159
166
  end
160
167
  end
@@ -169,6 +176,11 @@ module Lutaml
169
176
  def attribute(name, type, options = {})
170
177
  type, options = process_type_hash(type, options) if type.is_a?(::Hash)
171
178
 
179
+ if type.is_a?(::Array)
180
+ options = options.merge(union_member_types: type)
181
+ type = Lutaml::Model::Type::Union
182
+ end
183
+
172
184
  # Handle direct method option in options hash
173
185
  if options[:method]
174
186
  options[:method_name] = options.delete(:method)
@@ -39,8 +39,10 @@ module Lutaml
39
39
 
40
40
  return self unless block
41
41
 
42
- # Enable order tracking for mixed_content models
43
- @__order_tracking__ = mixed_content?
42
+ # Enable order tracking for ordered and mixed_content models.
43
+ # `ordered?` is true for both `ordered` and `mixed_content`
44
+ # (mixed_content sets @ordered = true in addition to @mixed_content).
45
+ @__order_tracking__ = ordered?
44
46
 
45
47
  # Evaluate the block - use instance_eval for no-receiver style
46
48
  # The block's first parameter determines the style:
@@ -62,6 +64,72 @@ module Lutaml
62
64
  mapping&.mixed_content? || false
63
65
  end
64
66
 
67
+ # Check if this model has ordered or mixed_content enabled.
68
+ # Both set @ordered = true on the XML mapping; mixed_content
69
+ # additionally sets @mixed_content = true. Order tracking is
70
+ # needed for either mode so the builder can emit elements in
71
+ # setter-call order rather than declaration order.
72
+ # @return [Boolean]
73
+ def ordered?
74
+ mapping = self.class.mappings_for(:xml, lutaml_register)
75
+ mapping&.ordered? || false
76
+ end
77
+
78
+ # Whether this instance was constructed via a builder block and
79
+ # therefore records mutations into element_order. Parsed models
80
+ # and instances constructed without a block do not track; their
81
+ # element_order (if any) comes from the parser and is treated as
82
+ # the complete source of truth by the serializer.
83
+ # @return [Boolean]
84
+ def order_tracking_enabled?
85
+ @__order_tracking__ ? true : false
86
+ end
87
+
88
+ # Record a singular attribute mutation in element_order.
89
+ #
90
+ # No-op unless order tracking is enabled (i.e. the instance was
91
+ # constructed via a builder block on an ordered/mixed_content
92
+ # model). This is the single entry point for singular mutations
93
+ # and is called from generated setters and getter-with-arg paths
94
+ # so that direct setters (`x.foo = v`) and appender calls
95
+ # (`x.foo(v)`) behave identically.
96
+ #
97
+ # Returns `value` so generated setters preserve Ruby's setter
98
+ # contract: `obj.foo = v` evaluates to `v`. Callers like
99
+ # `obj.foo || (obj.foo = [])` depend on this.
100
+ #
101
+ # @param attribute_name [Symbol] The attribute being mutated
102
+ # @param value [Object, nil] The value being set; stored as text
103
+ # content for content-mapped attributes, ignored otherwise
104
+ # @return [Object] the passed value
105
+ def record_mutation(attribute_name, value = nil)
106
+ return value unless @__order_tracking__
107
+
108
+ track_order(attribute_name, value, nil)
109
+ value
110
+ end
111
+
112
+ # Record a collection-attribute mutation in element_order.
113
+ #
114
+ # Emits one entry per item so that element_order length matches
115
+ # the number of serialized child elements. No-op when tracking
116
+ # is disabled, when the value is nil/empty, or when the
117
+ # collection's frozen sentinel is preserved (no real data).
118
+ #
119
+ # Returns `value` so generated setters preserve Ruby's setter
120
+ # contract (see {record_mutation}).
121
+ #
122
+ # @param attribute_name [Symbol] The collection attribute
123
+ # @param value [Object, nil] The value assigned to the collection
124
+ # @return [Object] the passed value
125
+ def record_mutation_collection(attribute_name, value)
126
+ return value unless @__order_tracking__
127
+ return value if value.nil? || Lutaml::Model::Utils.uninitialized?(value)
128
+
129
+ Array(value).each { |item| track_order(attribute_name, item, nil) }
130
+ value
131
+ end
132
+
65
133
  private
66
134
 
67
135
  # Intercept method calls to track order for mixed_content
@@ -370,7 +370,6 @@ module Lutaml
370
370
  instance_variable_get(:"@#{name}")
371
371
  else
372
372
  public_send(:"#{name}=", args.first)
373
- track_order(name, args.first, nil) if @__order_tracking__
374
373
  args.first
375
374
  end
376
375
  end
@@ -380,6 +379,7 @@ module Lutaml
380
379
  reg_attr = resolve_register_attr(name)
381
380
  value = reg_attr.cast_value(value, lutaml_register)
382
381
  instance_variable_set(:"@#{name}", value)
382
+ record_mutation(name, value)
383
383
  end
384
384
  end
385
385
 
@@ -395,7 +395,7 @@ module Lutaml
395
395
  current = [] if current.equal?(LAZY_EMPTY_COLLECTION)
396
396
  new_value = current.is_a?(Array) ? current + [value] : value
397
397
  instance_variable_set(:"@#{name}", new_value)
398
- track_order(name, value, nil) if @__order_tracking__
398
+ record_mutation(name, value)
399
399
  value
400
400
  end
401
401
  end
@@ -411,6 +411,7 @@ module Lutaml
411
411
  else
412
412
  instance_variable_set(:"@#{name}", value)
413
413
  end
414
+ record_mutation_collection(name, value)
414
415
  end
415
416
  end
416
417
 
@@ -71,71 +71,19 @@ module Lutaml
71
71
  propagated.merge(register: instance.lutaml_register))
72
72
  end
73
73
 
74
- # Apply a value map to transform a value
74
+ # Apply a value map to transform a value.
75
75
  #
76
- # Handles nil, empty, and omitted values according to the value map.
76
+ # Delegates to Attribute#apply_value_map. See attribute.rb for the
77
+ # canonical implementation.
77
78
  #
78
79
  # @param value [Object] The value to transform
79
80
  # @param value_map [Hash] The value map configuration
80
81
  # @param attr [Attribute] The attribute definition
81
82
  # @return [Object] The transformed value
82
83
  def apply_value_map(value, value_map, attr)
83
- if value.nil?
84
- value_for_option(value_map[:nil], attr)
85
- elsif Utils.empty?(value)
86
- # Check for new boolean value_map format (from: { empty: true/false })
87
- # Only use new format if the value is explicitly boolean (TrueClass or FalseClass)
88
- if value_map[:from] && (value_map[:from][:empty].is_a?(TrueClass) || value_map[:from][:empty].is_a?(FalseClass))
89
- return value_map[:from][:empty]
90
- end
91
- # Check for direct boolean format (rule.value_map(:from) returns { empty: true })
92
- # Only return directly if it's a boolean value (TrueClass/FalseClass), not a symbol
93
- if value_map[:empty].is_a?(TrueClass) || value_map[:empty].is_a?(FalseClass)
94
- return value_map[:empty]
95
- end
96
-
97
- # Fall back to legacy value_map format
98
- value_for_option(value_map[:empty], attr, value)
99
- elsif Utils.uninitialized?(value)
100
- # Check for new boolean value_map format (from: { omitted: true/false })
101
- # Only use new format if the value is explicitly boolean (TrueClass or FalseClass)
102
- if value_map[:from] && (value_map[:from][:omitted].is_a?(TrueClass) || value_map[:from][:omitted].is_a?(FalseClass))
103
- return value_map[:from][:omitted]
104
- end
105
- # Check for direct boolean format (rule.value_map(:from) returns { omitted: false })
106
- # Only return directly if it's a boolean value (TrueClass/FalseClass), not a symbol
107
- if value_map[:omitted].is_a?(TrueClass) || value_map[:omitted].is_a?(FalseClass)
108
- return value_map[:omitted]
109
- end
84
+ return value unless attr
110
85
 
111
- # Fall back to legacy value_map format
112
- value_for_option(value_map[:omitted], attr)
113
- else
114
- value
115
- end
116
- end
117
-
118
- # Get the value for a specific option in a value map
119
- #
120
- # @param option [Symbol] The option (:nil, :empty, etc.)
121
- # @param attr [Attribute] The attribute definition
122
- # @param empty_value [Object] The empty value to use (optional)
123
- # @return [Object, nil, UninitializedClass] The appropriate value
124
- def value_for_option(option, attr, empty_value = nil)
125
- return nil if option == :nil
126
- return empty_value || empty_object(attr) if option == :empty
127
-
128
- Lutaml::Model::UninitializedClass.instance
129
- end
130
-
131
- # Get an empty object for an attribute
132
- #
133
- # @param attr [Attribute] The attribute definition
134
- # @return [String, Array] Empty string or collection
135
- def empty_object(attr)
136
- return attr.build_collection if attr.collection?
137
-
138
- ""
86
+ attr.apply_value_map(value, value_map)
139
87
  end
140
88
 
141
89
  # Ensure a value is UTF-8 encoded
@@ -145,6 +93,10 @@ module Lutaml
145
93
  def ensure_utf8(value)
146
94
  case value
147
95
  when String
96
+ # Opal strings are JS strings with no per-string encoding;
97
+ # String#encode there doesn't accept MRI's keyword options.
98
+ return value if Lutaml::Model.opal?
99
+
148
100
  value.encode("UTF-8", invalid: :replace, undef: :replace,
149
101
  replace: "")
150
102
  when Array
@@ -202,9 +202,7 @@ module Lutaml
202
202
  def validate_attribute!(attr_name)
203
203
  attr = self.class.attributes[attr_name]
204
204
  value = instance_variable_get(:"@#{attr_name}")
205
- resolver = Services::DefaultValueResolver.new(attr, lutaml_register,
206
- self)
207
- attr.validate_value!(value, lutaml_register, resolver)
205
+ attr.validate_value!(value, lutaml_register, instance_object: self)
208
206
  end
209
207
 
210
208
  def key_exist?(hash, key)
@@ -301,12 +299,11 @@ module Lutaml
301
299
  return attr_value(attrs, name, attr)
302
300
  end
303
301
 
304
- if attr.default_set?(lutaml_register, self)
305
- using_default_for(name)
306
- attr.default(lutaml_register, self)
307
- else
308
- Lutaml::Model::UninitializedClass.instance
309
- end
302
+ resolved = attr.default_value(lutaml_register, self)
303
+ return Lutaml::Model::UninitializedClass.instance if Lutaml::Model::Utils.uninitialized?(resolved)
304
+
305
+ using_default_for(name)
306
+ attr.cast_value(resolved, lutaml_register)
310
307
  end
311
308
 
312
309
  def resolve_reference_key(ref)
@@ -50,9 +50,8 @@ instance_object)
50
50
  return fetch_value(name) if Utils.string_or_symbol_key?(doc, name)
51
51
 
52
52
  if attr
53
- resolver = Services::DefaultValueResolver.new(attr, register,
54
- instance_object)
55
- return resolver.default_value if resolver.default_set?
53
+ resolved = attr.default_value(register, instance_object)
54
+ return resolved unless Utils.uninitialized?(resolved)
56
55
  end
57
56
 
58
57
  uninitialized_value
@@ -4,8 +4,6 @@ module Lutaml
4
4
  module Model
5
5
  module Services
6
6
  autoload :Base, "#{__dir__}/services/base"
7
- autoload :DefaultValueResolver,
8
- "#{__dir__}/services/default_value_resolver"
9
7
  autoload :Type, "#{__dir__}/services/type"
10
8
  end
11
9
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "weakref"
3
+ # Opal has no `weakref` stdlib; runtime_compatibility.rb provides a stub.
4
+ require "weakref" unless Lutaml::Model.opal?
4
5
 
5
6
  module Lutaml
6
7
  module Model
@@ -16,7 +16,7 @@ module Lutaml
16
16
  Transform = ::Lutaml::Toml::Adapter::Transform
17
17
 
18
18
  def self.detect_toml_adapter
19
- return nil if Lutaml::Model::RuntimeCompatibility.opal?
19
+ return nil if Lutaml::Model.opal?
20
20
 
21
21
  # Skip tomlib on Windows entirely due to segfault issues
22
22
  if Lutaml::Model::RuntimeCompatibility.windows?
@@ -85,48 +85,26 @@ module Lutaml
85
85
  protected
86
86
 
87
87
  def apply_value_map(value, value_map, attr)
88
- if value.nil?
89
- value_for_option(value_map[:nil], attr)
90
- elsif Utils.empty?(value)
91
- # Check for boolean value_map format (value_map[:empty] is true/false)
92
- # Only apply for Boolean type attributes
93
- if value_map[:empty].is_a?(TrueClass) || value_map[:empty].is_a?(FalseClass)
94
- # Check if attribute is a Boolean type
95
- attr_type = attr&.type || attr&.unresolved_type
96
- if attr_type == Lutaml::Model::Type::Boolean
97
- return value_map[:empty]
98
- end
99
- end
100
- value_for_option(value_map[:empty], attr, value)
101
- elsif Utils.uninitialized?(value)
102
- # Check for boolean value_map format (value_map[:omitted] is true/false)
103
- # Only apply for Boolean type attributes
104
- if value_map[:omitted].is_a?(TrueClass) || value_map[:omitted].is_a?(FalseClass)
105
- # Check if attribute is a Boolean type
106
- attr_type = attr&.type || attr&.unresolved_type
107
- if attr_type == Lutaml::Model::Type::Boolean
108
- return value_map[:omitted]
109
- end
110
- end
111
- value_for_option(value_map[:omitted], attr)
112
- else
113
- value
88
+ return attr.apply_value_map(value, value_map) if attr
89
+
90
+ # Attribute-less dispatch for custom-method-only rules. Mirrors
91
+ # Attribute#apply_value_map for the options that need no attribute
92
+ # info: :nil -> nil and :omitted -> uninitialized. A typed :empty needs
93
+ # an Attribute (a crash case pre-consolidation), so the value passes
94
+ # through unchanged.
95
+ key = if value.nil? then :nil
96
+ elsif Utils.empty?(value) then :empty
97
+ elsif Utils.uninitialized?(value) then :omitted
98
+ end
99
+ return value unless key
100
+
101
+ case value_map[key]
102
+ when :nil then nil
103
+ when :empty then value
104
+ else Lutaml::Model::UninitializedClass.instance
114
105
  end
115
106
  end
116
107
 
117
- def value_for_option(option, attr, empty_value = nil)
118
- return nil if option == :nil
119
- return empty_value || empty_object(attr) if option == :empty
120
-
121
- Lutaml::Model::UninitializedClass.instance
122
- end
123
-
124
- def empty_object(attr)
125
- return [] if attr.collection?
126
-
127
- ""
128
- end
129
-
130
108
  def mappings_for(format, register = nil)
131
109
  context.mappings_for(format, register)
132
110
  end
@@ -90,7 +90,7 @@ module Lutaml
90
90
 
91
91
  def initialize
92
92
  @transformations = {} # Cache for transformation instances (mutex-protected for cycle detection)
93
- @mappings = if RuntimeCompatibility.opal?
93
+ @mappings = if Lutaml::Model.opal?
94
94
  {}
95
95
  else
96
96
  Concurrent::Map.new
@@ -16,6 +16,7 @@ module Lutaml
16
16
  autoload :Boolean, "#{__dir__}/type/boolean"
17
17
  autoload :Decimal, "#{__dir__}/type/decimal"
18
18
  autoload :Reference, "#{__dir__}/type/reference"
19
+ autoload :Union, "#{__dir__}/union"
19
20
  autoload :Hash, "#{__dir__}/type/hash"
20
21
  autoload :Symbol, "#{__dir__}/type/symbol"
21
22
  autoload :Duration, "#{__dir__}/type/duration"