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
@@ -38,7 +38,9 @@ DECL_RE = /autoload\s+:[A-Za-z0-9_]+\s*,\s*"([^"]+)"/
38
38
  # DECL_RE can match. We only merge when the line ends right after the
39
39
  # comma that follows `autoload :Name`.
40
40
  def join_multiline_autoloads(src)
41
- src.gsub(/(autoload\s+:[A-Za-z0-9_]+\s*,)\s*\n\s*/) { "#{Regexp.last_match(1)} " }
41
+ src.gsub(/(autoload\s+:[A-Za-z0-9_]+\s*,)\s*\n\s*/) do
42
+ "#{Regexp.last_match(1)} "
43
+ end
42
44
  end
43
45
 
44
46
  # Resolve an autoload's raw string argument to a require path relative to
@@ -21,22 +21,19 @@
21
21
 
22
22
  require "lutaml/model"
23
23
  require "lutaml/xml"
24
- require "lutaml/hash_format"
25
- require "lutaml/json"
26
- require "lutaml/jsonl"
27
24
  require "lutaml/jsonld"
28
- require "lutaml/key_value"
29
- require "lutaml/toml"
30
25
  require "lutaml/turtle"
31
- require "lutaml/yaml"
32
26
  require "lutaml/yamlld"
33
- require "lutaml/yamls"
34
27
  require "lutaml/hash_format/adapter"
28
+ require "lutaml/hash_format/format"
29
+ require "lutaml/json/format"
35
30
  require "lutaml/jsonl/adapter"
31
+ require "lutaml/jsonl/format"
36
32
  require "lutaml/jsonld/adapter"
37
33
  require "lutaml/key_value/adapter"
38
34
  require "lutaml/key_value/data_model"
39
35
  require "lutaml/key_value/document"
36
+ require "lutaml/key_value/format"
40
37
  require "lutaml/key_value/mapping"
41
38
  require "lutaml/key_value/mapping_rule"
42
39
  require "lutaml/key_value/transform"
@@ -103,6 +100,7 @@ require "lutaml/model/uninitialized_class"
103
100
  require "lutaml/model/union"
104
101
  require "lutaml/model/utils"
105
102
  require "lutaml/model/validation"
103
+ require "lutaml/model/value_policy"
106
104
  require "lutaml/model/value_transformer"
107
105
  require "lutaml/model/version"
108
106
  require "lutaml/model/yaml"
@@ -121,6 +119,7 @@ require "lutaml/rdf/namespace_set"
121
119
  require "lutaml/rdf/namespaces"
122
120
  require "lutaml/rdf/term_definition"
123
121
  require "lutaml/rdf/transform"
122
+ require "lutaml/toml/format"
124
123
  require "lutaml/turtle/adapter"
125
124
  require "lutaml/turtle/mapping"
126
125
  require "lutaml/turtle/transform"
@@ -143,6 +142,7 @@ require "lutaml/xml/document"
143
142
  require "lutaml/xml/element"
144
143
  require "lutaml/xml/element_prefix_resolver"
145
144
  require "lutaml/xml/encoding_normalizer"
145
+ require "lutaml/xml/format"
146
146
  require "lutaml/xml/format_chooser"
147
147
  require "lutaml/xml/hoisting_algorithm"
148
148
  require "lutaml/xml/listener"
@@ -164,6 +164,7 @@ require "lutaml/xml/namespace_scope_config"
164
164
  require "lutaml/xml/namespace_type_resolver"
165
165
  require "lutaml/xml/namespace_usage"
166
166
  require "lutaml/xml/oga"
167
+ require "lutaml/xml/parse_session"
167
168
  require "lutaml/xml/parsed_namespace_declaration"
168
169
  require "lutaml/xml/parsed_namespace_set"
169
170
  require "lutaml/xml/polymorphic_value_handler"
@@ -185,9 +186,11 @@ require "lutaml/xml/xml_attribute"
185
186
  require "lutaml/xml/xml_element"
186
187
  require "lutaml/xml/xsd_validator"
187
188
  require "lutaml/yaml/adapter"
189
+ require "lutaml/yaml/format"
188
190
  require "lutaml/yaml/schema"
189
191
  require "lutaml/yamlld/adapter"
190
192
  require "lutaml/yamls/adapter"
