lutaml-model 0.8.119 → 0.8.120

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: 8df481bfb5303c05153a8bda8eaa4996157a6060d34d70dd8eb835e461aff9f5
4
- data.tar.gz: a23f93de4f89b1a2b7925833786e9d0337ec5e94ec172723f5b2b70f78347d3c
3
+ metadata.gz: 5a2f85724acebb5e00bf61091dfd3da178b087700420af1f9c8168ea1eb7e579
4
+ data.tar.gz: 0220370237df57218c474fdc97f72dda7ca0e7183eff0d3aecea6397e7d56232
5
5
  SHA512:
6
- metadata.gz: adbc819876bf696f60009c53042dba835e5e557f32057f1aba4f58da56831c15f347d35dcf0160536a877f29f2d17ba59aadb8b1ac38b7fa9572b2d12d6c6dd2
7
- data.tar.gz: 9af887394f9b2e241f299cb8eabcc3e0202ec51dba5129477a16cb85234105f51c7198fc44b9c20c916fa89a1356918c4752bbd73afa8f83a5a49ffd3032f08d
6
+ metadata.gz: 6c2dfe1999803b5a232318a871fe71c07ccf7bb8050aa31691918e405b67d1b9bfdd41661f6ec6e7a9e3764c0d6124ed3e1d8d84dc0627dab28698d3f6ce615f
7
+ data.tar.gz: dc85cfcf2a10c301e80b533a679877f43df5e018428a5b1e19c8e2f50f545064088a955956bbbb6bd19462da5db995e16052ea6b4268fda05b8805a1f3ccb0d6
@@ -0,0 +1,9 @@
1
+ {
2
+ "repo": [
3
+ "metanorma/docbook",
4
+ "metanorma/metanorma-cli",
5
+ "metanorma/rfcxml",
6
+ "metanorma/uniword",
7
+ "ukiryu/ukiryu"
8
+ ]
9
+ }
@@ -14,22 +14,17 @@
14
14
  "lutaml/unibuf",
15
15
  "lutaml/xmi",
16
16
  "metanorma/atmospheric",
17
- "metanorma/docbook",
18
- "metanorma/metanorma-cli",
19
17
  "metanorma/modspec-ruby",
20
18
  "metanorma/niso-jats",
21
19
  "metanorma/prosereflect",
22
20
  "metanorma/revix",
23
- "metanorma/rfcxml",
24
21
  "metanorma/sts-ruby",
25
- "metanorma/uniword",
26
22
  "plurimath/mml",
27
23
  "plurimath/plurimath",
28
24
  "relaton/ieee-idams",
29
25
  "relaton/loc_mods",
30
26
  "relaton/w3c_api",
31
- "ukiryu/ukiryu",
32
27
  "unitsml/unitsdb-ruby",
33
28
  "unitsml/unitsml-ruby"
34
29
  ]
35
- }
30
+ }
@@ -18,10 +18,29 @@ on:
18
18
  types: [ release-passed ]
19
19
 
20
20
  jobs:
21
+ # Most dependents are pure data/format gems: their suites invoke no
22
+ # external tools, so skipping the tool install removes the Windows
23
+ # Chocolatey flake surface (choco CDN 504s killed cells pre-rspec,
24
+ # e.g. the ukiryu Windows leg) for the majority of the matrix.
21
25
  rake:
22
26
  uses: metanorma/ci/.github/workflows/dependent-rake.yml@main
23
27
  with:
24
28
  command: bundle exec rspec
29
+ repos-json-file: .github/workflows/dependent-repos.json
30
+ after-setup-ruby: |
31
+ if [ -f frontend/package.json ]; then cd frontend && npm install && npm run build; fi
32
+ secrets:
33
+ pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
34
+
35
+ # Dependents whose suites invoke the tools: ukiryu wraps
36
+ # inkscape/imagemagick/ghostscript and raises if absent, and its
37
+ # smoke suite exercises exiftool, yq and ffmpeg too; the metanorma
38
+ # renderers run full document renders.
39
+ rake-with-tools:
40
+ uses: metanorma/ci/.github/workflows/dependent-rake.yml@main
41
+ with:
42
+ command: bundle exec rspec
43
+ repos-json-file: .github/workflows/dependent-repos-tools.json
25
44
  setup-tools: 'inkscape,ghostscript,exiftool,ffmpeg,imagemagick,yq'
26
45
  after-setup-ruby: |
27
46
  if [ -f frontend/package.json ]; then cd frontend && npm install && npm run build; fi
@@ -32,9 +32,11 @@ module Lutaml
32
32
  # constant (the poison-process state the gem documents)
