lutaml-model 0.8.95 → 0.8.96

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.
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "XML plan serializer: namespaced models" do
6
+ # lutaml-model#847: the plan path used to refuse namespaced models
7
+ # outright (the xmlns dropped). The DOM emission now spells prefixed
8
+ # names, hoists every distinct (prefix, uri) declaration exactly once
9
+ # onto the output root, and prefixes type-namespaced attribute rows —
10
+ # byte-equal with the interpretive writer.
11
+
12
+ let(:wml_ns) do
13
+ Class.new(Lutaml::Xml::Namespace) do
14
+ uri "http://example.com/gate2/wml"
15
+ prefix_default "w"
16
+ end
17
+ end
18
+
19
+ let(:val_type) do
20
+ stub_const("Gate2Ns::WmlNs", wml_ns)
21
+ type = Class.new(Lutaml::Model::Type::String) do
22
+ include Lutaml::Xml::Type::Configurable
23
+
24
+ xml do
25
+ namespace Gate2Ns::WmlNs
26
+ end
27
+ end
28
+ stub_const("Gate2Ns::ValType", type)
29
+ type
30
+ end
31
+
32
+ let(:doc_class) do
33
+ val_type
34
+ body_class
35
+ model = Class.new(Lutaml::Model::Serializable) do
36
+ attribute :name, Gate2Ns::ValType
37
+ attribute :body, Gate2Ns::Body
38
+
39
+ xml do
40
+ element "document"
41
+ namespace Gate2Ns::WmlNs
42
+ map_attribute "name", to: :name
43
+ map_element "body", to: :body
44
+ end
45
+ end
46
+ stub_const("Gate2Ns::Document", model)
47
+ model
48
+ end
49
+
50
+ let(:body_class) do
51
+ val_type
52
+ model = Class.new(Lutaml::Model::Serializable) do
53
+ attribute :text, Gate2Ns::ValType, collection: true
54
+
55
+ xml do
56
+ element "body"
57
+ namespace Gate2Ns::WmlNs
58
+ map_element "t", to: :text
59
+ end
60
+ end
61
+ stub_const("Gate2Ns::Body", model)
62
+ model
63
+ end
64
+
65
+ let(:default_ns_model) do
66
+ ns = Class.new(Lutaml::Xml::Namespace) do
67
+ uri "http://example.com/gate2/default"
68
+ end
69
+ stub_const("Gate2Ns::DefaultNs", ns)
70
+ model = Class.new(Lutaml::Model::Serializable) do
71
+ attribute :name, :string
72
+
73
+ xml do
74
+ element "root"
75
+ namespace Gate2Ns::DefaultNs
76
+ map_attribute "name", to: :name
77
+ end
78
+ end
79
+ stub_const("Gate2Ns::DefaultDoc", model)
80
+ model
81
+ end
82
+
83
+ around do |example|
84
+ old = Lutaml::Model::Config.instance.xml_plan_fast_path
85
+ Lutaml::Model::Config.instance.xml_plan_fast_path = true
86
+ Lutaml::Model::Config.with_adapter(xml: :leptris) { example.run }
87
+ ensure
88
+ Lutaml::Model::Config.instance.xml_plan_fast_path = old
89
+ end
90
+
91
+ def plan_to_xml(klass, instance)
92
+ plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
93
+ klass, Lutaml::Model::Config.default_register
94
+ )
95
+ Lutaml::Xml::Leptris::PlanSerializer.call(
96
+ instance, plan, Lutaml::Model::Config.default_register
97
+ )
98
+ end
99
+
100
+ it "emits prefixed names with the declaration exactly once" do
101
+ instance = doc_class.new(
102
+ name: "d1",
103
+ body: body_class.new(text: ["hello"]),
104
+ )
105
+
106
+ out = plan_to_xml(doc_class, instance)
107
+ expect(out).to include(%(xmlns:w="http://example.com/gate2/wml"))
108
+ expect(out.scan("xmlns:w=").count).to eq(1)
109
+ expect(out).to include("<w:document")
110
+ expect(out).to include("w:name=\"d1\"")
111
+ expect(out).to include("<w:body>")
112
+ expect(out).to include("<w:t>hello</w:t>")
113
+ end
114
+
115
+ # The plan path spells the type-namespaced attribute w:name=; the
116
+ # interpretive writer emits name= unprefixed (a long-standing
117
+ # interpretive serialize gap — the unprefixed form does not rebind
118
+ # to the (URI, local) rule on reparse). Element tree, declarations,
119
+ # and values are compared; the attribute spelling divergence is
120
+ # pinned by the round-trip example below.
121
+ it "matches the interpretive element tree and declarations" do
122
+ instance = doc_class.new(
123
+ name: "d1",
124
+ body: body_class.new(text: ["hello", "world"]),
125
+ )
126
+
127
+ interpretive = Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
128
+ instance.to_xml
129
+ end
130
+
131
+ plan_out = plan_to_xml(doc_class, instance)
132
+ expect(plan_out.sub(" w:name=\"d1\"", "")).to eq(
133
+ interpretive.sub(" name=\"d1\"", "").strip,
134
+ )
135
+ end
136
+
137
+ it "round-trips through the plan parse path" do
138
+ instance = doc_class.new(
139
+ name: "d1",
140
+ body: body_class.new(text: ["hello"]),
141
+ )
142
+ xml = plan_to_xml(doc_class, instance)
143
+
144
+ reparsed = doc_class.from_xml(xml)
145
+ expect(reparsed.name).to eq("d1")
146
+ expect(reparsed.body.text).to eq(["hello"])
147
+ end
148
+
149
+ it "spells default-namespace models unprefixed with xmlns on the root" do
150
+ instance = default_ns_model.new(name: "plain")
151
+
152
+ out = plan_to_xml(default_ns_model, instance)
153
+ expect(out).to include(%(xmlns="http://example.com/gate2/default"))
154
+ expect(out).to include("<root")
155
+ expect(out).not_to include("xmlns:xmlns")
156
+ end
157
+ end
@@ -59,7 +59,8 @@ RSpec.describe "XML plan fast path: type-namespaced attribute rows" do
59
59
 