193
+ require "lutaml/yamls/format"
191
194
  require "lutaml/hash_format/adapter/document"
192
195
  require "lutaml/hash_format/adapter/mapping"
193
196
  require "lutaml/hash_format/adapter/mapping_rule"
@@ -34,6 +34,10 @@ if RUBY_ENGINE == "opal"
34
34
  require "moxml/doctype"
35
35
  require "moxml/entity_reference"
36
36
  require "moxml/entity_registry"
37
+ require "moxml/entity"
38
+ require "moxml/entity/restorer"
39
+ require "moxml/entity/reference"
40
+ require "moxml/xml_emitter"
37
41
  require "moxml/adapter"
38
42
  require "moxml/adapter/base"
39
43
  require "moxml/adapter/customized_rexml"
@@ -46,10 +50,11 @@ if RUBY_ENGINE == "opal"
46
50
  require "moxml/sax/namespace_splitter"
47
51
  require "moxml/adapter/rexml"
48
52
  require "moxml/adapter/customized_oga"
53
+ require "moxml/adapter/customized_oga/entity_decoder"
54
+ require "moxml/adapter/customized_oga/raw_value_override"
49
55
  require "moxml/adapter/customized_oga/xml_declaration"
50
56
  require "moxml/adapter/customized_oga/xml_generator"
51
57
  require "moxml/adapter/oga"
52
- require "moxml/document_builder"
53
58
  require "moxml/builder"
54
59
  require "moxml/context"
55
60
  require "moxml/config"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal Hash format implementation. Loaded from lib/lutaml/hash_format.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
+ module Lutaml
9
+ module HashFormat
10
+ class Error < StandardError; end
11
+
12
+ autoload :Adapter, "#{__dir__}/adapter"
13
+ end
14
+ end
15
+
16
+ Lutaml::Model::FormatRegistry.register(
17
+ :hash,
18
+ mapping_class: Lutaml::HashFormat::Adapter::Mapping,
19
+ adapter_class: Lutaml::HashFormat::Adapter::StandardAdapter,
20
+ transformer: Lutaml::HashFormat::Adapter::Transform,
21
+ key_value: true,
22
+ adapter_options: {
23
+ available: %i[standard standard_hash],
24
+ default: :standard,
25
+ },
26
+ )
27
+
28
+ require_relative "type/serializers"
29
+ Lutaml::HashFormat::Type::Serializers.register_all!
@@ -1,29 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Hash format module
4
- # Provides Lutaml::HashFormat namespace for Hash serialization
5
-
6
- module Lutaml
7
- module HashFormat
8
- class Error < StandardError; end
9
-
10
- autoload :Adapter, "#{__dir__}/hash_format/adapter"
11
- end
12
- end
13
-
14
- # Register Hash format with the format registry
15
- Lutaml::Model::FormatRegistry.register(
16
- :hash,
17
- mapping_class: Lutaml::HashFormat::Adapter::Mapping,
18
- adapter_class: Lutaml::HashFormat::Adapter::StandardAdapter,
19
- transformer: Lutaml::HashFormat::Adapter::Transform,
20
- key_value: true,
21
- adapter_options: {
22
- available: %i[standard standard_hash],
23
- default: :standard,
24
- },
25
- )
26
-
27
- # Register Hash type serializers
28
- require_relative "hash_format/type/serializers"
29
- Lutaml::HashFormat::Type::Serializers.register_all!
3
+ # Public Hash 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 "hash_format/format"
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal JSON format implementation. Loaded from lib/lutaml/json.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
+
11
+ module Lutaml
12
+ module Json
13
+ class Error < StandardError; end
14
+
15
+ require_relative "adapter/document"
16
+ require_relative "adapter/mapping"
17
+ require_relative "adapter/mapping_rule"
18
+ require_relative "adapter/transform"
19
+ require_relative "adapter/standard_adapter"
20
+ Lutaml::Model::RuntimeCompatibility.require_native(
21
+ "#{__dir__}/adapter/oj_adapter",
22
+ "#{__dir__}/adapter/multi_json_adapter",
23
+ )
24
+ require_relative "schema"
25
+
26
+ # Convenience aliases for common classes at the module level
27
+ # Allows Lutaml::Json::Mapping to resolve to Lutaml::Json::Adapter::Mapping
28
+ def self.const_missing(name)
29
+ if Adapter.const_defined?(name, false)
30
+ Adapter.const_get(name, false)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ # Detect available JSON adapters
37
+ def self.detect_adapter
38
+ return :standard if defined?(::JSON) && Lutaml::Model.opal?
39
+ return :oj if defined?(::Oj)
40
+ return :multi_json if defined?(::MultiJson)
41
+ return :standard if defined?(::JSON)
42
+
43
+ nil
44
+ end
45
+ end
46
+ end
47
+
48
+ Lutaml::Model::FormatRegistry.register(
49
+ :json,
50
+ mapping_class: Lutaml::Json::Adapter::Mapping,
51
+ adapter_class: Lutaml::Json::Adapter::StandardAdapter,
52
+ transformer: Lutaml::Json::Adapter::Transform,
53
+ key_value: true,
54
+ adapter_options: {
55
+ available: %i[standard standard_json multi_json oj],
56
+ default: :standard,
57
+ },
58
+ )
59
+
60
+ require_relative "type/serializers"
61
+ Lutaml::Json::Type::Serializers.register_all!
data/lib/lutaml/json.rb CHANGED
@@ -1,62 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # JSON format module
4
- # Provides Lutaml::Json namespace for JSON serialization
5
-
6
- require "lutaml/model/runtime_compatibility"
3
+ # Public JSON 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.
7
6
  require_relative "model"
