lutaml-model 0.8.18 → 0.8.22

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +1 -1
  3. data/.github/workflows/rake.yml +1 -0
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +133 -72
  6. data/bench/bench_common.rb +8 -5
  7. data/bench/bench_niso.rb +1 -2
  8. data/bench/gate_config.rb +4 -1
  9. data/benchmark/serialization_benchmark.rb +4 -4
  10. data/docs/_guides/builder-dsl.adoc +127 -0
  11. data/docs/_guides/value-transformations.adoc +27 -0
  12. data/lib/compat/opal/generate_boot.rb +3 -1
  13. data/lib/compat/opal/lutaml_model_boot.rb +10 -7
  14. data/lib/compat/opal/moxml_boot.rb +6 -1
  15. data/lib/lutaml/hash_format/format.rb +29 -0
  16. data/lib/lutaml/hash_format.rb +5 -27
  17. data/lib/lutaml/json/format.rb +61 -0
  18. data/lib/lutaml/json.rb +4 -59
  19. data/lib/lutaml/jsonl/format.rb +22 -0
  20. data/lib/lutaml/jsonl.rb +5 -19
  21. data/lib/lutaml/key_value/format.rb +26 -0
  22. data/lib/lutaml/key_value/transform.rb +4 -1
  23. data/lib/lutaml/key_value/transformation/value_serializer.rb +0 -2
  24. data/lib/lutaml/key_value.rb +4 -21
  25. data/lib/lutaml/model/attribute.rb +38 -4
  26. data/lib/lutaml/model/collection.rb +2 -2
  27. data/lib/lutaml/model/compiled_rule.rb +1 -1
  28. data/lib/lutaml/model/config.rb +2 -2
  29. data/lib/lutaml/model/consolidation_map.rb +2 -2
  30. data/lib/lutaml/model/consolidation_rule/dispatch_block.rb +2 -2
  31. data/lib/lutaml/model/errors.rb +2 -2
  32. data/lib/lutaml/model/global_context.rb +4 -2
  33. data/lib/lutaml/model/mapping/mapping_rule.rb +4 -1
  34. data/lib/lutaml/model/mapping/model_mapping.rb +2 -2
  35. data/lib/lutaml/model/runtime_compatibility.rb +2 -2
  36. data/lib/lutaml/model/schema/class_loader.rb +2 -1
  37. data/lib/lutaml/model/schema/decorators/definition_collection.rb +4 -4
  38. data/lib/lutaml/model/schema/definitions/namespace.rb +2 -1
  39. data/lib/lutaml/model/schema/file_writer.rb +5 -2
  40. data/lib/lutaml/model/schema/renderers/base.rb +4 -3
  41. data/lib/lutaml/model/schema/renderers/mappings.rb +9 -3
  42. data/lib/lutaml/model/schema/renderers/member_decls.rb +9 -3
  43. data/lib/lutaml/model/schema/renderers/model.rb +18 -6
  44. data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +2 -1
  45. data/lib/lutaml/model/schema/renderers.rb +2 -1
  46. data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +6 -2
  47. data/lib/lutaml/model/schema/rnc_compiler.rb +3 -1
  48. data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +35 -15
  49. data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +4 -2
  50. data/lib/lutaml/model/schema/rng_compiler.rb +10 -5
  51. data/lib/lutaml/model/schema/xml_compiler/element_order.rb +2 -1
  52. data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +4 -2
  53. data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +39 -13
  54. data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +29 -10
  55. data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +2 -2
  56. data/lib/lutaml/model/schema/xml_compiler.rb +10 -5
  57. data/lib/lutaml/model/sequence.rb +4 -4
  58. data/lib/lutaml/model/serialize/attribute_definition.rb +10 -9
  59. data/lib/lutaml/model/serialize/builder.rb +55 -0
  60. data/lib/lutaml/model/serialize/format_conversion.rb +2 -2
  61. data/lib/lutaml/model/serialize/initialization.rb +12 -3
  62. data/lib/lutaml/model/serialize/model_import.rb +1 -0
  63. data/lib/lutaml/model/serialize.rb +4 -3
  64. data/lib/lutaml/model/type/uninitialized_class_guard.rb +1 -1
  65. data/lib/lutaml/model/type.rb +9 -0
  66. data/lib/lutaml/model/type_registry.rb +2 -2
  67. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  68. data/lib/lutaml/model/union.rb +5 -2
  69. data/lib/lutaml/model/utils.rb +2 -2
  70. data/lib/lutaml/model/value_policy.rb +27 -0
  71. data/lib/lutaml/model/version.rb +1 -1
  72. data/lib/lutaml/model.rb +17 -16
  73. data/lib/lutaml/rdf/mapping.rb +2 -2
  74. data/lib/lutaml/rdf/namespace_set.rb +2 -2
  75. data/lib/lutaml/rdf/transform.rb +2 -2
  76. data/lib/lutaml/toml/format.rb +48 -0
  77. data/lib/lutaml/toml.rb +5 -46
  78. data/lib/lutaml/xml/adapter/base_adapter.rb +2 -2
  79. data/lib/lutaml/xml/builder/base.rb +2 -2
  80. data/lib/lutaml/xml/format.rb +331 -0
  81. data/lib/lutaml/xml/mapping.rb +2 -2
  82. data/lib/lutaml/xml/mapping_rule.rb +62 -3
  83. data/lib/lutaml/xml/model_transform.rb +132 -55
  84. data/lib/lutaml/xml/namespace_collector.rb +3 -3
  85. data/lib/lutaml/xml/parse_session.rb +59 -0
  86. data/lib/lutaml/xml/parsed_namespace_set.rb +2 -2
  87. data/lib/lutaml/xml/schema/builder.rb +5 -5
  88. data/lib/lutaml/xml/schema/xsd/schema_location_mapping.rb +2 -2
  89. data/lib/lutaml/xml/schema/xsd.rb +0 -2
  90. data/lib/lutaml/xml/schema/xsd_schema.rb +2 -1
  91. data/lib/lutaml/xml/serialization/format_conversion.rb +3 -3
  92. data/lib/lutaml/xml/transformation/element_builder.rb +2 -1
  93. data/lib/lutaml/xml/transformation/ordered_applier.rb +99 -4
  94. data/lib/lutaml/xml/xml_element.rb +2 -0
  95. data/lib/lutaml/xml.rb +4 -328
  96. data/lib/lutaml/yaml/format.rb +30 -0
  97. data/lib/lutaml/yaml.rb +5 -28
  98. data/lib/lutaml/yamls/adapter/mapping.rb +2 -2
  99. data/lib/lutaml/yamls/format.rb +22 -0
  100. data/lib/lutaml/yamls.rb +5 -19
  101. data/lib/tasks/benchmark_runner.rb +2 -2
  102. data/lutaml-model.gemspec +1 -1
  103. data/spec/lutaml/model/attribute_apply_value_map_spec.rb +26 -13
  104. data/spec/lutaml/model/attribute_spec.rb +8 -3
  105. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +2 -2
  106. data/spec/lutaml/model/custom_from_empty_values_spec.rb +74 -0
  107. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +8 -8
  108. data/spec/lutaml/model/defaults_spec.rb +45 -0
  109. data/spec/lutaml/model/json_spec.rb +1 -2
  110. data/spec/lutaml/model/ordered_content_spec.rb +18 -7
  111. data/spec/lutaml/model/serialize/builder_spec.rb +361 -0
  112. data/spec/lutaml/model/store_spec.rb +3 -1
  113. data/spec/lutaml/model/transform_apply_value_map_spec.rb +6 -3
  114. data/spec/lutaml/model/union_attribute_spec.rb +4 -2
  115. data/spec/lutaml/model/value_policy_spec.rb +82 -0
  116. data/spec/lutaml/require_load_order_spec.rb +25 -0
  117. data/spec/lutaml/xml/attribute_element_namespace_disjoint_spec.rb +88 -0
  118. data/spec/lutaml/xml/attribute_namespace_disjointness_spec.rb +125 -0
  119. data/spec/lutaml/xml/out_of_namespace_parsing_spec.rb +123 -0
  120. data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +17 -6
  121. data/spec/lutaml/xml/schema/rng_compiler_spec.rb +15 -6
  122. data/spec/lutaml/xml/w3c_types_spec.rb +3 -6
  123. metadata +22 -4
