lutaml-model 0.8.111 → 0.8.113

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: cb4de517a8e23bcf76c55a9e1e935f5be2b096fca9c4075d309c7ef748c7b238
4
- data.tar.gz: bb62186c0b7261db5b6cf1574280d21b70ee4a562c9bdc6f542c636fe96e2763
3
+ metadata.gz: be72639a2ee2fd3c23219f19141d43386c4126fbef9fa79fdff322f90bb58660
4
+ data.tar.gz: 8c88a30cdda640eba02043ba87859a3cda864dcc23addf6a60aeccda63600dff
5
5
  SHA512:
6
- metadata.gz: b26603400d059d7a60691c83b7a1f0ba54654e9c5d0dc21d7f851fc16b641bfe58de45859a4c9050a37ba6e4b69d1ab202ca41c8cda9910f93d8451d22b8bfdf
7
- data.tar.gz: 12e9f4eb81abe1dcce11bae28bbb538c66886743882b66c528dc215868ac7e54b6d4ea09dc0a1ffe6b5945352929a6cc16677f4412fad315be9b207b5f3044fe
6
+ metadata.gz: c5bfd563e53d7664d038da01d677152f3143c510cb1fa2cc56e13d20d5f6f9009251c618091258d58f7b25c45b70ab7c53e6a1fdafdb3a91213da871e3fde875
7
+ data.tar.gz: a29144bc6c8cc8a3cf466e911b766df00718db916ddd3fc7127de00679f009ebe322407b9b74b509bf8fd4362005c7d85ce6076497d4b1676ab06648ab2a8213
@@ -47,6 +47,7 @@ module Lutaml
47
47
  min_length
48
48
  max_length
49
49
  desc
50
+ singular_tolerance
50
51
  ].freeze
51
52
 
52
53
  # Per-type-class memo of the custom from_xml/from_json probe used by #cast.
@@ -827,6 +828,17 @@ instance_object = nil)
827
828
  end
828
829
  end
829
830
 
831
+ # Singular-overflow tolerance (#971): ingest can hand a singular
832
+ # attribute a collection — relaton's upstream documents carry
833
+ # multiple <formattedref> elements per bibitem. With
834
+ # singular_tolerance: :first the leading value is kept instead of
835
+ # raising CollectionTrueMissingError; the default stays strict.
836
+ def tolerates_singular_overflow?(value)
837
+ !collection? && @options[:singular_tolerance] == :first &&
838
+ collection_instance?(value) &&
839
+ !value_policy.whole_value?(unresolved_type)
840
+ end
841
+
830
842
  def valid_collection!(value, caller)
831
843
  if collection_instance?(value) && !collection? &&
832
844
  !value_policy.whole_value?(unresolved_type)
@@ -1041,6 +1053,12 @@ instance_object = nil)
1041
1053
  "Use map_element \"name\", to: :#{name}, raw: :content instead."
1042
1054
  end
1043
1055
  @validations = @options[:validations]
1056
+ tolerance = @options[:singular_tolerance]
1057
+ unless tolerance.nil? || tolerance == :first
1058
+ raise ArgumentError,
1059
+ "Invalid singular_tolerance: #{tolerance.inspect} for " \
1060
+ "attribute '#{name}'. Supported: :first"
1061
+ end
1044
1062
  set_default_for_collection if collection?
1045
1063
  end
1046
1064
 
@@ -37,11 +37,13 @@ module Lutaml
37
37
  @conversion_cache = nil
38
38
  @configured = false
39
39
  @xml_plan_fast_path = true
40
- # Opt-in until leptris#1743 lands: the native ordered lane
41
- # runs away (28+ CPU-min / 14GB RSS on sts-ruby's
42
- # ISO_13849-1 document) — flipping this on by default regressed
43
- # the sts bench cell from 4m41s to a 10-minute kill.
44
- @xml_plan_ordered_path = false
40
+ # On by default since the engine fix: leptris#1743 (the
41
+ # exponential flatten on diamond-shared plan graphs) shipped in
42
+ # 1.9.366.2's identity-memo flatten_plan — the ISO_13849 corpus
43
+ # that hung for 28+ CPU-min now parses in ~8s warm. The floor
44
+ # bump (MIN_LEPTRIS_VERSION = 1.9.366.2) keeps consumers on
45
+ # memo-less engines off this lane.
46
+ @xml_plan_ordered_path = true
45
47
  @element_order_tracking = true
46
48
  end
47
49
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.111"
5
+ VERSION = "0.8.113"
6
6
  end
7
7
  end
@@ -19,7 +19,7 @@ module Lutaml
19
19
  # leptris-ruby#447). Bundles resolving an older gem keep the
20
20
  # interpretive path — PlanCompiler.compile answers nil — the
21
21
  # opt-out protocol.
22
- MIN_LEPTRIS_VERSION = "1.9.351.1"
22
+ MIN_LEPTRIS_VERSION = "1.9.366.2"
23
23
 
24
24
  # Memoized: the compiler consults this on every cache hit (per
25
25
  # child row per node), and the comparison minted a Gem::Version
@@ -479,6 +479,11 @@ module Lutaml
479
479
  # violation entirely — check it while there is still someone to tell.
480
480
  if !instance_is_serialize && attr && !attr.collection? &&
481
481
  !rule.content_mapping?
482
+ # singular_tolerance: :first collapses an over-ingested
483
+ # collection to its leading value before the writer sees it
484
+ # (#971) — the reduced scalar then flows through
485
+ # deserialize and serializes as a single element.
486
+ value = value.first if attr.tolerates_singular_overflow?(value)
482
487
  attr.valid_collection!(value, context)