8
- require_relative "key_value"
9
-
10
- module Lutaml
11
- module Json
12
- class Error < StandardError; end
13
-
14
- require_relative "json/adapter/document"
15
- require_relative "json/adapter/mapping"
16
- require_relative "json/adapter/mapping_rule"
17
- require_relative "json/adapter/transform"
18
- require_relative "json/adapter/standard_adapter"
19
- Lutaml::Model::RuntimeCompatibility.require_native(
20
- "#{__dir__}/json/adapter/oj_adapter",
21
- "#{__dir__}/json/adapter/multi_json_adapter",
22
- )
23
- require_relative "json/schema"
24
-
25
- # Convenience aliases for common classes at the module level
26
- # Allows Lutaml::Json::Mapping to resolve to Lutaml::Json::Adapter::Mapping
27
- def self.const_missing(name)
28
- if Adapter.const_defined?(name, false)
29
- Adapter.const_get(name, false)
30
- else
31
- super
32
- end
33
- end
34
-
35
- # Detect available JSON adapters
36
- def self.detect_adapter
37
- return :standard if defined?(::JSON) && Lutaml::Model.opal?
38
- return :oj if defined?(::Oj)
39
- return :multi_json if defined?(::MultiJson)
40
- return :standard if defined?(::JSON)
41
-
42
- nil
43
- end
44
- end
45
- end
46
-
47
- # Register JSON format with the format registry
48
- Lutaml::Model::FormatRegistry.register(
49
- :json,
50
- mapping_class: Lutaml::Json::Adapter::Mapping,
51
- adapter_class: Lutaml::Json::Adapter::StandardAdapter,
52
- transformer: Lutaml::Json::Adapter::Transform,
53
- key_value: true,
54
- adapter_options: {
55
- available: %i[standard standard_json multi_json oj],
56
- default: :standard,
57
- },
58
- )
59
-
60
- # Register JSON type serializers
61
- require_relative "json/type/serializers"
62
- Lutaml::Json::Type::Serializers.register_all!
7
+ require_relative "json/format"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal JSONL format implementation. Loaded from lib/lutaml/jsonl.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
+ module Lutaml
9
+ module Jsonl
10
+ class Error < StandardError; end
11
+
12
+ autoload :Adapter, "#{__dir__}/adapter"
13
+ end
14
+ end
15
+
16
+ Lutaml::Model::FormatRegistry.register(
17
+ :jsonl,
18
+ mapping_class: Lutaml::Jsonl::Adapter::Mapping,
19
+ adapter_class: Lutaml::Jsonl::Adapter::StandardAdapter,
20
+ transformer: Lutaml::Jsonl::Adapter::Transform,
21
+ key_value: true,
22
+ )
data/lib/lutaml/jsonl.rb CHANGED
@@ -1,21 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # JSONL format module
4
- # Provides Lutaml::Jsonl namespace for JSONL serialization
5
-
6
- module Lutaml
7
- module Jsonl
8
- class Error < StandardError; end
9
-
10
- autoload :Adapter, "#{__dir__}/jsonl/adapter"
11
- end
12
- end
13
-
14
- # Register JSONL format with the format registry
15
- Lutaml::Model::FormatRegistry.register(
16
- :jsonl,
17
- mapping_class: Lutaml::Jsonl::Adapter::Mapping,
18
- adapter_class: Lutaml::Jsonl::Adapter::StandardAdapter,
19
- transformer: Lutaml::Jsonl::Adapter::Transform,
20
- key_value: true,
21
- )
3
+ # Public JSONL 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 "jsonl/format"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal KeyValue implementation. Loaded from lib/lutaml/key_value.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
+ module Lutaml
9
+ module KeyValue
10
+ autoload :DataModel, "#{__dir__}/data_model"
11
+ autoload :Document, "#{__dir__}/document"
12
+ autoload :Mapping, "#{__dir__}/mapping"
13
+ autoload :MappingRule, "#{__dir__}/mapping_rule"
14
+ autoload :Transform, "#{__dir__}/transform"
15
+ autoload :Transformation, "#{__dir__}/transformation"
16
+ autoload :TransformationBuilder, "#{__dir__}/transformation_builder"
17
+ autoload :Adapter, "#{__dir__}/adapter"
18
+ end
19
+ end
20
+
21
+ # Register KeyValue transformation builders for all key-value formats
22
+ %i[json yaml toml hash].each do |format|
23
+ Lutaml::Model::TransformationRegistry.register_builder(
24
+ format, Lutaml::KeyValue::TransformationBuilder
25
+ )
26
+ end
@@ -249,7 +249,10 @@ format)
249
249
  value = apply_value_map(value, rule.value_map(:from, options), attr)
