lutaml-model 0.8.112 → 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: 3f1d5fc3ccd267d7c8c8f4a7117db2f01b1a81da024724128402ca058584526b
4
- data.tar.gz: 0c5fa1399c3e9df481ee09480f1851493544c0c26cf1e0f8851c381dec5dfa64
3
+ metadata.gz: be72639a2ee2fd3c23219f19141d43386c4126fbef9fa79fdff322f90bb58660
4
+ data.tar.gz: 8c88a30cdda640eba02043ba87859a3cda864dcc23addf6a60aeccda63600dff
5
5
  SHA512:
6
- metadata.gz: 4809033fde8d06502a1cde12873b1d3214b2f9799c56e9e5e8d390979e0c370332a69d072f780d903349f6115028fb9c35fc0abbc1b84290d380a8ec9e0ed20c
7
- data.tar.gz: 7756798eb9c1484c509eeddef382a1566dbfbe5fb00dbc133f1d0912589cbd44e6fa539dd5f1ab428de9a37abd2c5a2aa752354df04e299ddd697bfe3ca80e60
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.112"
5
+ VERSION = "0.8.113"
6
6
  end
7
7
  end
@@ -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,
@@ -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.112
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-17 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