@@ -9,10 +9,12 @@ module Lutaml
9
9
  module XmlCompiler
10
10
  extend self
11
11
 
12
- autoload :SpecBuilder, "#{__dir__}/xml_compiler/spec_builder"
13
- autoload :RegistryGenerator, "#{__dir__}/xml_compiler/registry_generator"
14
- autoload :SupportedDataTypes, "#{__dir__}/xml_compiler/supported_data_types"
15
- autoload :ElementOrder, "#{__dir__}/xml_compiler/element_order"
12
+ autoload :SpecBuilder, "#{__dir__}/xml_compiler/spec_builder"
13
+ autoload :RegistryGenerator,
14
+ "#{__dir__}/xml_compiler/registry_generator"
15
+ autoload :SupportedDataTypes,
16
+ "#{__dir__}/xml_compiler/supported_data_types"
17
+ autoload :ElementOrder, "#{__dir__}/xml_compiler/element_order"
16
18
 
17
19
  ELEMENT_ORDER_IGNORABLE = %w[import include].freeze
18
20
 
@@ -60,7 +62,10 @@ module Lutaml
60
62
  FileWriter.write(output, dir, registry_generator: RegistryGenerator)
61
63
  true
62
64
  else
63
- ClassLoader.load(output, registry_generator: RegistryGenerator) if options[:load_classes]
65
+ if options[:load_classes]
66
+ ClassLoader.load(output,
67
+ registry_generator: RegistryGenerator)
68
+ end
64
69
  output.sources
