lutaml-model 0.8.113 → 0.8.115

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be72639a2ee2fd3c23219f19141d43386c4126fbef9fa79fdff322f90bb58660
4
- data.tar.gz: 8c88a30cdda640eba02043ba87859a3cda864dcc23addf6a60aeccda63600dff
3
+ metadata.gz: b924b08953808c53f7f6db9f9832ffa870b11a5d2c7fe450b1340d8b2f289562
4
+ data.tar.gz: 3ba1f1afe7b2a95e83f137f8d3944e56e0ba516962b261665c4c3b91de74824f
5
5
  SHA512:
6
- metadata.gz: c5bfd563e53d7664d038da01d677152f3143c510cb1fa2cc56e13d20d5f6f9009251c618091258d58f7b25c45b70ab7c53e6a1fdafdb3a91213da871e3fde875
7
- data.tar.gz: a29144bc6c8cc8a3cf466e911b766df00718db916ddd3fc7127de00679f009ebe322407b9b74b509bf8fd4362005c7d85ce6076497d4b1676ab06648ab2a8213
6
+ metadata.gz: 2d956aca0be2bdbae7e513391962490429057f04ca4371be1477d4bbc29e79f8ad942beffbb40debf27db64f7be7e7c301110832439a3ece8eb4ece5e6c673d9
7
+ data.tar.gz: 7a384e7b06e927f9b78a129914de369c2befa23b14e84c6a9c2ba24e769c145dd0b0814fa3f92483516132020ced34c885c71872c251d41a8bdc6dff995b41db
@@ -687,6 +687,10 @@ partition = nil)
687
687
  # attribute is not a collection, so this guard defers exactly that case
688
688
  # to `.validate` while leaving declared ranges checked at parse. A mapped
689
689
  # PORO has no `.validate` to defer into, so it keeps the eager check.
690
+ # singular_tolerance: :first on the KV lane (#971): collapse an
691
+ # over-ingested collection before the writer stores it whole.
692
+ value = value.first if attr.tolerates_singular_overflow?(value)
693
+
690
694
  if attr.collection? || !instance.is_a?(Lutaml::Model::Serialize)
691
695
  attr.valid_collection!(value, context)
692
696
  end
@@ -1031,6 +1031,16 @@ child_mappings, options)
1031
1031
  rule.attribute_type < Lutaml::Model::Serialize
1032
1032
  end
1033
1033
 
1034
+ if is_nested_model && !value.is_a?(Lutaml::Model::Serialize)
1035
+ # The KV mirror of the XML lane's #971 collapse: a tolerated
1036
+ # singular attribute holding an over-assigned collection
1037
+ # serializes its leading value instead of raising.
1038
+ attr = if model_class.respond_to?(:attributes)
1039
+ model_class.attributes[rule.attribute_name.to_sym]
1040
+ end
1041
+ value = value.first if attr&.tolerates_singular_overflow?(value)
1042
+ end
1043
+
1034
1044
  if is_nested_model
1035
1045
  # Validate that value matches the expected type
1036
1046
  # There are three valid cases:
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.113"
5
+ VERSION = "0.8.115"
6
6
  end
7
7
  end
@@ -62,7 +62,12 @@ module Lutaml
62
62
  # (namespaced spellings are instance-dependent; ordered,
63
63
  # wildcard, raw, and callback shapes are not
64
64
  # ops-expressible).
65
- return NativeEmission.emit(instance, plan, register) if NativeEmission.compatible?(plan)
65
+ # element_order is the order contract: a model carrying it —
66
+ # parse-captured or programmatically tracked (#977) — must
67
+ # serialize through the ordered walk, not the flat
68
+ # schema-order native ops layout.
69
+ return NativeEmission.emit(instance, plan, register) if
70
+ NativeEmission.compatible?(plan) && instance.element_order.nil?
66
71
 
67
72
  doc = ::Leptris::XML::Document.create
68
73
  ns_ctx = Namespaces.new(instance, plan, register)
@@ -85,7 +90,7 @@ module Lutaml
85
90
  root.set_attribute("xsi:schemaLocation",
86
91
  instance.raw_schema_location)
87
92
  end
88
- built = if plan[:ordered]
93
+ built = if plan[:ordered] || !instance.element_order.nil?
89
94
  build_ordered(root, instance, plan, doc, register, ns_ctx)
90
95
  else
91
96
  build(root, instance, plan, doc, register, ns_ctx)
@@ -310,7 +315,7 @@ module Lutaml
310
315
  ns_ctx ? ns_ctx.spell(row_name(rule, spelling), child_ns) : row_name(rule, spelling),
311
316
  )
312
317
  ns_ctx&.collect(child_plan[:tree])