33
33
  # passes const_defined? and require while every parse fails,
34
34
  # and the native parse error then surfaces on valid input.
35
- # Idempotent; a missing or broken install reports false
36
- # instead of raising, so optional-engine callers (detection)
37
- # can gate on it and fall back to the standard lane.
35
+ # Any failure — missing native, engine error, or a binding and
36
+ # native binary out of sync (NameError on a missing FFI surface,
37
+ # yeptris 0.6.34.2 x64-mingw-ucrt) — reports false instead of
38
+ # raising, so optional-engine callers (detection) gate on it and
39
+ # fall back to the standard lane.
38
40
  def self.available?
39
41
  require_engine
40
42
  native_ready?
@@ -48,7 +50,7 @@ module Lutaml
48
50
  @native_ready = begin
49
51
  ::Yeptris::JSON.load('{"native_probe": 1}')
50
52
  true
51
- rescue ::Yeptris::Error, LoadError, NoMemoryError
53
+ rescue StandardError
52
54
  false
53
55
  end
54
56
  end
@@ -154,12 +154,15 @@ module Lutaml
154
154
  member.is_a?(::Class) && member.include?(Serialize)
155
155
  end
156
156
 
157
- # Structural (key-coverage) selection: every input key maps to one of
158
- # the member's mapped fields, with no input key left unmapped. Once
159
- # selected, field values cast leniently via the member's normal path.
160
- # An existing member instance conforms as-is, keeping conformance
161
- # idempotent — values can re-enter casting (e.g. a value transformer
162
- # builds the member before the cast runs).
157
+ # Structural selection: at least one input key maps to one of the
158
+ # member's mapped fields. Keys no rule claims are ignored exactly
159
+ # as they are when the same hash reaches the model outside a
160
+ # union — extension keys (x-** vendor keywords, forward-compatible
161
+ # vocabularies) must not defeat dispatch (#987). Once selected,
162
+ # field values cast leniently via the member's normal path. An
163
+ # existing member instance conforms as-is, keeping conformance
164
+ # idempotent — values can re-enter casting (e.g. a value
165
+ # transformer builds the member before the cast runs).
163
166
  def conform_model(member, value, format, register)
164
167
  return value if value.is_a?(member)
165
168
 
@@ -167,7 +170,7 @@ module Lutaml
167
170
  return :no_match if keys.nil? || keys.empty?
168
171
 
169
172
  field_names = member_field_names(member, format, register)
170
- return :no_match unless keys.all? do |key|
173
+ return :no_match unless keys.any? do |key|
171
174
  field_names.include?(key)
172
175
  end
173
176
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.119"
5
+ VERSION = "0.8.120"
6
6
  end
7
7
  end
@@ -46,9 +46,11 @@ module Lutaml
46
46
  # constant (the poison-process state the gem documents)
47
47
  # passes const_defined? and require while every parse fails,
48
48
  # and the native parse error then surfaces on valid input.
49
- # Idempotent; a missing or broken install reports false
50
- # instead of raising, so optional-engine callers (detection)
51
- # can gate on it and fall back to the standard lane.
49
+ # Any failure — missing native, engine error, or a binding and
50
+ # native binary out of sync (NameError on a missing FFI surface,
51
+ # yeptris 0.6.34.2 x64-mingw-ucrt) — reports false instead of
52
+ # raising, so optional-engine callers (detection) gate on it and
53
+ # fall back to the standard lane.
52
54
  def self.available?
53
55
  require_engine
54
56
  native_ready?
@@ -63,7 +65,7 @@ module Lutaml
63
65
  # rubocop:disable-next Naming/VariableNumber -- upstream schema literal
64
66
  ::Yeptris::YAML.load("native_probe: 1", schema: :compat_11)
65
67
  true
66
- rescue ::Yeptris::Error, LoadError, NoMemoryError
68
+ rescue StandardError
67
69
  false
68
70
  end
69
71
  end
@@ -214,6 +214,25 @@ module UnionAttributeSpec
214
214
  end
215
215
  end
216
216
 
217
+ # Union members for the #987 dispatch contract: an extension-carrying
218
+ # model (keys no rule maps must not defeat dispatch) and a holder
219
+ # mixing it with a scalar member.
220
+ class ExtendedShape < Lutaml::Model::Serializable
221
+ attribute :type, :string
222
+
223
+ key_value do
224
+ map "type", to: :type
225
+ end
226
+ end
227
+
228
+ class ExtendedHolder < Lutaml::Model::Serializable
229
+ attribute :value, [ExtendedShape, :boolean]
230
+
231
+ key_value do
232
+ map "value", to: :value
233
+ end
234
+ end
235
+
217
236
  RSpec.describe "Union-typed attributes (issue #190)" do