60
60
  expect(plan).not_to be_nil
61
61
  ns_row = plan[:tree][:attributes].find { |a| a[:name] == "val" }
62
- expect(ns_row[:ns]).to eq(exact: "http://example.com/type-ns/wml")
62
+ expect(ns_row[:ns]).to include(exact: "http://example.com/type-ns/wml")
63
+ expect(ns_row[:ns][:prefix]).to eq("w")
63
64
  end
64
65
 
65
66
  it "binds the attribute from the type's namespace" do
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "XML plan fast path: map_any_element catch-all" do
6
+ # leptris#1552: the wildcard child row rides a two-pass walk where
7
+ # named rows take precedence; the hydrator bridges the unclaimed
8
+ # remainder in document order and casts it through the attribute —
9
+ # the interpretive pass's contract for map_any_element.
10
+
11
+ let(:host_class) do
12
+ klass = Class.new(Lutaml::Model::Serializable) do
13
+ attribute :title, :string
14
+ attribute :others, :string, collection: true
15
+
16
+ xml do
17
+ element "host"
18
+ map_element "title", to: :title
19
+ map_any_element to: :others
20
+ end
21
+ end
22
+ stub_const("Gate4Any::Host", klass)
23
+ klass
24
+ end
25
+
26
+ let(:typed_class) do
27
+ item = Class.new(Lutaml::Model::Serializable) do
28
+ attribute :content, :string
29
+
30
+ xml do
31
+ element "item"
32
+ map_content to: :content
33
+ end
34
+ end
35
+ stub_const("Gate4Any::Item", item)
36
+ model = Class.new(Lutaml::Model::Serializable) do
37
+ attribute :label, :string
38
+ attribute :items, Gate4Any::Item, collection: true
39
+
40
+ xml do
41
+ element "bag"
42
+ map_element "label", to: :label
43
+ map_any_element to: :items
44
+ end
45
+ end
46
+ stub_const("Gate4Any::Bag", model)
47
+ model
48
+ end
49
+
50
+ around do |example|
51
+ old = Lutaml::Model::Config.instance.xml_plan_fast_path
52
+ Lutaml::Model::Config.instance.xml_plan_fast_path = true
53
+ Lutaml::Model::Config.with_adapter(xml: :leptris) { example.run }
54
+ ensure
55
+ Lutaml::Model::Config.instance.xml_plan_fast_path = old
56
+ end
57
+
58
+ # Members flow through the attribute's cast exactly as the
59
+ # interpretive pass does: String attributes stringify the wrapper
60
+ # (both paths identically), Serializable attributes build instances.
61
+ def member_shapes(elements)
62
+ elements.map { |e| e.class.name.split("::").last }
63
+ end
64
+
65
+ it "captures unclaimed children in document order" do
66
+ doc = "<host><title>t</title><extra1>a</extra1>" \
67
+ "<extra2>b</extra2></host>"
68
+
69
+ parsed = host_class.from_xml(doc)
70
+ expect(parsed.title).to eq("t")
71
+ expect(parsed.others.size).to eq(2)
72
+ expect(member_shapes(parsed.others)).to eq(%w[String String])
73
+ end
74
+
75
+ it "captures typed members as model instances" do
76
+ doc = "<bag><label>L</label><item>one</item><item>two</item></bag>"
77
+
78
+ parsed = typed_class.from_xml(doc)
79
+ expect(parsed.label).to eq("L")
80
+ expect(parsed.items.map(&:content)).to eq(%w[one two])
81
+ end
82
+
83
+ it "matches the interpretive path's capture" do
84
+ doc = "<host><title>t</title><extra1>a</extra1><extra2>b</extra2></host>"
85
+
86
+ interpretive = Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
87
+ host_class.from_xml(doc)
88
+ end
89
+
90
+ parsed = host_class.from_xml(doc)
91
+ expect(parsed.others.size).to eq(interpretive.others.size)
92
+ expect(member_shapes(parsed.others)).to eq(
93
+ member_shapes(interpretive.others).map { |n| n.sub("Nokogiri", "Leptris") },
94
+ )
95
+ end
96
+
97
+ it "compiles under engines without wildcard rows to nil" do
98
+ allow(Lutaml::Xml::Leptris).to receive(:wildcard_rows_compatible?)
99
+ .and_return(false)
100
+
101
+ expect(Lutaml::Xml::Leptris::PlanCompiler.compile(
102
+ host_class, Lutaml::Model::Config.default_register
103
+ )).to be_nil
104
+ end
105
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.95
4
+ version: 0.8.96
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -207,6 +207,7 @@ files:
207
207
  - docs/_guides/creating-xsd.adoc