483
488
  end
484
489
  rule.deserialize(instance, value, deserialize_attributes, context,
@@ -51,11 +51,12 @@ RSpec.describe Lutaml::Model::AdapterResolver, ".adapter_for with a configured t
51
51
  end
52
52
 
53
53
  describe "plan lane defaults" do
54
- it "ships the ordered plan lane opt-in until leptris#1743 is fixed" do
55
- # The native ordered lane runs away on sts-ruby's ISO_13849-1
56
- # document (leptris#1743): 28+ CPU-min / 14 GB RSS vs 6.36s on the
57
- # same parse with the lane off. Opt-in until the engine fix ships.
58
- expect(Lutaml::Model::Config.instance.xml_plan_ordered_path).to be(false)
54
+ it "ships the ordered plan lane on, floored to the memo-capable engine" do
55
+ # leptris#1743 (exponential flatten on diamond-shared plan
56
+ # graphs) shipped its identity-memo flatten_plan in 1.9.366.2;
57
+ # the MIN_LEPTRIS_VERSION floor keeps memo-less engines on the
58
+ # interpretive path, so the default-on lane is safe.
59
+ expect(Lutaml::Model::Config.instance.xml_plan_ordered_path).to be(true)
59
60
  end
60
61
  end
61
62
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # #971: a singular attribute may ask ingest to keep the leading value
6
+ # when the mapper collects more than one element (relaton's upstream
7
+ # bibitems carry multiple <formattedref> elements). Default stays
8
+ # strict: the over-count remains a CollectionTrueMissingError.
9
+ RSpec.describe "singular_tolerance attribute option" do
10
+ let(:item_class) do
11
+ Class.new(Lutaml::Model::Serializable) do
12
+ attribute :content, :string
13
+ end
14
+ end
15
+
16
+ before do
17
+ stub_const("ToleranceItem", item_class)
18
+ end
19
+
20
+ describe "option validation" do
21
+ it "accepts :first" do
22
+ expect do
23
+ Lutaml::Model::Attribute.new(:ref, "ToleranceItem",
24
+ singular_tolerance: :first)
25
+ end.not_to raise_error
26
+ end
27
+
28
+ it "accepts omission (strict default)" do
29
+ expect do
30
+ Lutaml::Model::Attribute.new(:ref, "ToleranceItem")
31
+ end.not_to raise_error
32
+ end
33
+
34
+ it "rejects unsupported tolerances" do
35
+ expect do
36
+ Lutaml::Model::Attribute.new(:ref, "ToleranceItem",
37
+ singular_tolerance: :last)
38
+ end.to raise_error(ArgumentError, /singular_tolerance: :last/)
39
+ end
40
+ end
41
+
42
+ describe "#tolerates_singular_overflow?" do
43
+ let(:tolerant) do
44
+ Lutaml::Model::Attribute.new(:ref, "ToleranceItem",
45
+ singular_tolerance: :first)
46
+ end
47
+
48
+ let(:strict) { Lutaml::Model::Attribute.new(:ref, "ToleranceItem") }
49
+
50
+ let(:collection_attr) do
51
+ Lutaml::Model::Attribute.new(:refs, "ToleranceItem",
52
+ collection: true,
53
+ singular_tolerance: :first)
54
+ end
55
+
56
+ it "is true for an over-ingested collection on a tolerant singular" do
57
+ expect(tolerant.tolerates_singular_overflow?([1, 2])).to be(true)
58
+ end
59
+
60
+ it "is false without the option" do
61
+ expect(strict.tolerates_singular_overflow?([1, 2])).to be(false)
62
+ end
63
+
64
+ it "is false for a scalar on a tolerant singular" do
65
+ expect(tolerant.tolerates_singular_overflow?(1)).to be(false)
66
+ end
67
+
68
+ it "is false for a collection attribute — the option does not apply" do
69
+ expect(collection_attr.tolerates_singular_overflow?([1, 2])).to be(false)
70
+ end
71
+ end
72
+
73
+ describe "strict default" do
74
+ it "still raises CollectionTrueMissingError on over-ingest" do
75
+ attr = Lutaml::Model::Attribute.new(:ref, "ToleranceItem")
76
+
77
+ expect do
78
+ attr.valid_collection!([item_class.new, item_class.new], String)
79
+ end.to raise_error(Lutaml::Model::CollectionTrueMissingError)
80
+ end
81
+ end
82
+
83
+ describe "tolerant ingest" do
84
+ it "lets valid_collection! pass after the transform reduces to first" do
85
+ attr = Lutaml::Model::Attribute.new(:ref, "ToleranceItem",
86
+ singular_tolerance: :first)
87
+ value = [item_class.new, item_class.new]
88
+
89
+ reduced = value.first if attr.tolerates_singular_overflow?(value)
90
+ expect(attr.valid_collection!(reduced, String)).to be(true)
91
+ expect(reduced).to eq(value.first)
92
+ end
93
+ end
94
+ end
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.111
4
+ version: 0.8.113
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-16 00:00:00.000000000 Z
11
+ date: 2026-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -1824,6 +1824,7 @@ files:
1824
1824
  - spec/lutaml/model/serialize_perf_guard_spec.rb
1825
1825
  - spec/lutaml/model/services/transformer_spec.rb
1826
1826
  - spec/lutaml/model/simple_model_spec.rb
1827
+ - spec/lutaml/model/singular_tolerance_spec.rb
1827
1828
  - spec/lutaml/model/store_spec.rb
1828
1829
  - spec/lutaml/model/toml_adapter_spec.rb
1829
1830
  - spec/lutaml/model/toml_spec.rb