65
70
  end
66
71
  end
@@ -27,12 +27,12 @@ module Lutaml
27
27
  @model.attribute(name, type, options)
28
28
  end
29
29
 
30
- def sequence(&)
31
- instance_eval(&)
30
+ def sequence(&block)
31
+ instance_eval(&block)
32
32
  end
33
33
 
34
- def map_element(name, **)
35
- @attributes << @model.map_element(name, **)
34
+ def map_element(name, **options)
35
+ @attributes << @model.map_element(name, **options)
36
36
  end
37
37
 
38
38
  def import_model_mappings(model, register = nil)
@@ -108,24 +108,20 @@ 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
@@ -156,12 +152,16 @@ module Lutaml
156
152
  else
157
153
  instance_variable_set(:"@#{name}", value)
158
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)
159
158
  end
160
159
  else
161
160
  define_method(:"#{name}=") do |value|
162
161
  value_set_for(name)
163
162
  value = attr.cast_value(value, lutaml_register)
164
163
  instance_variable_set(:"@#{name}", value)
164
+ record_mutation(name, value)
165
165
  end
166
166
  end
167
167
  end
@@ -188,6 +188,7 @@ module Lutaml
188
188
 
189
189
  attr = Attribute.new(name, type, options)
190
190
  @attributes[name] = attr
191
+ @merged_attributes_cache = nil
191
192
  define_attribute_methods(attr)
192
193
 
193
194
  attr
@@ -75,6 +75,61 @@ module Lutaml
75
75
  mapping&.ordered? || false
76
76
  end
77
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
+
78
133
  private
79
134
 
80
135
  # Intercept method calls to track order for mixed_content
@@ -13,7 +13,7 @@ module Lutaml
13
13
  # @param format [Symbol] The format (:xml, :json, etc.)
14
14
  # @param args [Array] Additional arguments (e.g., mapping class for XML)
15
15
  # @param block [Proc] The DSL block to evaluate
16
- def process_mapping(format, *_args, &)
16
+ def process_mapping(format, *_args, &block)
17
17
  klass = ::Lutaml::Model::Config.mappings_class_for(format)
18
18
  existing = mappings[format]
19
19
  mappings[format] = if existing.nil? || !existing.is_a?(klass)
@@ -21,7 +21,7 @@ module Lutaml
21
21
  else
22
22
  existing
23
23
  end
24
- mappings[format].instance_eval(&)
24
+ mappings[format].instance_eval(&block)
25
25
 
26
26
  if mappings[format].is_a?(Lutaml::Xml::Mapping)
27
27
  mappings[format].finalize(self)
@@ -115,13 +115,18 @@ module Lutaml
115
115
  # Get all attributes for this model
116
116
  #
117
117
  # Merges class-level attributes with register-specific attributes.
118
+ # Memoized per register: this is on the per-element deserialization
119
+ # hot path, and the merged hash is stable until an attribute, import
120
+ # or register record mutates (those clear @merged_attributes_cache).
118
121
  #