208
208
  - docs/_guides/custom-methods.adoc
209
209
  - docs/_guides/document-validation.adoc
210
+ - docs/_guides/grammar-backed-formats.adoc
210
211
  - docs/_guides/index.adoc
211
212
  - docs/_guides/jsonld-serialization.adoc
212
213
  - docs/_guides/keyvalue-serialization.adoc
@@ -1811,6 +1812,7 @@ files:
1811
1812
  - spec/lutaml/model/serializable_instantiate_spec.rb
1812
1813
  - spec/lutaml/model/serializable_spec.rb
1813
1814
  - spec/lutaml/model/serializable_validation_spec.rb
1815
+ - spec/lutaml/model/serialize/attribute_definition_block_form_spec.rb
1814
1816
  - spec/lutaml/model/serialize/builder_spec.rb
1815
1817
  - spec/lutaml/model/serialize_perf_guard_spec.rb
1816
1818
  - spec/lutaml/model/services/transformer_spec.rb
@@ -1986,10 +1988,12 @@ files:
1986
1988
  - spec/lutaml/xml/pipeline_integration_spec.rb
1987
1989
  - spec/lutaml/xml/plain_model_accessors_spec.rb
1988
1990
  - spec/lutaml/xml/plan_fast_path_spec.rb
1991
+ - spec/lutaml/xml/plan_namespaced_serialize_spec.rb
1989
1992
  - spec/lutaml/xml/plan_ns_less_document_spec.rb
1990
1993
  - spec/lutaml/xml/plan_partition_spec.rb
1991
1994
  - spec/lutaml/xml/plan_type_ns_attribute_spec.rb
1992
1995
  - spec/lutaml/xml/plan_walk_spec.rb
1996
+ - spec/lutaml/xml/plan_wildcard_any_spec.rb
1993
1997
  - spec/lutaml/xml/prefix_control_spec.rb
1994
1998
  - spec/lutaml/xml/register_spec.rb
1995
1999
  - spec/lutaml/xml/reusable_mapping_spec.rb