lutaml-model 0.8.19 → 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.
- 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/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/format.rb +61 -0
- data/lib/lutaml/json.rb +4 -59
- data/lib/lutaml/jsonl/format.rb +22 -0
- data/lib/lutaml/jsonl.rb +5 -19
- 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 +2 -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/serialize/attribute_definition.rb +1 -0
- data/lib/lutaml/model/serialize/format_conversion.rb +2 -2
- 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 +4 -3
- 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 +2 -2
- data/lib/lutaml/xml/mapping_rule.rb +62 -3
- data/lib/lutaml/xml/model_transform.rb +132 -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/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/model/attribute_apply_value_map_spec.rb +26 -13
- data/spec/lutaml/model/attribute_spec.rb +8 -3
- 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/json_spec.rb +1 -2
- 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/w3c_types_spec.rb +3 -6
- metadata +20 -4
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# lutaml-model#746: an empty value ("", [], {}) is a PRESENT value per
|
|
6
|
+
# docs/_guides/missing-values-handling.adoc and must reach custom `from`
|
|
7
|
+
# methods. Only nil (non-existent) and an omitted key (undefined) skip them.
|
|
8
|
+
module CustomFromEmptyValuesSpec
|
|
9
|
+
class Tagged < Lutaml::Model::Serializable
|
|
10
|
+
attribute :received, :string
|
|
11
|
+
|
|
12
|
+
key_value do
|
|
13
|
+
map "tags", to: :received, with: { to: :tags_to, from: :tags_from }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def tags_to(model, doc)
|
|
17
|
+
doc["tags"] = model.received
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tags_from(model, value)
|
|
21
|
+
model.received = value.inspect
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Mapped < Lutaml::Model::Serializable
|
|
26
|
+
attribute :received, :string
|
|
27
|
+
|
|
28
|
+
key_value do
|
|
29
|
+
map "fields", to: :received, with: { to: :fields_to, from: :fields_from }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fields_to(model, doc)
|
|
33
|
+
doc["fields"] = model.received
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fields_from(model, value)
|
|
37
|
+
model.received = value.inspect
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
RSpec.describe "custom from methods and empty values" do
|
|
43
|
+
it "receives an empty collection" do
|
|
44
|
+
expect(CustomFromEmptyValuesSpec::Tagged.from_yaml("tags: []").received)
|
|
45
|
+
.to eq("[]")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "receives an empty string" do
|
|
49
|
+
expect(CustomFromEmptyValuesSpec::Tagged.from_yaml('tags: ""').received)
|
|
50
|
+
.to eq("\"\"")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "receives an empty mapping" do
|
|
54
|
+
expect(CustomFromEmptyValuesSpec::Mapped.from_yaml("fields: {}").received)
|
|
55
|
+
.to eq("{}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "receives empty values from JSON too" do
|
|
59
|
+
expect(CustomFromEmptyValuesSpec::Tagged.from_json(%({"tags": []})).received)
|
|
60
|
+
.to eq("[]")
|
|
61
|
+
expect(CustomFromEmptyValuesSpec::Tagged.from_json(%({"tags": ""})).received)
|
|
62
|
+
.to eq("\"\"")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "still skips the method for a nil value" do
|
|
66
|
+
expect(CustomFromEmptyValuesSpec::Tagged.from_yaml("tags:").received)
|
|
67
|
+
.to be_nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "still skips the method for an omitted key" do
|
|
71
|
+
expect(CustomFromEmptyValuesSpec::Tagged.from_yaml("{}").received)
|
|
72
|
+
.to be_nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -201,8 +201,8 @@ module CustomVobjectAdapterSpec
|
|
|
201
201
|
@objects
|
|
202
202
|
end
|
|
203
203
|
|
|
204
|
-
def map(&)
|
|
205
|
-
@objects.map(&)
|
|
204
|
+
def map(&block)
|
|
205
|
+
@objects.map(&block)
|
|
206
206
|
end
|
|
207
207
|
|
|
208
208
|
private
|
|
@@ -260,16 +260,16 @@ module CustomVobjectAdapterSpec
|
|
|
260
260
|
@element_type = element_type
|
|
261
261
|
end
|
|
262
262
|
|
|
263
|
-
def map_value(to:, **)
|
|
264
|
-
add_mapping(:value, to, type: :simple, **)
|
|
263
|
+
def map_value(to:, **options)
|
|
264
|
+
add_mapping(:value, to, type: :simple, **options)
|
|
265
265
|
end
|
|
266
266
|
|
|
267
|
-
def map_property(name, to:, type: :simple, **)
|
|
268
|
-
add_mapping(name.downcase, to, type: type, **)
|
|
267
|
+
def map_property(name, to:, type: :simple, **options)
|
|
268
|
+
add_mapping(name.downcase, to, type: type, **options)
|
|
269
269
|
end
|
|
270
270
|
|
|
271
|
-
def map_component(name, to:, **)
|
|
272
|
-
add_mapping(name.downcase, to, type: :component, **)
|
|
271
|
+
def map_component(name, to:, **options)
|
|
272
|
+
add_mapping(name.downcase, to, type: :component, **options)
|
|
273
273
|
end
|
|
274
274
|
|
|
275
275
|
def map_field_set(count:, item_type:, item_options: {})
|
|
@@ -146,6 +146,33 @@ module DefaultsSpec
|
|
|
146
146
|
|
|
147
147
|
class InstanceAwareGrandChild < InstanceAwareChild
|
|
148
148
|
end
|
|
149
|
+
|
|
150
|
+
# Class-method defaults must keep resolving as they did before 0.8.0
|
|
151
|
+
# (lutaml-model#745): default lambdas are evaluated against the instance,
|
|
152
|
+
# with the model class as fallback for methods the instance does not have.
|
|
153
|
+
class ClassMethodDefault < Lutaml::Model::Serializable
|
|
154
|
+
attribute :os, :string, default: -> { detect_os }
|
|
155
|
+
|
|
156
|
+
def self.detect_os
|
|
157
|
+
"from-class-method"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class ClassMethodDefaultWithArgs < Lutaml::Model::Serializable
|
|
162
|
+
attribute :label, :string, default: -> { build_label("x") }
|
|
163
|
+
|
|
164
|
+
def self.build_label(prefix)
|
|
165
|
+
"#{prefix}-label"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
class InstanceMethodDefault < Lutaml::Model::Serializable
|
|
170
|
+
attribute :value, :string, default: -> { instance_helper }
|
|
171
|
+
|
|
172
|
+
def instance_helper
|
|
173
|
+
"from-instance-method"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
149
176
|
end
|
|
150
177
|
|
|
151
178
|
RSpec.describe DefaultsSpec::Glaze do
|
|
@@ -346,3 +373,21 @@ RSpec.describe DefaultsSpec::Glaze do
|
|
|
346
373
|
end
|
|
347
374
|
end
|
|
348
375
|
end
|
|
376
|
+
|
|
377
|
+
RSpec.describe DefaultsSpec::ClassMethodDefault do
|
|
378
|
+
it "resolves class methods from default lambdas" do
|
|
379
|
+
expect(described_class.new.os).to eq("from-class-method")
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
RSpec.describe DefaultsSpec::ClassMethodDefaultWithArgs do
|
|
384
|
+
it "passes arguments through to class methods" do
|
|
385
|
+
expect(described_class.new.label).to eq("x-label")
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
RSpec.describe DefaultsSpec::InstanceMethodDefault do
|
|
390
|
+
it "still resolves instance methods in default lambdas" do
|
|
391
|
+
expect(described_class.new.value).to eq("from-instance-method")
|
|
392
|
+
end
|
|
393
|
+
end
|
|
@@ -8,9 +8,8 @@ RSpec.describe Lutaml::Model::Json do
|
|
|
8
8
|
let(:lib_path) { File.expand_path("../../../lib", __dir__) }
|
|
9
9
|
|
|
10
10
|
it "loads require 'lutaml/json' without preloading lutaml/model" do
|
|
11
|
-
# rubocop:disable Style/CommandLiteral
|
|
11
|
+
# rubocop:disable-next Style/CommandLiteral
|
|
12
12
|
result = `#{RbConfig.ruby} -I#{lib_path} -e 'require "lutaml/json"; puts :ok' 2>&1`
|
|
13
|
-
# rubocop:enable Style/CommandLiteral
|
|
14
13
|
|
|
15
14
|
expect($?.success?).to be(true), result
|
|
16
15
|
expect(result).to include("ok")
|
|
@@ -112,7 +112,8 @@ RSpec.describe "Lutaml::Model::Serialize::Builder" do
|
|
|
112
112
|
<items>c</items>
|
|
113
113
|
</container>
|
|
114
114
|
XML
|
|
115
|
-
expect(obj.element_order.map(&:name)).to eq(%w[singular items items
|
|
115
|
+
expect(obj.element_order.map(&:name)).to eq(%w[singular items items
|
|
116
|
+
items])
|
|
116
117
|
end
|
|
117
118
|
|
|
118
119
|
it "all direct setters: emits in call order" do
|
|
@@ -298,7 +299,8 @@ RSpec.describe "Lutaml::Model::Serialize::Builder" do
|
|
|
298
299
|
# so both assignments are recorded. The serializer emits the current
|
|
299
300
|
# value of the attribute via element_order in recorded order; the
|
|
300
301
|
# safety net guarantees no data loss.
|
|
301
|
-
expect(obj.element_order.map(&:name)).to eq(%w[items items items items
|
|
302
|
+
expect(obj.element_order.map(&:name)).to eq(%w[items items items items
|
|
303
|
+
items])
|
|
302
304
|
xml = obj.to_xml
|
|
303
305
|
# The serialized output must contain all current items
|
|
304
306
|
expect(xml).to include("<items>c</items>")
|
|
@@ -317,7 +319,8 @@ RSpec.describe "Lutaml::Model::Serialize::Builder" do
|
|
|
317
319
|
xml = obj.to_xml
|
|
318
320
|
|
|
319
321
|
if !obj.singular.nil? && !obj.singular.to_s.empty?
|
|
320
|
-
expect(xml).to include("<singular>"),
|
|
322
|
+
expect(xml).to include("<singular>"),
|
|
323
|
+
"singular was dropped from output"
|
|
321
324
|
end
|
|
322
325
|
return if obj.items.nil? || obj.items.empty?
|
|
323
326
|
|
|
@@ -43,7 +43,9 @@ RSpec.describe Lutaml::Model::Store do
|
|
|
43
43
|
|
|
44
44
|
describe "indexed lookup" do
|
|
45
45
|
it "builds index lazily on first resolve" do
|
|
46
|
-
|
|
46
|
+
# Hold strong references: Store keeps WeakRefs, so unreferenced
|
|
47
|
+
# objects can be collected before resolve runs.
|
|
48
|
+
_objects = Array.new(5) { |i| model_class.new(id: "obj-#{i}") }
|
|
47
49
|
|
|
48
50
|
result = described_class.resolve(model_class, :id, "obj-3")
|
|
49
51
|
expect(result.id).to eq("obj-3")
|
|
@@ -33,7 +33,8 @@ RSpec.describe Lutaml::Model::Transform, "#apply_value_map (nil attr)" do
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
it "passes initialized values through unchanged" do
|
|
36
|
-
expect(transform.apply_value_map("hello", default_vmap,
|
|
36
|
+
expect(transform.apply_value_map("hello", default_vmap,
|
|
37
|
+
nil)).to eq("hello")
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
40
|
|
|
@@ -50,11 +51,13 @@ RSpec.describe Lutaml::Model::Transform, "#apply_value_map (nil attr)" do
|
|
|
50
51
|
# custom method runs), and resolved :nil/:empty consistently for empty inputs.
|
|
51
52
|
describe "non-:nil options with nil attr" do
|
|
52
53
|
it "maps a nil value to uninitialized via nil: :omitted" do
|
|
53
|
-
expect(transform.apply_value_map(nil, { nil: :omitted },
|
|
54
|
+
expect(transform.apply_value_map(nil, { nil: :omitted },
|
|
55
|
+
nil)).to eq(uninit)
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
it "maps an empty value to uninitialized via empty: :omitted" do
|
|
57
|
-
expect(transform.apply_value_map("", { empty: :omitted },
|
|
59
|
+
expect(transform.apply_value_map("", { empty: :omitted },
|
|
60
|
+
nil)).to eq(uninit)
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
it "maps an empty value to nil via empty: :nil" do
|
|
@@ -311,7 +311,8 @@ RSpec.describe "Union-typed attributes (issue #190)" do
|
|
|
311
311
|
it "serializes a model member built in plain Ruby identically to a deserialized one" do
|
|
312
312
|
built = UnionAttributeSpec::Ceramic.new(
|
|
313
313
|
firing_temperature:
|
|
314
|
-
UnionAttributeSpec::TemperatureWithUnit.new(number: 1300.0,
|
|
314
|
+
UnionAttributeSpec::TemperatureWithUnit.new(number: 1300.0,
|
|
315
|
+
unit: "C"),
|
|
315
316
|
)
|
|
316
317
|
round = UnionAttributeSpec::Ceramic.from_yaml(built.to_yaml)
|
|
317
318
|
expect(round.firing_temperature)
|
|
@@ -424,7 +425,8 @@ RSpec.describe "Union-typed attributes (issue #190)" do
|
|
|
424
425
|
end
|
|
425
426
|
|
|
426
427
|
it "rejects unsupported scalar member types at definition time" do
|
|
427
|
-
%i[hash symbol time time_without_date date
|
|
428
|
+
%i[hash symbol time time_without_date date
|
|
429
|
+
date_time].each do |unsupported|
|
|
428
430
|
expect do
|
|
429
431
|
Class.new(Lutaml::Model::Serializable) do
|
|
430
432
|
attribute :bad, [unsupported, :string]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
module ValuePolicySpec
|
|
6
|
+
# A value type whose whole purpose is to wrap any literal:
|
|
7
|
+
# scalar, list, or map (issue #752).
|
|
8
|
+
class LiteralValue < Lutaml::Model::Type::Value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class LiteralDoc < Lutaml::Model::Serializable
|
|
12
|
+
attribute :v, LiteralValue
|
|
13
|
+
key_value do
|
|
14
|
+
map "v", to: :v
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class StringsDoc < Lutaml::Model::Serializable
|
|
19
|
+
attribute :v, :string
|
|
20
|
+
key_value do
|
|
21
|
+
map "v", to: :v
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class CollectionDoc < Lutaml::Model::Serializable
|
|
26
|
+
attribute :v, :string, collection: true
|
|
27
|
+
key_value do
|
|
28
|
+
map "v", to: :v
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
RSpec.describe "ValuePolicy shaping of non-collection attributes" do
|
|
34
|
+
describe "user-defined Type::Value subclass" do
|
|
35
|
+
it "receives the whole array from key-value data" do
|
|
36
|
+
expect(ValuePolicySpec::LiteralDoc.from_yaml("v: [a, b]").v)
|
|
37
|
+
.to eq(["a", "b"])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "receives an empty array from key-value data" do
|
|
41
|
+
expect(ValuePolicySpec::LiteralDoc.from_yaml("v: []").v).to eq([])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "receives the whole hash from key-value data" do
|
|
45
|
+
v = ValuePolicySpec::LiteralDoc.from_yaml("v: {a: 1}").v
|
|
46
|
+
expect(v.value).to eq("a" => 1)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "keeps scalar behavior" do
|
|
50
|
+
doc = ValuePolicySpec::LiteralDoc.from_yaml(<<~YAML)
|
|
51
|
+
v: hello
|
|
52
|
+
YAML
|
|
53
|
+
expect(doc.v).to eq("hello")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "round-trips an array value through YAML" do
|
|
57
|
+
doc = ValuePolicySpec::LiteralDoc.from_yaml("v: [a, b]")
|
|
58
|
+
expect(ValuePolicySpec::LiteralDoc.from_yaml(doc.to_yaml).v)
|
|
59
|
+
.to eq(["a", "b"])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "accepts an array assigned after construction" do
|
|
63
|
+
doc = ValuePolicySpec::LiteralDoc.new(v: "seed")
|
|
64
|
+
doc.v = ["a", "b"]
|
|
65
|
+
expect(doc.v).to eq(["a", "b"])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "built-in scalar types" do
|
|
70
|
+
it "keep the collection: true guidance error" do
|
|
71
|
+
expect { ValuePolicySpec::StringsDoc.from_yaml("v: [a]") }
|
|
72
|
+
.to raise_error(Lutaml::Model::CollectionTrueMissingError)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "collection attributes" do
|
|
77
|
+
it "keep element-wise casting" do
|
|
78
|
+
expect(ValuePolicySpec::CollectionDoc.from_yaml("v: [a, b]").v)
|
|
79
|
+
.to eq(["a", "b"])
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# lutaml-model: every public format entrypoint and `require "lutaml/model"`
|
|
6
|
+
# itself must not produce "circular require considered harmful" warnings
|
|
7
|
+
# under ruby -w (regression guard after the public-wrapper/internal-format
|
|
8
|
+
# restructuring).
|
|
9
|
+
RSpec.describe "require load order" do
|
|
10
|
+
it "loads lutaml/model with no circular-require warnings under -w" do
|
|
11
|
+
output = `bundle exec ruby -w -e 'require "lutaml/model"' 2>&1`
|
|
12
|
+
circular = output.lines.count { |l| l.include?("circular require considered harmful") }
|
|
13
|
+
expect(circular).to eq(0),
|
|
14
|
+
"Expected no circular-require warnings, got #{circular}:\n#{output}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
%w[json key_value yaml toml hash_format jsonl yamls xml].each do |format|
|
|
18
|
+
it "loads lutaml/#{format} standalone without circular-require warnings" do
|
|
19
|
+
output = `bundle exec ruby -w -e 'require "lutaml/#{format}"' 2>&1`
|
|
20
|
+
circular = output.lines.count { |l| l.include?("circular require considered harmful") }
|
|
21
|
+
expect(circular).to eq(0),
|
|
22
|
+
"Expected no circular-require warnings loading lutaml/#{format}, got #{circular}:\n#{output}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "../../../lib/lutaml/model"
|
|
5
|
+
|
|
6
|
+
# lutaml-model#758: a plain (unnamespaced) map_attribute rule on an
|
|
7
|
+
# element-namespaced class must not absorb a namespaced sibling attribute
|
|
8
|
+
# (and vice versa), and a type-level `namespace :blank` must not crash.
|
|
9
|
+
module ElementNsDisjointSpec
|
|
10
|
+
class XmiNs < Lutaml::Xml::Namespace
|
|
11
|
+
uri "http://www.example.com/xmi"
|
|
12
|
+
prefix_default "xmi"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class ElemNs < Lutaml::Xml::Namespace
|
|
16
|
+
uri "http://www.example.com/elem"
|
|
17
|
+
prefix_default "e"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class XmiTypeAttr < Lutaml::Model::Type::String
|
|
21
|
+
xml do
|
|
22
|
+
namespace XmiNs
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class DocBase < Lutaml::Model::Serializable
|
|
27
|
+
attribute :xmi_type, XmiTypeAttr
|
|
28
|
+
|
|
29
|
+
xml do
|
|
30
|
+
namespace ElemNs
|
|
31
|
+
map_attribute "type", to: :xmi_type
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Doc < DocBase
|
|
36
|
+
attribute :plain_type, Lutaml::Model::Type::String
|
|
37
|
+
|
|
38
|
+
xml do
|
|
39
|
+
root "doc"
|
|
40
|
+
map_attribute "type", to: :plain_type
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class BlankType < Lutaml::Model::Type::String
|
|
45
|
+
xml do
|
|
46
|
+
namespace :blank
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class BlankDoc < Lutaml::Model::Serializable
|
|
51
|
+
attribute :v, BlankType
|
|
52
|
+
xml do
|
|
53
|
+
root "bd"
|
|
54
|
+
map_attribute "v", to: :v
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
RSpec.describe "attribute namespace-disjointness on element-namespaced classes (#758)" do
|
|
60
|
+
it "routes xmi:type to the namespaced slot and leaves plain_type nil" do
|
|
61
|
+
doc = ElementNsDisjointSpec::Doc.from_xml(
|
|
62
|
+
%(<doc xmlns:xmi="http://www.example.com/xmi" xmi:type="uml:Parameter"/>),
|
|
63
|
+
)
|
|
64
|
+
expect(doc.xmi_type).to eq("uml:Parameter")
|
|
65
|
+
expect(doc.plain_type).to be_nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "routes an unprefixed type to plain_type, leaves xmi_type nil" do
|
|
69
|
+
doc = ElementNsDisjointSpec::Doc.from_xml(%(<doc type="EAnone_void"/>))
|
|
70
|
+
expect(doc.plain_type).to eq("EAnone_void")
|
|
71
|
+
expect(doc.xmi_type).to be_nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "round-trips both spellings without emitting a spurious duplicate" do
|
|
75
|
+
doc = ElementNsDisjointSpec::Doc.from_xml(
|
|
76
|
+
%(<doc xmlns:xmi="http://www.example.com/xmi" xmi:type="uml:Parameter"/>),
|
|
77
|
+
)
|
|
78
|
+
out = doc.to_xml.strip
|
|
79
|
+
expect(out).to include('xmi:type="uml:Parameter"')
|
|
80
|
+
expect(out).not_to match(/\stype="uml:Parameter"/)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "survives type-level namespace :blank without crashing" do
|
|
84
|
+
expect do
|
|
85
|
+
ElementNsDisjointSpec::BlankDoc.from_xml(%(<bd v="x"/>))
|
|
86
|
+
end.not_to raise_error
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Two map_attribute rules that share a local name but are distinguished by
|
|
6
|
+
# their type classes' bound namespaces must parse disjointly, mirroring
|
|
7
|
+
# serialization (lutaml-model#744).
|
|
8
|
+
#
|
|
9
|
+
# The coexisting-spelling cases under nokogiri/rexml additionally require the
|
|
10
|
+
# moxml adapter fixes (lutaml/moxml#94, lutaml/moxml#95); oga and ox already
|
|
11
|
+
# preserve same-local-name attributes across namespaces.
|
|
12
|
+
module AttributeNamespaceDisjointnessSpec
|
|
13
|
+
class XmiNs < Lutaml::Xml::Namespace
|
|
14
|
+
uri "http://www.example.com/xmi"
|
|
15
|
+
prefix_default "xmi"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class XmiNsType < Lutaml::Model::Type::String
|
|
19
|
+
xml do
|
|
20
|
+
namespace XmiNs
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Probe < Lutaml::Model::Serializable
|
|
25
|
+
attribute :xmi_type, XmiNsType
|
|
26
|
+
attribute :plain_type, Lutaml::Model::Type::String
|
|
27
|
+
|
|
28
|
+
xml do
|
|
29
|
+
element "probe"
|
|
30
|
+
map_attribute "type", to: :xmi_type
|
|
31
|
+
map_attribute "type", to: :plain_type
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.parse(xml, adapter)
|
|
36
|
+
Lutaml::Model::Config.with_adapter(xml: adapter) { Probe.from_xml(xml) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
RSpec.describe "Attribute namespace-disjoint parsing" do
|
|
41
|
+
let(:namespaced_xmi) do
|
|
42
|
+
%(<probe xmlns:xmi="http://www.example.com/xmi" xmi:type="uml:Parameter"/>)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
let(:namespaced_foo) do
|
|
46
|
+
%(<probe xmlns:foo="http://www.example.com/xmi" foo:type="uml:Parameter"/>)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
let(:plain) { %(<probe type="EAnone_void"/>) }
|
|
50
|
+
|
|
51
|
+
let(:both_spellings) do
|
|
52
|
+
%(<probe xmlns:xmi="http://www.example.com/xmi" xmi:type="uml:Parameter" type="EAnone_void"/>)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
let(:both_spellings_plain_first) do
|
|
56
|
+
%(<probe type="EAnone_void" xmlns:xmi="http://www.example.com/xmi" xmi:type="uml:Parameter"/>)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
%i[oga ox nokogiri].each do |adapter|
|
|
60
|
+
context "with the #{adapter} adapter" do
|
|
61
|
+
it "routes a namespaced rule to the attribute bound to its URI" do
|
|
62
|
+
probe = AttributeNamespaceDisjointnessSpec.parse(namespaced_xmi,
|
|
63
|
+
adapter)
|
|
64
|
+
|
|
65
|
+
expect(probe.xmi_type).to eq("uml:Parameter")
|
|
66
|
+
expect(probe.plain_type).to be_nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "accepts any prefix bound to the namespace URI" do
|
|
70
|
+
probe = AttributeNamespaceDisjointnessSpec.parse(namespaced_foo,
|
|
71
|
+
adapter)
|
|
72
|
+
|
|
73
|
+
expect(probe.xmi_type).to eq("uml:Parameter")
|
|
74
|
+
expect(probe.plain_type).to be_nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "does not let a namespace-less attribute satisfy the namespaced rule" do
|
|
78
|
+
probe = AttributeNamespaceDisjointnessSpec.parse(plain, adapter)
|
|
79
|
+
|
|
80
|
+
expect(probe.xmi_type).to be_nil
|
|
81
|
+
expect(probe.plain_type).to eq("EAnone_void")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Same-local-name attributes coexisting on one element require the moxml
|
|
87
|
+
# fixes (lutaml/moxml#94 drops one under nokogiri; lutaml/moxml#95 crashes
|
|
88
|
+
# under rexml), so these cases run on the adapters that preserve both.
|
|
89
|
+
%i[oga ox].each do |adapter|
|
|
90
|
+
context "with the #{adapter} adapter" do
|
|
91
|
+
it "keeps both spellings disjoint regardless of document order" do
|
|
92
|
+
[both_spellings, both_spellings_plain_first].each do |xml|
|
|
93
|
+
probe = AttributeNamespaceDisjointnessSpec.parse(xml, adapter)
|
|
94
|
+
|
|
95
|
+
expect(probe.xmi_type).to eq("uml:Parameter")
|
|
96
|
+
expect(probe.plain_type).to eq("EAnone_void")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "serialization" do
|
|
103
|
+
it "writes each rule in its own namespace" do
|
|
104
|
+
probe = AttributeNamespaceDisjointnessSpec::Probe.new(
|
|
105
|
+
xmi_type: "uml:Parameter",
|
|
106
|
+
plain_type: "EAnone_void",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
expect(probe.to_xml.strip).to be_xml_equivalent_to(
|
|
110
|
+
%(<probe xmlns:xmi="http://www.example.com/xmi" xmi:type="uml:Parameter" type="EAnone_void"/>),
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "round-trips both spellings through parsing" do
|
|
115
|
+
probe = AttributeNamespaceDisjointnessSpec::Probe.new(
|
|
116
|
+
xmi_type: "uml:Parameter",
|
|
117
|
+
plain_type: "EAnone_void",
|
|
118
|
+
)
|
|
119
|
+
reparsed = AttributeNamespaceDisjointnessSpec.parse(probe.to_xml, :oga)
|
|
120
|
+
|
|
121
|
+
expect(reparsed.xmi_type).to eq("uml:Parameter")
|
|
122
|
+
expect(reparsed.plain_type).to eq("EAnone_void")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|