250
250
 
251
251
  if rule.has_custom_method_for_deserialization?
252
- return unless Lutaml::Model::Utils.present?(value)
252
+ # An empty value ("", [], {}) is a present value per the
253
+ # missing-values semantics and must reach the custom method;
254
+ # only nil (non-existent) skips it (lutaml-model#746).
255
+ return if value.nil?
253
256
 
254
257
  return rule.deserialize(instance, value, attributes, model_class)
255
258
  end
@@ -88,11 +88,9 @@ model_class: nil)
88
88
  # @param rule [CompiledRule] The compiled rule
89
89
  # @return [Boolean] true if the rule defines a nested model
90
90
  def nested_model?(rule)
91
- # rubocop:disable Style/IncludeOrExtend
92
91
  # Use include? instead of < because < returns nil (not false) for non-subclasses
93
92
  rule.attribute_type.is_a?(Class) &&
94
93
  rule.attribute_type.include?(Lutaml::Model::Serialize)
95
- # rubocop:enable Style/IncludeOrExtend
96
94
  end
97
95
 
98
96
  # Serialize a nested model to a hash representation.
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Lutaml
4
- module KeyValue
5
- autoload :DataModel, "#{__dir__}/key_value/data_model"
6
- autoload :Document, "#{__dir__}/key_value/document"
7
- autoload :Mapping, "#{__dir__}/key_value/mapping"
8
- autoload :MappingRule, "#{__dir__}/key_value/mapping_rule"
9
- autoload :Transform, "#{__dir__}/key_value/transform"
10
- autoload :Transformation, "#{__dir__}/key_value/transformation"
11
- autoload :TransformationBuilder,
12
- "#{__dir__}/key_value/transformation_builder"
13
- autoload :Adapter, "#{__dir__}/key_value/adapter"
14
- end
15
- end
16
-
3
+ # Public KeyValue 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.
17
6
  require_relative "model"
18
-
19
- # Register KeyValue transformation builders for all key-value formats
20
- %i[json yaml toml hash].each do |format|
21
- Lutaml::Model::TransformationRegistry.register_builder(
22
- format, Lutaml::KeyValue::TransformationBuilder
23
- )
24
- end
7
+ require_relative "key_value/format"
@@ -119,6 +119,17 @@ module Lutaml
119
119
  @resolver ||= GlobalContext.resolver
