lutaml-model 0.8.35 → 0.8.36

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: 0a5f36f080c15bb2639142a36b424853d26458732c867963dbf7a99d6a1cd189
4
- data.tar.gz: 3159ba755e2cc317e5e9b3785b2be1cffb08e424b2bd792bca34f20c853d25be
3
+ metadata.gz: 13023a5add888c4affa15d1e7c025548e329b0aad8112582550be7cff794524a
4
+ data.tar.gz: 7393786a16da4f06d7962f7395edab78b5985ab1820c98da8b95f66f669be99d
5
5
  SHA512:
6
- metadata.gz: e83080b05e3277310d103e84c5f63fda7e963e25bb1cdc764934a1d931e388f212b51f97c5a02182966b3e376a97d00fc30fa396568a644100b5745064d1fd1a
7
- data.tar.gz: f59d08e3915dacf3a7da3c0bfbc2e9b300cb7c66177c6dd4455870349fc4f2cb892d8d34a1028b74f2660b87e21980b44a4e941329fcf8a69687fde9a7d336f0
6
+ metadata.gz: 03be6773d23935e26c6593d2c966f5063af4840142f988c6db534517b26b7889adc15c11c5d5280b181b9b19e35a971bbbc889ce673d63a750385e5b392d60b7
7
+ data.tar.gz: 656f73aab6fe55e9dbe8337a8c6ec76fae5c2cadfade74bdd30500d3ea16105cdf3de1ebce9d837b4ddd3c6851e84ac7b5b7a7f40f2729d79827014e9d0a0b9e
@@ -436,18 +436,31 @@ module Lutaml
436
436
  end
437
437
 
438
438
  def cast_element(value, register)
439
- # Resolve first: an undeclared type has to raise UnknownTypeError even
440
- # when the value carries nothing, the way it did before the guard
441
- # below existed.
442
- resolved_type = type(register)
439
+ # Resolve first: an undeclared type has to raise UnknownTypeError
440
+ # even for a nil value (the 0.8.33 behavior).
441
+ type(register)
442
+
443
+ return value if Utils.uninitialized?(value)
444
+
445
+ # nil reaches the type's cast — the long-shipped contract
446
+ # ST_OnOff-style types depend on (absent attribute -> cast(nil)
447
+ # -> false; lutaml-model#795). What nil must NOT do is
448
+ # manufacture a typed INSTANCE from nothing: that is the
449
+ # phantom #793 removed, suppressed by the post-check below.
450
+ if value.nil?
451
+ result = cast_element_present(value, register)
452
+ return result if result.nil?
453
+ return nil if result.is_a?(Lutaml::Model::Type::Value) ||
454
+ result.is_a?(Lutaml::Model::Serialize)
443
455
 
444
- # Casting is for data. Every built-in type already hands nil and the
445
- # uninitialized sentinel straight back, so this changes nothing for
446
- # them. A type with its own `self.cast` returns a real instance
447
- # instead, and that instance becomes a value nobody wrote — a phantom
448
- # element in the document, or a one-item collection the source never
449
- # contained. `cast` below guards the format entry point the same way.
450
- return value if no_data?(value)
456
+ return result
457
+ end
458
+
459
+ cast_element_present(value, register)
460
+ end
461
+
462
+ def cast_element_present(value, register)
463
+ resolved_type = type(register)
451
464
 
452
465
  return cast_union(value, nil, register) if union?
453
466
  return resolved_type.new(value) if value.is_a?(::Hash) && !hash_type?
@@ -785,14 +798,25 @@ instance_object = nil)
785
798
  end
786
799
 
787
800
  def cast(value, format, register, options = {})
788
- # Same rule as cast_element, for the format entry point. from_json /
789
- # from_yaml / from_toml / from_hash reach a missing key as nil, and a
790
- # type with its own `self.cast` turns that nil into an instance the
791
- # document never carried. Unlike cast_element this returns before
792
- # resolving the type: resolution here depends on the format options
793
- # below, and an undeclared type still raises through cast_element.
794
- return value if no_data?(value)
801
+ # Sentinel: never a cast input. nil flows on absent values
802
+ # reach the type's cast (ST_OnOff booleans, #795); a cast that
803
+ # manufactures a typed instance from nothing is the phantom
804
+ # #793 removed, suppressed here as in cast_element.
805
+ return value if Utils.uninitialized?(value)
806
+
807
+ if value.nil?
808
+ result = cast_present(value, format, register, options)
809
+ return result if result.nil?
810
+ return nil if result.is_a?(Lutaml::Model::Type::Value) ||
811
+ result.is_a?(Lutaml::Model::Serialize)
812
+
813
+ return result
814
+ end
815
+
816
+ cast_present(value, format, register, options)
817
+ end
795
818
 
819
+ def cast_present(value, format, register, options = {})
796
820
  # Namespace-aware type resolution: use type_with_namespace if namespace_uri provided
797
821
  namespace_uri = options[:namespace_uri]
798
822
  resolved_type = if options[:resolved_type]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.35"
5
+ VERSION = "0.8.36"
6
6
  end
7
7
  end
@@ -402,7 +402,7 @@ instance_is_serialize = nil)
402
402
  # dup: XmlElement#order hands back a frozen cache shared with the
403
403
  # DOM. The model's copy has to stay mutable so callers can maintain
404
404
  # element_order themselves.