218
237
  describe "deserialization — member resolution by shape" do
219
238
  it "resolves a scalar string to the :string member" do
@@ -646,6 +665,38 @@ RSpec.describe "Union-typed attributes (issue #190)" do
646
665
  end
647
666
  end
648
667
 
668
+ describe "unmapped keys do not defeat model dispatch (#987)" do
669
+ # ExtendedShape / ExtendedHolder: extension-carrying objects (JSON
670
+ # Schema vocabularies, OpenAPI x-** keywords) carry keys no rule
671
+ # maps; the same hash reaches the model member fine outside a
672
+ # union, so it must inside one too.
673
+
674
+ it "selects the model member when mapped and unmapped keys mix" do
675
+ parsed = ExtendedHolder.from_json(
676
+ '{"value":{"type":"string","x-ext":1}}',
677
+ )
678
+
679
+ expect(parsed.value).to be_a(ExtendedShape)
680
+ expect(parsed.value.type).to eq("string")
681
+ end
682
+
683
+ it "still falls through when no key maps to any model member" do
684
+ parsed = ExtendedHolder.from_json('{"value":true}')
685
+
686
+ expect(parsed.value).to be(true)
687
+ end
688
+
689
+ it "returns nil only when no member conforms at all" do
690
+ holder = Class.new(Lutaml::Model::Serializable) do
691
+ attribute :value, [ExtendedShape, :boolean]
692
+ key_value { map "value", to: :value }
693
+ end
694
+ stub_const("ExtendedHolderNinetyEight", holder)
695
+
696
+ expect(holder.from_json('{"value":{"unrelated":1}}').value).to be_nil
697
+ end
698
+ end
699
+
649
700
  describe "value transformers (cast idempotence)" do
650
701
  it "keeps a model member built by transform: before the cast runs" do
651
702
  reading = UnionAttributeSpec::TransformedReading.from_yaml(<<~YAML)
@@ -70,7 +70,17 @@ RSpec.describe "yeptris adapter error parity" do
70
70
  RUBY
71
71
  end
72
72
 
73
- before { skip "yeptris is not installed" unless yeptris_installed? }
73
+ # Installed is not enough: a binding/native desync (yeptris
74
+ # 0.6.34.2 x64-mingw-ucrt) leaves the gem resolvable while every
75
+ # parse raises. The parity contract only holds when the engines
76
+ # actually answer a probe — detection then selects them.
77
+ def yeptris_usable?
78
+ yeptris_installed? &&
79
+ Lutaml::Json::Adapter::YeptrisAdapter.available? &&
80
+ Lutaml::Yaml::Adapter::YeptrisAdapter.available?
81
+ end
82
+
83
+ before { skip "yeptris is not usable on this platform" unless yeptris_usable? }
74
84
 
75
85
  it "prefers the yeptris adapters when the engine is installed" do
76
86
  expect(outcomes).to include("yaml-adapter: Lutaml::Yaml::Adapter::YeptrisAdapter")
@@ -27,12 +27,16 @@ RSpec.describe Lutaml::Model::AdapterResolver, ".adapter_for yeptris native dete
27
27
  RUBY
28
28
  end
29
29
 
30
+ # The gem resolving is not enough: a binding/native desync leaves
31
+ # yeptris loadable while every parse raises. The healthy-lane spec
32
+ # is only meaningful where the native answers a probe.
30
33
  def subprocess_yeptris_capable?
31
34
  probe = <<~RUBY
32
35
  begin
33
36
  require "yeptris"
34
- exit(0)
35
- rescue LoadError
37
+ require "lutaml/json/adapter/yeptris_adapter"
38
+ exit(Lutaml::Json::Adapter::YeptrisAdapter.available? ? 0 : 1)
39
+ rescue LoadError, StandardError
36
40
  exit(1)
37
41
  end
38
42
  RUBY
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.119
4
+ version: 0.8.120
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-20 00:00:00.000000000 Z
11
+ date: 2026-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -155,6 +155,7 @@ files:
155
155
  - ".envrc"
156
156
  - ".github/scripts/test_installed_gem.rb"
157
157
  - ".github/workflows/dependent-repos-todo.json"
158
+ - ".github/workflows/dependent-repos-tools.json"
158
159
  - ".github/workflows/dependent-repos.json"
159
160
  - ".github/workflows/dependent-tests.yml"
160
161
  - ".github/workflows/docs.yml"