lutaml-model 0.8.114 → 0.8.116

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: 381cba1519b14ff18965d44164e7d75e4564ac92c803815dad95277b7076ef70
4
- data.tar.gz: acd3466cbc30ed27ce60ce62b181732ab957abde427da1e609003ced88c47a67
3
+ metadata.gz: bd6d747592ab333f3edefaf4af21e7db87189a39c541bd7c70dda87b544615d8
4
+ data.tar.gz: 2380f18d9b82122602c3afc60695dd2077c2cc125375032673f90721d152fa53
5
5
  SHA512:
6
- metadata.gz: 8f48a3c4bc8a0242a714d945aae2aaca68ae6c0a8a87cedb038a1229363545bb41f1a3c24f833db86c89e3468cc216c77536af23385cce37e6ce20f8a2e3b142
7
- data.tar.gz: 9ba5616b479da6952fd44284792ce708712ed6576ba90fa11507581aefd344f30ccd22e5887e3200c714feff15e7e8c95742149f49ac2a5618b2d5ea0f0b742a
6
+ metadata.gz: d15bea65d1270d4eda9e07bbac48cee6232da11aab2df7bcbd51e74ab1ec8d9ecf2eb217b1b9d302cfdf128a2b51b610bd1062e7bfea1a81a65f68d16f952c43
7
+ data.tar.gz: 976d507e33e4a79de59b767215279bee14aaba443a05bfb8c84918190e236faa19d4383af9a0b3b20c3743dbd95e47404967b37a0611619738bd9fe4667af6bb
@@ -977,6 +977,24 @@ instance_object = nil)
977
977
  end
978
978
 
979
979
  klass = resolve_polymorphic_class(resolved_type, value, options)
980
+ # A scalar handed to a mapped-model attribute crashed
981
+ # apply_mappings with an internal NoMethodError (key? on
982
+ # String, #978). A type declaring its own .cast owns scalar
983
+ # construction; base-cast identity types fall through to the
984
+ # legacy conversion chain, whose parse error names the token.
985
+ if serializable_type?(register) && !value.nil? &&
986
+ !value.is_a?(::Hash) && !value.is_a?(::Array) &&
987
+ !value.respond_to?(:key?) && klass.respond_to?(:cast) &&
988
+ klass.method(:cast).owner == klass.singleton_class
989
+ casted = klass.cast(value)
990
+ return casted if casted.is_a?(klass)
991
+
992
+ raise Lutaml::Model::IncorrectModelError,
993
+ "attribute '#{name}': the #{format} input for type " \
994
+ "#{klass} is a #{value.class}, and #{klass}.cast did " \
995
+ "not build one (received #{casted.class})"
996
+ end
997
+
980
998
  if can_serialize?(klass, value, format, options)
981
999
  propagated = Serialize::DeserializationContext.propagate(options)
982
1000
  klass.apply_mappings(value, format,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.114"
5
+ VERSION = "0.8.116"
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
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # #978: a JSON scalar handed to a mapped-model attribute crashed with
6
+ # an internal NoMethodError (key? on String) wrapped as
7
+ # InvalidFormatError. A type that declares its own .cast owns scalar
8
+ # construction from that scalar; a cast-less type falls through to the
9
+ # legacy conversion chain, whose parse error surfaces the offending
10
+ # token — the remedy there is declaring .cast on the type.
11
+ RSpec.describe "from_json scalar into a mapped-model attribute" do
12
+ let(:marked_class) do
13
+ Class.new(Lutaml::Model::Serializable) do
14
+ attribute :content, :string
15
+
16
+ key_value do
17
+ map "content", to: :content
18
+ end
19
+
20
+ def self.cast(value)
21
+ return value if value.is_a?(self)
22
+
23
+ new(content: value.to_s)
24
+ end
25
+ end
26
+ end
27
+
28
+ let(:plain_class) do
29
+ Class.new(Lutaml::Model::Serializable) do
30
+ attribute :content, :string
31
+
32
+ key_value do
33
+ map "content", to: :content
34
+ end
35
+ end
36
+ end
37
+
38
+ let(:holder_class) do
39
+ Class.new(Lutaml::Model::Serializable) do
40
+ attribute :description, "ScalarMarked"
41
+ attribute :plain_desc, "ScalarPlain"
42
+
43
+ key_value do
44
+ map "description", to: :description
45
+ map "plain_desc", to: :plain_desc
46
+ end
47
+ end
48
+ end
49
+
50
+ before do
51
+ stub_const("ScalarMarked", marked_class)
52
+ stub_const("ScalarPlain", plain_class)
53
+ stub_const("ScalarHolder", holder_class)
54
+ end
55
+
56
+ it "constructs the mapped value through the type's .cast" do
57
+ model = ScalarHolder.from_json('{"description": "text"}')
58
+
59
+ expect(model.description).to be_a(ScalarMarked)
60
+ expect(model.description.content).to eq("text")
61
+ end
62
+
63
+ it "falls through to the format parse error for a cast-less mapped type" do
64
+ expect { ScalarHolder.from_json('{"plain_desc": "text"}') }
65
+ .to raise_error(
66
+ Lutaml::Model::InvalidFormatError,
67
+ /unexpected token 'text'/,
68
+ )
69
+ end
70
+ 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.114
4
+ version: 0.8.116
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
@@ -1750,6 +1750,7 @@ files:
1750
1750
  - spec/lutaml/model/enum_spec.rb
1751
1751
  - spec/lutaml/model/except_spec.rb
1752
1752
  - spec/lutaml/model/finalization_cache_spec.rb
1753
+ - spec/lutaml/model/from_json_scalar_cast_spec.rb
1753
1754
  - spec/lutaml/model/generator_defaults_spec.rb
1754
1755
  - spec/lutaml/model/global_context_spec.rb
1755
1756
  - spec/lutaml/model/global_register_spec.rb