120
120
  end
121
121
 
122
+ # The value-shaping policy for this attribute. Constructed lazily so
123
+ # the collection/union/ref flags are settled; memoized because they
124
+ # are fixed after definition.
125
+ def value_policy
126
+ @value_policy ||= ValuePolicy.new(
127
+ collection: collection?,
128
+ union: union?,
129
+ reference: !@options[:ref_model_class].nil?,
130
+ )
131
+ end
132
+
122
133
  def type(context_or_register = nil)
123
134
  return if unresolved_type.nil?
124
135
 
@@ -394,7 +405,9 @@ instance_object = nil)
394
405
  instance_object)
395
406
  elsif options[:default].is_a?(Proc)
396
407
  if instance_object
397
- instance_object.instance_exec(&options[:default])
408
+ ::Lutaml::Model::Attribute.evaluating_default do
409
+ instance_object.instance_exec(&options[:default])
410
+ end
398
411
  else
399
412
  options[:default].call
400
413
  end
@@ -405,6 +418,22 @@ instance_object = nil)
405
418
  end
406
419
  end
407
420
 
421
+ # Marks the dynamic extent in which a default proc runs against a
422
+ # model instance, so Serialize#method_missing can fall back to the
423
+ # model class for methods the instance does not define (0.7.x
424
+ # compatibility, lutaml-model#745).
425
+ def self.evaluating_default
426
+ previous = Thread.current[:__lutaml_evaluating_default]
427
+ Thread.current[:__lutaml_evaluating_default] = true
428
+ yield
429
+ ensure
430
+ Thread.current[:__lutaml_evaluating_default] = previous
431
+ end
432
+
433
+ def self.default_evaluation?
434
+ Thread.current[:__lutaml_evaluating_default] == true
435
+ end
436
+
408
437
  def default_set?(register, instance_object = nil)
409
438
  !Utils.uninitialized?(default_value(register, instance_object))
410
439
  end
@@ -478,7 +507,10 @@ instance_object = nil)
478
507
  # Use the default value if the value is nil
479
508
  validate_required!(value)
480
509
 
481
- value = cast_value(default_value(register, instance_object), register) if value.nil?
510
+ if value.nil?
511
+ value = cast_value(default_value(register, instance_object),
512
+ register)
513
+ end
482
514
  resolved_type = type(register)
483
515
 
484
516
  valid_value!(value) &&
@@ -551,7 +583,8 @@ instance_object = nil)
551
583
  end
552
584
 
553
585
  def valid_collection!(value, caller)
554
- if collection_instance?(value) && !collection?
586
+ if collection_instance?(value) && !collection? &&
587
+ !value_policy.whole_value?(unresolved_type)
555
588
  raise Lutaml::Model::CollectionTrueMissingError.new(name,
556
589
  caller)
557
590
  end
@@ -632,7 +665,8 @@ instance_object = nil)
632
665
  else
633
666
  type(register)
634
667
  end
635
- if collection_instance?(value) || value.is_a?(Array)
668
+ if (collection_instance?(value) || value.is_a?(Array)) &&
669
+ !value_policy.whole_value?(resolved_type)
636
670
  merged_opts = options.merge(resolved_type: resolved_type,
637
671
  converted: true)
638
672
  return build_collection(value.map do |v|
@@ -481,8 +481,8 @@ lutaml_register: Lutaml::Model::Config.default_register)
481
481
  self.class.new(items - other.items)
482
482
  end
483
483
 
484
- def each(&)
485
- collection.each(&)
484
+ def each(&block)
485
+ collection.each(&block)
486
486
  end
487
487
 
488
488
  def size
@@ -192,7 +192,7 @@ module Lutaml
192
192
  # @param args [Array] Arguments (ignored for options)
193
193
  # @param block [Proc] Block (ignored for options)
194
194
  # @return [Object] The option value or nil
195
- def method_missing(method_name, *args, &)
195
+ def method_missing(method_name, *args, &block)
196
196
  # Check if this is an option key
197
197
  if options.key?(method_name)
198
198
  return options[method_name]
@@ -42,8 +42,8 @@ module Lutaml
42
42
  # Config.with_adapter(xml: :nokogiri, toml: :tomlib) do
43
43
  # MyModel.from_xml(data)