119
122
  # @param register [Symbol, nil] The register context
120
123
  # @return [Hash] The attributes hash
121
124
  def attributes(register = nil)
122
125
  ensure_imports!(register) if finalized?
123
126
  if @register_records&.any?
124
- @attributes.merge(@register_records[extract_register_id(register)][:attributes])
127
+ register_id = extract_register_id(register)
128
+ (@merged_attributes_cache ||= {})[register_id] ||=
129
+ @attributes.merge(@register_records[register_id][:attributes])
125
130
  else
126
131
  @attributes
127
132
  end
@@ -183,6 +188,9 @@ module Lutaml
183
188
  GlobalContext.resolver.clear_cache(register_id)
184
189
  end
185
190
 
191
+ # Clear memoized attribute merge (see .attributes)
192
+ @merged_attributes_cache = nil
193
+
186
194
  # Clear per-Attribute type caches (stale entries from GC'd TypeContext objects)
187
195
  class_attributes.each_value(&:clear_type_cache)
188
196
  @register_records&.each_value do |record|
@@ -370,7 +378,6 @@ module Lutaml
370
378
  instance_variable_get(:"@#{name}")
371
379
  else
372
380
  public_send(:"#{name}=", args.first)
373
- track_order(name, args.first, nil) if @__order_tracking__
374
381
  args.first
375
382
  end
376
383
  end
@@ -380,6 +387,7 @@ module Lutaml
380
387
  reg_attr = resolve_register_attr(name)
381
388
  value = reg_attr.cast_value(value, lutaml_register)
382
389
  instance_variable_set(:"@#{name}", value)
390
+ record_mutation(name, value)
383
391
  end
384
392
  end
385
393
 
@@ -395,7 +403,7 @@ module Lutaml
395
403
  current = [] if current.equal?(LAZY_EMPTY_COLLECTION)
396
404
  new_value = current.is_a?(Array) ? current + [value] : value
397
405
  instance_variable_set(:"@#{name}", new_value)
398
- track_order(name, value, nil) if @__order_tracking__
406
+ record_mutation(name, value)
399
407
  value
400
408
  end
401
409
  end
@@ -411,6 +419,7 @@ module Lutaml
411
419
  else
412
420
  instance_variable_set(:"@#{name}", value)
413
421
  end
422
+ record_mutation_collection(name, value)
414
423
  end
415
424
  end
416
425
 
@@ -121,6 +121,7 @@ module Lutaml
121
121
  @register_records[register_id][:choice_attributes].concat(
122
122
  deep_duplicate_choice_attributes(model, register_id),
123
123
  )
124
+ @merged_attributes_cache = nil
124
125
  end
125
126
  private :register_only_import_model_attributes
126
127
 
@@ -175,18 +175,19 @@ module Lutaml
175
175
  @using_default[attribute_name]
176
176
  end
177
177
 
178
- # rubocop:disable Style/ArgumentsForwarding -- anonymous * requires Ruby 3.2+, but required_ruby_version >= 3.0
179
- def method_missing(method_name, *args)
178
+ def method_missing(method_name, *args, &block)
180
179
  if method_name.to_s.end_with?("=") && attribute_exist?(method_name)
181
180
  define_singleton_method(method_name) do |value|
182
181
  instance_variable_set(:"@#{method_name.to_s.chomp('=')}", value)
183
182
  end
184
183
  send(method_name, *args)
184
+ elsif ::Lutaml::Model::Attribute.default_evaluation? &&
185
+ self.class.respond_to?(method_name)
186
+ self.class.public_send(method_name, *args, &block)
185
187
  else
186
188
  super
187
189
  end
188
190
  end
189
- # rubocop:enable Style/ArgumentsForwarding
190
191
 
191
192
  def respond_to_missing?(method_name, include_private = false)
192
193
  (method_name.to_s.end_with?("=") && attribute_exist?(method_name)) ||
@@ -17,7 +17,7 @@ module Lutaml
17
17
  #
18
18
  # The prepend ensures this method runs BEFORE the subclass's
19
19
  # cast implementation, even if the subclass doesn't call super.
20
- def cast(value, *args, **kwargs, &)
20
+ def cast(value, *args, **kwargs, &block)
21
21
  # Return UninitializedClass unchanged - don't transform it
22
22
  return value if Utils.uninitialized?(value)