313
- if child_plan[:ordered]
318
+ if child_plan[:ordered] || !item.element_order.nil?
314
319
  build_ordered(child, item, child_plan,
315
320
  doc, reg, ns_ctx, child_ns) || return
316
321
  else
@@ -522,7 +527,7 @@ module Lutaml
522
527
  child = element.create_child(
523
528
  ns_ctx ? ns_ctx.spell(object.name, child_ns) : object.name,
524
529
  )
525
- if child_plan[:ordered]
530
+ if child_plan[:ordered] || !item.element_order.nil?
526
531
  return nil unless build_ordered(child, item, child_plan,
527
532
  doc, reg, ns_ctx, child_ns)
528
533
  else
@@ -580,7 +585,7 @@ module Lutaml
580
585
  child = element.create_child(
581
586
  ns_ctx ? ns_ctx.spell(name, child_ns) : name,
582
587
  )
583
- if child_plan[:ordered]
588
+ if child_plan[:ordered] || !item.element_order.nil?
584
589
  return nil unless build_ordered(child, item, child_plan,
585
590
  doc, reg, ns_ctx, child_ns)
586
591
  else
data/lutaml-model.gemspec CHANGED
@@ -41,7 +41,7 @@ Gem::Specification.new do |spec|
41
41
  # 0.5.84 always passes XML_PARSE_HUGE to libxml2: very large documents
42
42
  # parse completely instead of being silently truncated at the 10 MB
43
43
  # input-buffer cap (lutaml-model#871).
44
- spec.add_dependency "moxml", "~> 0.5.84"
44
+ spec.add_dependency "moxml", "~> 0.5.139"
45
45
  spec.add_dependency "ostruct"
46
46
  spec.add_dependency "rubyzip", "~> 3.7"
47
47
  spec.add_dependency "thor", "~> 1.4"
@@ -91,4 +91,69 @@ RSpec.describe "singular_tolerance attribute option" do
91
91
  expect(reduced).to eq(value.first)
92
92
  end
93
93
  end
94
+
95
+ describe "on the KeyValue lane" do
96
+ let(:marked_class) do
97
+ Class.new(Lutaml::Model::Serializable) do
98
+ attribute :content, :string
99
+
100
+ key_value do
101
+ map "content", to: :content
102
+ end
103
+ end
104
+ end
105
+
106
+ let(:relation_class) do
107
+ Class.new(Lutaml::Model::Serializable) do
108
+ attribute :rel_type, :string
109
+ attribute :description, "ToleranceMarked", singular_tolerance: :first
110
+
111
+ key_value do
112
+ map "type", to: :rel_type
113
+ map "description", to: :description
114
+ end
115
+ end
116
+ end
117
+
118
+ before do
119
+ stub_const("ToleranceMarked", marked_class)
120
+ stub_const("ToleranceRelation", relation_class)
121
+ end
122
+
123
+ it "keeps the leading value on ingest instead of the whole array" do
124
+ model = ToleranceRelation.from_yaml(
125
+ "---\ntype: cites\ndescription:\n - content: First\n - content: Second\n",
126
+ )
127
+
128
+ expect(model.description).to be_a(ToleranceMarked)
129
+ expect(model.description.content).to eq("First")
130
+ end
131
+
132
+ it "round-trips the collapsed singular through to_yaml" do
133
+ model = ToleranceRelation.from_yaml(
134
+ "---\ntype: cites\ndescription:\n - content: First\n - content: Second\n",
135
+ )
136
+
137
+ yaml = model.to_yaml
138
+ expect(yaml).to include("First")
139
+ expect(yaml).not_to include("Second")
140
+ expect(ToleranceRelation.from_yaml(yaml).description.content).to eq("First")
141
+ end
142
+
143
+ it "collapses an over-assigned collection at serialization" do
144
+ model = ToleranceRelation.new(
145
+ rel_type: "cites",
146
+ description: [ToleranceMarked.new(content: "One"),
147
+ ToleranceMarked.new(content: "Two")],
148
+ )
149
+
150
+ yaml = model.to_yaml
151
+ expect(yaml).to include("One")
152
+ expect(yaml).not_to include("Two")
153
+ end
154
+ end
94
155
  end
156
+
157
+ # The KV mirror of the XML-lane cases: the YAML lane never consulted
158
+ # the tolerance — ingest stored the whole array and re-serialization
159
+ # raised the mapped-PORO type check.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.113
4
+ version: 0.8.115
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-18 00:00:00.000000000 Z
11
+ date: 2026-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -92,14 +92,14 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: 0.5.84
95
+ version: 0.5.139
96
96
  type: :runtime
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: 0.5.84
102
+ version: 0.5.139
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: ostruct
105
105
  requirement: !ruby/object:Gem::Requirement