405
- instance.element_order = doc.root.order.dup
405
+ instance.element_order = doc.root.order
406
406
  if instance_is_serialize && doc.root.is_a?(::Lutaml::Xml::XmlElement)
407
407
  instance.attribute_order = doc.root.attribute_order
408
408
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # The 0.8.33 parse contracts restored after #795: a custom type's cast
6
+ # sees an absent attribute's nil (ST_OnOff booleans — an absent flag is
7
+ # semantically false), and the parsed element_order array is frozen
8
+ # (consumers build thaw-on-demand helpers on that).
9
+ RSpec.describe "absent-value and order contracts" do
10
+ before do
11
+ stub_const("OnOff", Class.new(Lutaml::Model::Type::Boolean) do
12
+ def self.cast(value, _options = {})
13
+ return false if value.nil?
14
+
15
+ super
16
+ end
17
+ end)
18
+
19
+ stub_const("Doc", Class.new(Lutaml::Model::Serializable) do
20
+ attribute :flag, OnOff
21
+ xml do
22
+ element "doc"
23
+ map_attribute "flag", to: :flag
24
+ end
25
+ end)
26
+ end
27
+
28
+ it "casts an absent attribute through the type (nil -> false)" do
29
+ expect(Doc.from_xml("<doc/>").flag).to be(false)
30
+ end
31
+
32
+ it "still casts a present attribute" do
33
+ expect(Doc.from_xml('<doc flag="0"/>').flag).to be(false)
34
+ expect(Doc.from_xml('<doc flag="1"/>').flag).to be(true)
35
+ end
36
+
37
+ it "hands the parsed element_order back frozen" do
38
+ expect(Doc.from_xml('<doc flag="1"/>').element_order).to be_frozen
39
+ end
40
+
41
+ it "does not manufacture typed instances from nothing" do
42
+ stub_const("PhantomType", Class.new(Lutaml::Model::Type::String) do
43
+ # Value#initialize casts what it is given, so a cast that
44
+ # constructs has to bypass it (see phantom_value_spec's Eager).
45
+ def initialize(value) # rubocop:disable Lint/MissingSuper
46
+ @value = value
47
+ end
48
+
49
+ def self.cast(value, _options = {})
50
+ return value if value.is_a?(PhantomType)
51
+
52
+ new("phantom(#{value.inspect})")
53
+ end
54
+ end)
55
+ stub_const("PhantomDoc", Class.new(Lutaml::Model::Serializable) do
56
+ attribute :phantom, PhantomType
57
+ xml do
58
+ element "k"
59
+ map_attribute "p", to: :phantom
60
+ end
61
+ end)
62
+
63
+ expect(PhantomDoc.from_xml("<k/>").phantom).to be_nil
64
+ end
65
+ end
@@ -294,12 +294,18 @@ RSpec.describe "parsed model mutation" do
294
294
  end
295
295
  end
296
296
 
297
- # Defect 3: element_order was frozen on parsed models.
297
+ # element_order on a parsed model: the parsed order array is frozen
298
+ # (the 0.8.33 contract consumers build thaw-on-demand helpers on,
299
+ # lutaml-model#795). Mutation goes through reassignment — the
300
+ # accessor is a public writer.
298
301
  describe "element_order on a parsed model" do
299
- it "is mutable" do
302
+ it "is reassignable" do
300
303
  model = ParsedModelMutationSpec::Mixed.from_xml('<p><a v="1"/></p>')
301
304
 
302
- expect { model.element_order << "x" }.not_to raise_error
305
+ expect(model.element_order).to be_frozen
306
+ expect do
307
+ model.element_order = model.element_order.dup << "x"
308
+ end.not_to raise_error
303
309
  end
304
310
  end
305
311
 
@@ -467,7 +473,7 @@ RSpec.describe "parsed model mutation" do
467
473
  it "serializes without raising when element_order holds an Integer" do
468
474
  model = ParsedModelMutationSpec::NilColl
469
475
  .from_xml("<s><lead>L</lead><item>a</item></s>")
470
- model.element_order << 42
476
+ model.element_order = model.element_order.dup << 42
471
477
 
472
478
  expect { model.to_xml }.not_to raise_error
473
479
  end
@@ -475,7 +481,7 @@ RSpec.describe "parsed model mutation" do
475
481
  it "still reconciles the well-formed entries around a foreign one" do
476
482
  model = ParsedModelMutationSpec::NilColl
477
483
  .from_xml("<s><lead>L</lead><item>a</item></s>")
478
- model.element_order.insert(1, 42)
484
+ model.element_order = model.element_order.dup.insert(1, 42)
479
485
  model.items << "b"
480
486
 
481
487
  xml = model.to_xml
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.35
4
+ version: 0.8.36
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-09-17 00:00:00.000000000 Z
11
+ date: 2026-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -1685,6 +1685,7 @@ files:
1685
1685
  - spec/lutaml/key_value/transformation/rule_compiler_spec.rb
1686
1686
  - spec/lutaml/key_value/transformation/value_serializer_spec.rb
1687
1687
  - spec/lutaml/key_value/transformation_spec.rb
1688
+ - spec/lutaml/model/absent_value_contracts_spec.rb
1688
1689
  - spec/lutaml/model/attribute_apply_value_map_spec.rb
1689
1690
  - spec/lutaml/model/attribute_collection_spec.rb
1690
1691
  - spec/lutaml/model/attribute_default_cache_spec.rb