23
23
 
@@ -55,7 +55,16 @@ module Lutaml
55
55
  hex_binary: "Lutaml::Model::Type::HexBinary",
56
56
  }.freeze
57
57
 
58
+ BUILTIN_CLASSES =
59
+ TYPE_CODES.each_value.map { |type_class_name| const_get(type_class_name) }.freeze
60
+
58
61
  class << self
62
+ # @param type [Class] a candidate type class
63
+ # @return [Boolean] whether the class is one of the built-ins
64
+ def builtin?(type)
65
+ BUILTIN_CLASSES.include?(type)
66
+ end
67
+
59
68
  # Register all built-in types into any TypeRegistry.
60
69
  #
61
70
  # This method is used by the new type resolution architecture.
@@ -127,8 +127,8 @@ module Lutaml
127
127
  #
128
128
  # @example
129
129
  # registry.each { |name, klass| puts "#{name}: #{klass}" }
130
- def each(&)
131
- @types.each(&)
130
+ def each(&block)
131
+ @types.each(&block)
132
132
  end
133
133
 
134
134
  # Create a copy of this registry
@@ -48,7 +48,7 @@ module Lutaml
48
48
  "".encoding
49
49
  end
50
50
 
51
- def method_missing(method, *_args, &)
51
+ def method_missing(method, *_args)
52
52
  return false if method.end_with?("?")
53
53
 
54
54
  nil
@@ -131,7 +131,8 @@ module Lutaml
131
131
 
132
132
  def validate_members!(members)
133
133
  unless members.is_a?(::Array) && !members.empty?
134
- raise ArgumentError, "union type must be a non-empty array of types"
134
+ raise ArgumentError,
135
+ "union type must be a non-empty array of types"
135
136
  end
136
137
 
137
138
  resolved = members.map { |member| resolve_member!(member) }
@@ -166,7 +167,9 @@ module Lutaml
166
167
  return :no_match if keys.nil? || keys.empty?
167
168
 
168
169
  field_names = member_field_names(member, format, register)
169
- return :no_match unless keys.all? { |key| field_names.include?(key) }
170
+ return :no_match unless keys.all? do |key|
171
+ field_names.include?(key)
172
+ end
170
173
 
171
174
  # Plain-Ruby assignment (format nil) builds straight from the
172
175
  # attribute hash, mirroring how non-union model attributes accept
@@ -201,10 +201,10 @@ module Lutaml
201
201
  end
202
202
  end
203
203
 
204
- def add_singleton_method_if_not_defined(instance, method_name, &)
204
+ def add_singleton_method_if_not_defined(instance, method_name, &block)
205
205
  return if instance.singleton_class.method_defined?(method_name, false)
206
206
 
207
- instance.define_singleton_method(method_name, &)
207
+ instance.define_singleton_method(method_name, &block)
208
208
  end
209
209
 
210
210
  # Determines the appropriate register for a child model during deserialization.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ # Decides how an attribute shapes incoming raw values: whether an
6
+ # Array is split element-wise (collection semantics) or handed whole
7
+ # to the declared type (value-owning semantics).
8
+ #
9
+ # A user-defined Type::Value subclass may legitimately wrap lists or
10
+ # maps in a singular slot (#752); built-in scalar types keep the
11
+ # "collection: true is missing" guidance error.
12
+ class ValuePolicy
13
+ def initialize(collection:, union:, reference:)
14
+ @collection = collection
15
+ @union = union
16
+ @reference = reference
17
+ end
18
+
19
+ # @param type [Class, Symbol] the resolved (or unresolved) type
20
+ # @return [Boolean] whether raw values must reach the type whole
21
+ def whole_value?(type)
22
+ !@collection && !@union && !@reference &&
23
+ type.is_a?(Class) && type < Type::Value && !Type.builtin?(type)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.18"
5
+ VERSION = "0.8.22"
6
6
  end
7
7
  end
data/lib/lutaml/model.rb CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Lutaml
4
4
  # Autoload top-level modules (outside Model namespace)