44
44
  # end
45
- def with_adapter(**overrides, &)
46
- AdapterScope.with(overrides, &)
45
+ def with_adapter(**overrides, &block)
46
+ AdapterScope.with(overrides, &block)
47
47
  end
48
48
 
49
49
  # Delegate configure to Configuration
@@ -39,8 +39,8 @@ module Lutaml
39
39
  end
40
40
 
41
41
  # Pattern A: declare discriminator routing
42
- def dispatch_by(discriminator, &)
43
- routes = DispatchBuilder.new.evaluate(&)
42
+ def dispatch_by(discriminator, &block)
43
+ routes = DispatchBuilder.new.evaluate(&block)
44
44
  @rules << DispatchBlock.new(discriminator, routes)
45
45
  end
46
46
 
@@ -20,9 +20,9 @@ module Lutaml
20
20
 
21
21
  # Helper builder for the dispatch_by block
22
22
  class DispatchBuilder
23
- def evaluate(&)
23
+ def evaluate(&block)
24
24
  @routes = {}
25
- instance_eval(&)
25
+ instance_eval(&block)
26
26
  @routes
27
27
  end
28
28
 
@@ -18,8 +18,8 @@ module Lutaml
18
18
  @errors.empty?
19
19
  end
20
20
 
21
- def each(&)
22
- @errors.each(&)
21
+ def each(&block)
22
+ @errors.each(&block)
23
23
  end
24
24
 
25
25
  def full_messages
@@ -436,8 +436,10 @@ context_id = nil)
436
436
  instance.register_for_namespace(namespace_uri)
437
437
  end
438
438
 
439
- def resolve_type_with_namespace(type_name, namespace_uri = nil, context_id = nil)
440
- instance.resolve_type_with_namespace(type_name, namespace_uri, context_id)
439
+ def resolve_type_with_namespace(type_name, namespace_uri = nil,
440
+ context_id = nil)
441
+ instance.resolve_type_with_namespace(type_name, namespace_uri,
442
+ context_id)
441
443
  end
442
444
 
443
445
  def namespace_register_map
@@ -67,6 +67,9 @@ module Lutaml
67
67
  )
68
68
  @name = name
69
69
  @to = to
70
+ # Dynamic symbol built per call allocated ~10% of parse objects on
71
+ # attribute-heavy documents; `to` is immutable, so build it once.
72
+ @setter_name = :"#{to}="
70
73
  @to_instance = to_instance
71
74
  @as_attribute = as_attribute
72
75
  @render_nil = render_nil
@@ -457,7 +460,7 @@ module Lutaml
457
460
  end
458
461
 
459
462
  def assign_value(model, value)
460
- model.public_send(:"#{to}=", value)
463
+ model.public_send(@setter_name, value)
461
464
  end
462
465
  end
463
466
  end
@@ -43,7 +43,7 @@ module Lutaml
43
43
  to: nil,
44
44
  transform: nil,
45
45
  reverse_transform: nil,
46
- &
46
+ &block
47
47
  )
48
48
  map(
49
49
  from: from,
@@ -51,7 +51,7 @@ module Lutaml
51
51
  transform: transform,
52
52
  reverse_transform: reverse_transform,
53
53
  collection: true,
54
- &
54
+ &block
55
55
  )
56
56
  end
57
57
 
@@ -114,8 +114,8 @@ if Lutaml::Model::RuntimeCompatibility.opal?
114
114
 
115
115
  # Minimal method_missing so the WeakRef quacks like its target
116
116
  # for the Store's index lookups.
117
- def method_missing(name, *, &)
118
- @__target__.public_send(name, *, &)
117
+ def method_missing(name, ...)
118
+ @__target__.public_send(name, ...)
119
119
  end
120
120
 
121
121
  def respond_to_missing?(name, include_private = false)
@@ -36,7 +36,8 @@ module Lutaml
36
36
 
37
37
  def load
38
38
  Dir.mktmpdir do |dir|
39
- FileWriter.write(@output, dir, registry_generator: @registry_generator)
39
+ FileWriter.write(@output, dir,
40
+ registry_generator: @registry_generator)
40
41
  require File.join(dir, "#{registry_basename}_registry")
41
42
  call_register_all
42
43
  end