lutaml-model 0.8.19 → 0.8.23
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.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-tests.yml +1 -1
- data/.github/workflows/rake.yml +1 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +133 -72
- data/bench/bench_common.rb +8 -5
- data/bench/bench_niso.rb +1 -2
- data/bench/gate_config.rb +4 -1
- data/benchmark/serialization_benchmark.rb +4 -4
- data/docs/_guides/value-transformations.adoc +27 -0
- data/docs/_guides/xml-mapping.adoc +31 -0
- data/lib/compat/opal/generate_boot.rb +3 -1
- data/lib/compat/opal/lutaml_model_boot.rb +10 -7
- data/lib/compat/opal/moxml_boot.rb +6 -1
- data/lib/lutaml/hash_format/format.rb +29 -0
- data/lib/lutaml/hash_format.rb +5 -27
- data/lib/lutaml/json/adapter/oj_adapter.rb +7 -0
- data/lib/lutaml/json/adapter/standard_adapter.rb +17 -5
- data/lib/lutaml/json/format.rb +61 -0
- data/lib/lutaml/json/generator_options.rb +62 -0
- data/lib/lutaml/json.rb +4 -59
- data/lib/lutaml/jsonl/adapter/standard_adapter.rb +1 -1
- data/lib/lutaml/jsonl/format.rb +22 -0
- data/lib/lutaml/jsonl.rb +5 -19
- data/lib/lutaml/jsonld/adapter.rb +12 -4
- data/lib/lutaml/key_value/adapter/json/oj_adapter.rb +7 -0
- data/lib/lutaml/key_value/adapter/json/standard_adapter.rb +17 -5
- data/lib/lutaml/key_value/adapter/jsonl/standard_adapter.rb +1 -1
- data/lib/lutaml/key_value/format.rb +26 -0
- data/lib/lutaml/key_value/transform.rb +4 -1
- data/lib/lutaml/key_value/transformation/value_serializer.rb +0 -2
- data/lib/lutaml/key_value.rb +4 -21
- data/lib/lutaml/model/attribute.rb +38 -4
- data/lib/lutaml/model/collection.rb +7 -2
- data/lib/lutaml/model/compiled_rule.rb +1 -1
- data/lib/lutaml/model/config.rb +2 -2
- data/lib/lutaml/model/consolidation_map.rb +2 -2
- data/lib/lutaml/model/consolidation_rule/dispatch_block.rb +2 -2
- data/lib/lutaml/model/errors.rb +2 -2
- data/lib/lutaml/model/global_context.rb +4 -2
- data/lib/lutaml/model/mapping/mapping_rule.rb +4 -1
- data/lib/lutaml/model/mapping/model_mapping.rb +2 -2
- data/lib/lutaml/model/runtime_compatibility.rb +2 -2
- data/lib/lutaml/model/schema/class_loader.rb +2 -1
- data/lib/lutaml/model/schema/decorators/definition_collection.rb +4 -4
- data/lib/lutaml/model/schema/definitions/namespace.rb +2 -1
- data/lib/lutaml/model/schema/file_writer.rb +5 -2
- data/lib/lutaml/model/schema/renderers/base.rb +4 -3
- data/lib/lutaml/model/schema/renderers/mappings.rb +9 -3
- data/lib/lutaml/model/schema/renderers/member_decls.rb +9 -3
- data/lib/lutaml/model/schema/renderers/model.rb +18 -6
- data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +2 -1
- data/lib/lutaml/model/schema/renderers.rb +2 -1
- data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +6 -2
- data/lib/lutaml/model/schema/rnc_compiler.rb +3 -1
- data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +35 -15
- data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +4 -2
- data/lib/lutaml/model/schema/rng_compiler.rb +10 -5
- data/lib/lutaml/model/schema/xml_compiler/element_order.rb +2 -1
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +4 -2
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +39 -13
- data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +29 -10
- data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +2 -2
- data/lib/lutaml/model/schema/xml_compiler.rb +10 -5
- data/lib/lutaml/model/sequence.rb +4 -4
- data/lib/lutaml/model/serialization_adapter.rb +1 -1
- data/lib/lutaml/model/serialize/attribute_definition.rb +1 -0
- data/lib/lutaml/model/serialize/format_conversion.rb +40 -9
- data/lib/lutaml/model/serialize/initialization.rb +9 -1
- data/lib/lutaml/model/serialize/model_import.rb +1 -0
- data/lib/lutaml/model/serialize.rb +22 -3
- data/lib/lutaml/model/transformation.rb +5 -0
- data/lib/lutaml/model/type/uninitialized_class_guard.rb +1 -1
- data/lib/lutaml/model/type.rb +9 -0
- data/lib/lutaml/model/type_registry.rb +2 -2
- data/lib/lutaml/model/uninitialized_class.rb +1 -1
- data/lib/lutaml/model/union.rb +5 -2
- data/lib/lutaml/model/utils.rb +2 -2
- data/lib/lutaml/model/value_policy.rb +27 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +17 -16
- data/lib/lutaml/rdf/mapping.rb +2 -2
- data/lib/lutaml/rdf/namespace_set.rb +2 -2
- data/lib/lutaml/rdf/transform.rb +2 -2
- data/lib/lutaml/toml/format.rb +48 -0
- data/lib/lutaml/toml.rb +5 -46
- data/lib/lutaml/xml/adapter/base_adapter.rb +2 -2
- data/lib/lutaml/xml/builder/base.rb +2 -2
- data/lib/lutaml/xml/format.rb +331 -0
- data/lib/lutaml/xml/mapping.rb +26 -2
- data/lib/lutaml/xml/mapping_rule.rb +62 -3
- data/lib/lutaml/xml/model_transform.rb +183 -55
- data/lib/lutaml/xml/namespace_collector.rb +3 -3
- data/lib/lutaml/xml/parse_session.rb +59 -0
- data/lib/lutaml/xml/parsed_namespace_set.rb +2 -2
- data/lib/lutaml/xml/schema/builder.rb +5 -5
- data/lib/lutaml/xml/schema/xsd/schema_location_mapping.rb +2 -2
- data/lib/lutaml/xml/schema/xsd.rb +0 -2
- data/lib/lutaml/xml/schema/xsd_schema.rb +2 -1
- data/lib/lutaml/xml/serialization/format_conversion.rb +3 -3
- data/lib/lutaml/xml/transformation/element_builder.rb +2 -1
- data/lib/lutaml/xml/transformation/ordered_applier.rb +2 -1
- data/lib/lutaml/xml/transformation.rb +29 -0
- data/lib/lutaml/xml/xml_element.rb +2 -0
- data/lib/lutaml/xml.rb +4 -328
- data/lib/lutaml/yaml/format.rb +30 -0
- data/lib/lutaml/yaml.rb +5 -28
- data/lib/lutaml/yamls/adapter/mapping.rb +2 -2
- data/lib/lutaml/yamls/format.rb +22 -0
- data/lib/lutaml/yamls.rb +5 -19
- data/lib/tasks/benchmark_runner.rb +2 -2
- data/lutaml-model.gemspec +1 -1
- data/spec/lutaml/json/adapter/standard_adapter_spec.rb +159 -0
- data/spec/lutaml/jsonld/adapter_spec.rb +19 -0
- data/spec/lutaml/model/attribute_apply_value_map_spec.rb +26 -13
- data/spec/lutaml/model/attribute_spec.rb +8 -3
- data/spec/lutaml/model/collection_spec.rb +1 -1
- data/spec/lutaml/model/collection_state_nesting_spec.rb +44 -0
- data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +2 -2
- data/spec/lutaml/model/custom_from_empty_values_spec.rb +74 -0
- data/spec/lutaml/model/custom_vobject_adapter_spec.rb +8 -8
- data/spec/lutaml/model/defaults_spec.rb +45 -0
- data/spec/lutaml/model/generator_defaults_spec.rb +101 -0
- data/spec/lutaml/model/json_spec.rb +1 -2
- data/spec/lutaml/model/json_state_nesting_spec.rb +82 -0
- data/spec/lutaml/model/serialize/builder_spec.rb +6 -3
- data/spec/lutaml/model/store_spec.rb +3 -1
- data/spec/lutaml/model/transform_apply_value_map_spec.rb +6 -3
- data/spec/lutaml/model/union_attribute_spec.rb +4 -2
- data/spec/lutaml/model/value_policy_spec.rb +82 -0
- data/spec/lutaml/require_load_order_spec.rb +25 -0
- data/spec/lutaml/xml/attribute_element_namespace_disjoint_spec.rb +88 -0
- data/spec/lutaml/xml/attribute_namespace_disjointness_spec.rb +125 -0
- data/spec/lutaml/xml/out_of_namespace_parsing_spec.rb +123 -0
- data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +17 -6
- data/spec/lutaml/xml/schema/rng_compiler_spec.rb +15 -6
- data/spec/lutaml/xml/shared_element_rules_spec.rb +64 -0
- data/spec/lutaml/xml/w3c_types_spec.rb +3 -6
- metadata +26 -4
|
@@ -29,7 +29,8 @@ module Lutaml
|
|
|
29
29
|
array.each_with_index do |element, i|
|
|
30
30
|
next unless element == builder
|
|
31
31
|
|
|
32
|
-
array[i] =
|
|
32
|
+
array[i] =
|
|
33
|
+
Array(object.public_send(Utils.snake_case(builder.name)))[index]
|
|
33
34
|
index += 1
|
|
34
35
|
end
|
|
35
36
|
end
|
|
@@ -25,7 +25,8 @@ module Lutaml
|
|
|
25
25
|
def build(complex_type)
|
|
26
26
|
model = Definitions::Model.new(
|
|
27
27
|
class_name: Utils.camel_case(complex_type.name),
|
|
28
|
-
xml_root: Definitions::XmlRoot.new(kind: :element,
|
|
28
|
+
xml_root: Definitions::XmlRoot.new(kind: :element,
|
|
29
|
+
name: complex_type.name),
|
|
29
30
|
mixed: !!complex_type.mixed,
|
|
30
31
|
namespace_class_name: @parent.namespace_class_name,
|
|
31
32
|
)
|
|
@@ -49,7 +50,8 @@ module Lutaml
|
|
|
49
50
|
base_name = group.name || Utils.last_of_split(group.ref)
|
|
50
51
|
Definitions::Model.new(
|
|
51
52
|
class_name: Utils.camel_case(base_name),
|
|
52
|
-
xml_root: Definitions::XmlRoot.new(kind: :type_name,
|
|
53
|
+
xml_root: Definitions::XmlRoot.new(kind: :type_name,
|
|
54
|
+
name: base_name),
|
|
53
55
|
members: inner_members,
|
|
54
56
|
module_wrappable: false,
|
|
55
57
|
lazy_register: true,
|
|
@@ -56,7 +56,8 @@ module Lutaml
|
|
|
56
56
|
type: build_type_ref(resolve_element_type(element)),
|
|
57
57
|
xml_name: element.name,
|
|
58
58
|
kind: :element,
|
|
59
|
-
collection: collection_from_occurs(element.min_occurs,
|
|
59
|
+
collection: collection_from_occurs(element.min_occurs,
|
|
60
|
+
element.max_occurs),
|
|
60
61
|
default: element.default,
|
|
61
62
|
render_default: !element.default.nil?,
|
|
62
63
|
render_empty: element_required?(element.min_occurs),
|
|
@@ -75,7 +76,9 @@ module Lutaml
|
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
def build_choice(choice)
|
|
78
|
-
alternatives = ElementOrder.resolved(choice).filter_map
|
|
79
|
+
alternatives = ElementOrder.resolved(choice).filter_map do |item|
|
|
80
|
+
build_member(item)
|
|
81
|
+
end
|
|
79
82
|
Definitions::Choice.new(
|
|
80
83
|
alternatives: alternatives,
|
|
81
84
|
header: choice_header(choice),
|
|
@@ -97,11 +100,18 @@ module Lutaml
|
|
|
97
100
|
# ----- TypeRef ---------------------------------------------------
|
|
98
101
|
|
|
99
102
|
def build_type_ref(raw_type)
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
if raw_type.nil?
|
|
104
|
+
return Definitions::TypeRef.new(kind: :symbol,
|
|
105
|
+
value: "string")
|
|
106
|
+
end
|
|
107
|
+
if w3c_type?(raw_type)
|
|
108
|
+
return Definitions::TypeRef.new(kind: :w3c,
|
|
109
|
+
value: raw_type)
|
|
110
|
+
end
|
|
102
111
|
|
|
103
112
|
local = Utils.last_of_split(raw_type)
|
|
104
|
-
Definitions::TypeRef.new(kind: :symbol,
|
|
113
|
+
Definitions::TypeRef.new(kind: :symbol,
|
|
114
|
+
value: Utils.snake_case(local))
|
|
105
115
|
end
|
|
106
116
|
|
|
107
117
|
private
|
|
@@ -131,7 +141,8 @@ module Lutaml
|
|
|
131
141
|
def resolve_attribute_type(attr)
|
|
132
142
|
return attr.type if attr.type
|
|
133
143
|
|
|
134
|
-
register_anonymous_simple_type("ST_#{attr.name}",
|
|
144
|
+
register_anonymous_simple_type("ST_#{attr.name}",
|
|
145
|
+
attr.simple_type)
|
|
135
146
|
end
|
|
136
147
|
|
|
137
148
|
def resolve_element_ref(element)
|
|
@@ -143,7 +154,8 @@ module Lutaml
|
|
|
143
154
|
type: target.type,
|
|
144
155
|
xml_name: target.xml_name,
|
|
145
156
|
kind: :element,
|
|
146
|
-
collection: collection_from_occurs(element.min_occurs,
|
|
157
|
+
collection: collection_from_occurs(element.min_occurs,
|
|
158
|
+
element.max_occurs),
|
|
147
159
|
default: target.default,
|
|
148
160
|
render_default: !target.default.nil?,
|
|
149
161
|
render_empty: element_required?(element.min_occurs),
|
|
@@ -152,28 +164,42 @@ module Lutaml
|
|
|
152
164
|
|
|
153
165
|
def resolve_element_type(element)
|
|
154
166
|
return element.type if element.type
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
if element.simple_type
|
|
168
|
+
return register_anonymous_simple_type("ST_#{element.name}",
|
|
169
|
+
element.simple_type)
|
|
170
|
+
end
|
|
171
|
+
if element.complex_type
|
|
172
|
+
return register_anonymous_complex_type("CT_#{element.name}",
|
|
173
|
+
element.complex_type)
|
|
174
|
+
end
|
|
157
175
|
|
|
158
176
|
"string"
|
|
159
177
|
end
|
|
160
178
|
|
|
161
179
|
def top_level_type_str(item)
|
|
162
180
|
return item.type if item.respond_to?(:type) && item.type
|
|
163
|
-
|
|
181
|
+
if item.respond_to?(:simple_type) && item.simple_type
|
|
182
|
+
return register_anonymous_simple_type("ST_#{item.name}",
|
|
183
|
+
item.simple_type)
|
|
184
|
+
end
|
|
164
185
|
|
|
165
|
-
|
|
186
|
+
if item.respond_to?(:complex_type) && item.complex_type
|
|
187
|
+
register_anonymous_complex_type("CT_#{item.name}",
|
|
188
|
+
item.complex_type)
|
|
189
|
+
end
|
|
166
190
|
end
|
|
167
191
|
|
|
168
192
|
def register_anonymous_simple_type(anon_name, anon_node)
|
|
169
193
|
anon_node.name = anon_name
|
|
170
|
-
@parent.simple_types[anon_name] =
|
|
194
|
+
@parent.simple_types[anon_name] =
|
|
195
|
+
@parent.build_simple_type(anon_node)
|
|
171
196
|
anon_name
|
|
172
197
|
end
|
|
173
198
|
|
|
174
199
|
def register_anonymous_complex_type(anon_name, anon_node)
|
|
175
200
|
anon_node.name = anon_name
|
|
176
|
-
@parent.complex_types[anon_name] =
|
|
201
|
+
@parent.complex_types[anon_name] =
|
|
202
|
+
@parent.build_complex_type(anon_node)
|
|
177
203
|
anon_name
|
|
178
204
|
end
|
|
179
205
|
|
|
@@ -65,7 +65,9 @@ module Lutaml
|
|
|
65
65
|
# passed :namespace, every generated complex type gets this
|
|
66
66
|
# name. Mirrors XmlCompiler::ComplexType#setup_options on main.
|
|
67
67
|
@namespace_class_name =
|
|
68
|
-
requested_uri && @namespace_classes.values.find
|
|
68
|
+
requested_uri && @namespace_classes.values.find do |ns|
|
|
69
|
+
ns.uri == requested_uri
|
|
70
|
+
end&.class_name
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
def walk_schemas(schemas)
|
|
@@ -79,11 +81,15 @@ module Lutaml
|
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
def collect_lookups(schemas)
|
|
82
|
-
each_schema_item(schemas)
|
|
84
|
+
each_schema_item(schemas) do |item, schema|
|
|
85
|
+
dispatch_lookup(item, schema)
|
|
86
|
+
end
|
|
83
87
|
end
|
|
84
88
|
|
|
85
89
|
def build_complex_types(schemas)
|
|
86
|
-
each_schema_item(schemas)
|
|
90
|
+
each_schema_item(schemas) do |item, schema|
|
|
91
|
+
dispatch_complex(item, schema)
|
|
92
|
+
end
|
|
87
93
|
end
|
|
88
94
|
|
|
89
95
|
def dispatch_lookup(item, schema)
|
|
@@ -91,13 +97,17 @@ module Lutaml
|
|
|
91
97
|
when Lutaml::Xml::Schema::Xsd::SimpleType
|
|
92
98
|
@simple_types[item.name] = build_simple_type(item)
|
|
93
99
|
when Lutaml::Xml::Schema::Xsd::Element
|
|
94
|
-
@elements[item.name] =
|
|
100
|
+
@elements[item.name] =
|
|
101
|
+
@members_builder.build_top_level_attribute(item, kind: :element)
|
|
95
102
|
when Lutaml::Xml::Schema::Xsd::Attribute
|
|
96
103
|
return if xml_defined_attribute?(schema, item.name)
|
|
97
104
|
|
|
98
|
-
@attributes[item.name] =
|
|
105
|
+
@attributes[item.name] =
|
|
106
|
+
@members_builder.build_top_level_attribute(item,
|
|
107
|
+
kind: :attribute)
|
|
99
108
|
when Lutaml::Xml::Schema::Xsd::AttributeGroup
|
|
100
|
-
@attribute_groups[item.name] =
|
|
109
|
+
@attribute_groups[item.name] =
|
|
110
|
+
@complex_types_builder.build_attribute_group(item)
|
|
101
111
|
end
|
|
102
112
|
end
|
|
103
113
|
|
|
@@ -117,7 +127,8 @@ module Lutaml
|
|
|
117
127
|
next if info[:skippable]
|
|
118
128
|
|
|
119
129
|
str_name = name.to_s
|
|
120
|
-
@simple_types[str_name] =
|
|
130
|
+
@simple_types[str_name] =
|
|
131
|
+
@simple_types_builder.build_supported(str_name, info)
|
|
121
132
|
end
|
|
122
133
|
end
|
|
123
134
|
|
|
@@ -141,8 +152,13 @@ module Lutaml
|
|
|
141
152
|
# Public surface used by the Members sub-builder to register
|
|
142
153
|
# nested anonymous types it encounters during attribute /
|
|
143
154
|
# element resolution.
|
|
144
|
-
def build_simple_type(simple_type)
|
|
145
|
-
|
|
155
|
+
def build_simple_type(simple_type)
|
|
156
|
+
@simple_types_builder.build(simple_type)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def build_complex_type(complex_type)
|
|
160
|
+
@complex_types_builder.build(complex_type)
|
|
161
|
+
end
|
|
146
162
|
|
|
147
163
|
private
|
|
148
164
|
|
|
@@ -150,7 +166,10 @@ module Lutaml
|
|
|
150
166
|
# yields each resolved element-order item with its owning schema.
|
|
151
167
|
def each_schema_item(schemas, &dispatch)
|
|
152
168
|
schemas.each do |schema|
|
|
153
|
-
|
|
169
|
+
if schema.include&.any?
|
|
170
|
+
each_schema_item(schema.include,
|
|
171
|
+
&dispatch)
|
|
172
|
+
end
|
|
154
173
|
each_schema_item(schema.import, &dispatch) if schema.import&.any?
|
|
155
174
|
schema.resolved_element_order.each { |item| yield(item, schema) }
|
|
156
175
|
end
|
|
@@ -9,10 +9,12 @@ module Lutaml
|
|
|
9
9
|
module XmlCompiler
|
|
10
10
|
extend self
|
|
11
11
|
|
|
12
|
-
autoload :SpecBuilder,
|
|
13
|
-
autoload :RegistryGenerator,
|
|
14
|
-
|
|
15
|
-
autoload :
|
|
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
|
-
|
|
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)
|
|
@@ -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)
|
|
@@ -192,24 +192,55 @@ module Lutaml
|
|
|
192
192
|
# is always preserved when available, regardless of this option.
|
|
193
193
|
# @return [String] The serialized output
|
|
194
194
|
def to(format, instance, options = {})
|
|
195
|
+
# Ruby's JSON generator hands #to_json its own JSON::State rather
|
|
196
|
+
# than an options hash. It carries no LutaML options, but it does
|
|
197
|
+
# carry the surrounding indent context, so it is forwarded to the
|
|
198
|
+
# adapter unchanged instead of being read like a Hash -- json 3.0
|
|
199
|
+
# removed JSON::State#[].
|
|
200
|
+
options = Serialize.wrap_generator_state(options)
|
|
201
|
+
generator_state = options.delete(Serialize::GENERATOR_STATE_KEY)
|
|
202
|
+
|
|
195
203
|
Instrumentation.instrument(:to, model: name, format: format) do
|
|
196
|
-
adapter_override = options.
|
|
197
|
-
if adapter_override
|
|
198
|
-
options[:_adapter_override] =
|
|
199
|
-
true
|
|
200
|
-
end
|
|
204
|
+
adapter_override = options.delete(:adapter)
|
|
205
|
+
options[:_adapter_override] = true if adapter_override
|
|
201
206
|
value = public_send(:"as_#{format}", instance, options)
|
|
202
207
|
adapter = resolve_adapter(format, adapter_override)
|
|
203
208
|
|
|
204
209
|
# Hook for format-specific options preparation (e.g., XML prefix/namespace/declaration)
|
|
205
210
|
options = prepare_to_options(format, instance, options)
|
|
206
211
|
|
|
207
|
-
adapter.new(value, register: options[:register])
|
|
208
|
-
|
|
212
|
+
document = adapter.new(value, register: options[:register])
|
|
213
|
+
|
|
214
|
+
document.public_send(
|
|
215
|
+
:"to_#{format}",
|
|
216
|
+
forward_options(document, generator_state, options),
|
|
209
217
|
)
|
|
210
218
|
end
|
|
211
219
|
end
|
|
212
220
|
|
|
221
|
+
# Main's behaviour differs per ADAPTER, not per option, so this follows
|
|
222
|
+
# the adapter rather than trying to translate option names:
|
|
223
|
+
# stdlib honours script_safe / ascii_only / pretty -> give it the state
|
|
224
|
+
# Oj ignores them all and uses its own escape_mode -> give it none
|
|
225
|
+
# others reach the stdlib generator underneath -> give them the options
|
|
226
|
+
def forward_options(document, generator_state, options)
|
|
227
|
+
return options if generator_state.nil?
|
|
228
|
+
|
|
229
|
+
if declares?(document, :accepts_generator_state?)
|
|
230
|
+
generator_state
|
|
231
|
+
elsif declares?(document, :ignores_generator_options?)
|
|
232
|
+
options
|
|
233
|
+
elsif generator_state.respond_to?(:to_h)
|
|
234
|
+
options.merge(generator_state.to_h)
|
|
235
|
+
else
|
|
236
|
+
options
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def declares?(document, predicate)
|
|
241
|
+
document.respond_to?(predicate) && document.public_send(predicate)
|
|
242
|
+
end
|
|
243
|
+
|
|
213
244
|
# Hook for format-specific options preparation before serialization.
|
|
214
245
|
# XML overrides to handle prefix, namespace overrides, declaration plan.
|
|
215
246
|
#
|
|
@@ -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
|
-
|
|
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|
|
|
@@ -175,18 +175,19 @@ module Lutaml
|
|
|
175
175
|
@using_default[attribute_name]
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
|
|
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)) ||
|
|
@@ -224,7 +225,25 @@ module Lutaml
|
|
|
224
225
|
self.class.as_yaml(self)
|
|
225
226
|
end
|
|
226
227
|
|
|
228
|
+
# Ruby's JSON generator hands #to_json its own JSON::State instead of an
|
|
229
|
+
# options hash. It carries no LutaML options, but it does carry the
|
|
230
|
+
# surrounding indent context, so it travels IN BAND under a private key
|
|
231
|
+
# rather than replacing the options hash. Wrapping rather than returning
|
|
232
|
+
# early keeps every later step -- register propagation, root-mapping
|
|
233
|
+
# validation, Collection's `collection: true` merge -- working on a real
|
|
234
|
+
# Hash. json 3.0 removed JSON::State#[] and rejects unknown keys in
|
|
235
|
+
# State#merge, so nothing may treat it as a Hash.
|
|
236
|
+
GENERATOR_STATE_KEY = :_generator_state
|
|
237
|
+
|
|
238
|
+
def self.wrap_generator_state(options)
|
|
239
|
+
return options if options.is_a?(::Hash)
|
|
240
|
+
|
|
241
|
+
{ GENERATOR_STATE_KEY => options }
|
|
242
|
+
end
|
|
243
|
+
|
|
227
244
|
def to_format(format, options = {})
|
|
245
|
+
options = Lutaml::Model::Serialize.wrap_generator_state(options)
|
|
246
|
+
|
|
228
247
|
# Hook for format-specific validation (e.g., XML root mapping check)
|
|
229
248
|
validate_root_mapping!(format, options)
|
|
230
249
|
|
|
@@ -37,9 +37,14 @@ module Lutaml
|
|
|
37
37
|
@format = format
|
|
38
38
|
@register = register
|
|
39
39
|
@compiled_rules = compile_rules(mapping_dsl)
|
|
40
|
+
after_compile
|
|
40
41
|
freeze
|
|
41
42
|
end
|
|
42
43
|
|
|
44
|
+
# Hook for format subclasses to derive rule-set-level data after
|
|
45
|
+
# compilation, before the transformation is frozen and shared.
|
|
46
|
+
def after_compile; end
|
|
47
|
+
|
|
43
48
|
# Transform a model instance into format-specific representation
|
|
44
49
|
#
|
|
45
50
|
# @abstract Subclasses must implement this method
|
|
@@ -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
|
|
data/lib/lutaml/model/type.rb
CHANGED
|
@@ -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.
|
data/lib/lutaml/model/union.rb
CHANGED
|
@@ -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,
|
|
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?
|
|
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
|
data/lib/lutaml/model/utils.rb
CHANGED
|
@@ -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
|