5
- autoload :KeyValue, "#{__dir__}/key_value"
6
- autoload :Json, "#{__dir__}/json"
7
- autoload :Yaml, "#{__dir__}/yaml"
8
- autoload :Toml, "#{__dir__}/toml"
9
- autoload :HashFormat, "#{__dir__}/hash_format"
10
- autoload :Jsonl, "#{__dir__}/jsonl"
11
- autoload :Yamls, "#{__dir__}/yamls"
12
- autoload :Xml, "#{__dir__}/xml"
5
+ autoload :KeyValue, "#{__dir__}/key_value/format"
6
+ autoload :Json, "#{__dir__}/json/format"
7
+ autoload :Yaml, "#{__dir__}/yaml/format"
8
+ autoload :Toml, "#{__dir__}/toml/format"
9
+ autoload :HashFormat, "#{__dir__}/hash_format/format"
10
+ autoload :Jsonl, "#{__dir__}/jsonl/format"
11
+ autoload :Yamls, "#{__dir__}/yamls/format"
12
+ autoload :Xml, "#{__dir__}/xml/format"
13
13
  autoload :JsonLd, "#{__dir__}/jsonld"
14
14
  autoload :YamlLd, "#{__dir__}/yamlld"
15
15
  autoload :Turtle, "#{__dir__}/turtle"
@@ -72,6 +72,7 @@ module Lutaml
72
72
  autoload :CollectionHandler, "#{__dir__}/model/collection_handler"
73
73
  autoload :AttributeValidator, "#{__dir__}/model/attribute_validator"
74
74
  autoload :Attribute, "#{__dir__}/model/attribute"
75
+ autoload :ValuePolicy, "#{__dir__}/model/value_policy"
75
76
  autoload :JsonAdapter, "#{__dir__}/model/json_adapter"
76
77
  autoload :SchemaLocation, "#{__dir__}/model/schema_location"
77
78
  autoload :Validation, "#{__dir__}/model/validation"
@@ -258,14 +259,14 @@ end
258
259
 
259
260
  # Format files register DSL methods - use require to ensure proper loading order
260
261
  # These files set up FormatRegistry and detect/configure adapters
261
- require "#{__dir__}/key_value"
262
- require "#{__dir__}/json"
263
- require "#{__dir__}/yaml"
264
- require "#{__dir__}/toml"
265
- require "#{__dir__}/hash_format"
266
- require "#{__dir__}/jsonl"
267
- require "#{__dir__}/yamls"
268
- require "#{__dir__}/xml"
262
+ require "#{__dir__}/key_value/format"
263
+ require "#{__dir__}/json/format"
264
+ require "#{__dir__}/yaml/format"
265
+ require "#{__dir__}/toml/format"
266
+ require "#{__dir__}/hash_format/format"
267
+ require "#{__dir__}/jsonl/format"
268
+ require "#{__dir__}/yamls/format"
269
+ require "#{__dir__}/xml/format"
269
270
 
270
271
  # Optional formats: require "lutaml/jsonld" or "lutaml/turtle" to enable.
271
272
  # These are not eagerly loaded because they depend on optional gems
@@ -19,8 +19,8 @@ module Lutaml
19
19
  @namespace_set = Lutaml::Rdf::NamespaceSet.new(*namespace_classes)
20
20
  end
21
21
 
22
- def subject(&)
23
- @rdf_subject = Proc.new(&) if block_given?
22
+ def subject(&block)
23
+ @rdf_subject = Proc.new(&block) if block
24
24
  end
25
25
 
26
26
  def type(value)
@@ -38,8 +38,8 @@ module Lutaml
38
38
  nil
39
39
  end
40
40
 
41
- def each(&)
42
- @by_prefix.each_value(&)
41
+ def each(&block)
42
+ @by_prefix.each_value(&block)
43
43
  end
44
44
 
45
45
  def size
@@ -17,9 +17,9 @@ module Lutaml
17
17
  mapping.rdf_type.map { |t| resolve_single_type_uri(mapping, t) }
18
18
  end
19
19
 
20
- def each_member(instance, member_rule, &)
20
+ def each_member(instance, member_rule, &block)
21
21
  collection = Array(instance.public_send(member_rule.attr_name))
22
- collection.each(&)
22
+ collection.each(&block)
23
23
  end
24
24
 
25
25
  def member_mapping_for(member, format)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal TOML format implementation. Loaded from lib/lutaml/toml.rb
4
+ # (the public entrypoint) and from lib/lutaml/model.rb (so the model
5
+ # entrypoint triggers the registration side effects).
6
+ # No require_relative "model" here — keeps the require graph acyclic.
7
+
8
+ require "lutaml/model/runtime_compatibility"
9
+ require_relative "../key_value/format"
10
+ require_relative "adapter"
11
+
12
+ module Lutaml
13
+ module Toml
14
+ class Error < StandardError; end
15
+
16
+ # Convenience aliases for common classes at the module level
17
+ # Allows Lutaml::Toml::Mapping to resolve to Lutaml::Toml::Adapter::Mapping
18
+ def self.const_missing(name)
19
+ if Adapter.const_defined?(name, false)
20
+ Adapter.const_get(name, false)
21
+ else
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Lutaml::Model::FormatRegistry.register(
29
+ :toml,
30
+ mapping_class: Lutaml::Toml::Adapter::Mapping,
31
+ adapter_class: nil,
32
+ transformer: Lutaml::Toml::Adapter::Transform,
33
+ key_value: true,
34
+ adapter_options: if Lutaml::Model.opal?
35
+ nil
36
+ else
37
+ {
38
+ available: %i[tomlib toml_rb],
39
+ default: Lutaml::Model::RuntimeCompatibility.windows? ? :toml_rb : :tomlib,
40
+ }
41
+ end,
42
+ )
43
+
44
+ require_relative "type/serializers"
45
+ Lutaml::Toml::Type::Serializers.register_all!
46
+
47
+ # Auto-detection is now handled lazily by AdapterResolver on first use.
48
+ # No eager adapter selection at require time.
data/lib/lutaml/toml.rb CHANGED
@@ -1,48 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # TOML format module
4
- # Provides Lutaml::Toml namespace for TOML serialization
5
-
6
- require "lutaml/model/runtime_compatibility"
7
- require_relative "key_value"
8
- require_relative "toml/adapter"
9
-
10
- module Lutaml
11
- module Toml
12
- class Error < StandardError; end
13
-
14
- # Convenience aliases for common classes at the module level
15
- # Allows Lutaml::Toml::Mapping to resolve to Lutaml::Toml::Adapter::Mapping
16
- def self.const_missing(name)
17
- if Adapter.const_defined?(name, false)
18
- Adapter.const_get(name, false)
19
- else
20
- super
21
- end
22
- end
23
- end
24
- end
25
-
26
- # Register TOML format with the format registry
27
- Lutaml::Model::FormatRegistry.register(
28
- :toml,
29
- mapping_class: Lutaml::Toml::Adapter::Mapping,
30
- adapter_class: nil,
31
- transformer: Lutaml::Toml::Adapter::Transform,
32
- key_value: true,
33
- adapter_options: if Lutaml::Model.opal?
34
- nil
35
- else
36
- {
37
- available: %i[tomlib toml_rb],
38
- default: Lutaml::Model::RuntimeCompatibility.windows? ? :toml_rb : :tomlib,
39
- }
40
- end,
41
- )
42
-
43
- # Register TOML type serializers
44
- require_relative "toml/type/serializers"
45
- Lutaml::Toml::Type::Serializers.register_all!
46
-
47
- # Auto-detection is now handled lazily by AdapterResolver on first use.
48
- # No eager adapter selection at require time.
3
+ # Public TOML format entrypoint. Loads the base model and the internal
4
+ # implementation. The internal file does not require the model back,
5
+ # which keeps the require graph acyclic.
6
+ require_relative "model"
7
+ require_relative "toml/format"
@@ -215,8 +215,8 @@ module Lutaml
215
215
 
216
216
  private
217
217
 
218
- def attribute_values(element, &)
219
- element.attributes.each_value(&)
218
+ def attribute_values(element, &block)
219
+ element.attributes.each_value(&block)
220
220
  end
221
221
 
222
222
  def schema_location_attribute?(attr)
@@ -180,9 +180,9 @@ module Lutaml
180
180
  result
181
181
  end
182
182
 
183
- def method_missing(method_name, *args, &)
183
+ def method_missing(method_name, *args, &block)
184
184
  attrs = args.first.is_a?(Hash) ? args.first : {}
185
- create_and_add_element(method_name.to_s, attributes: attrs, &)
185
+ create_and_add_element(method_name.to_s, attributes: attrs, &block)
186
186
  end
187
187
 
188
188
  def respond_to_missing?(_method_name, _include_private = false)