lutaml-model 0.8.145 → 0.8.147

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: 1d4a991baea8734712513f2d1ffb0025d0a5867f174a40b32bee58b1be5017c5
4
- data.tar.gz: f1713d5b0ef5024da66ee23f8251604da86fd3f8d7035e7f64118bf080ea9128
3
+ metadata.gz: 73297b741e0c6f033811660bef32b0bcd51529d9ebd517ec3c67cd386e7134e6
4
+ data.tar.gz: a8fd9b7f9f0d6d95fd40441a116dc8e81639b8ef8e79c5c9c026c7d82691a7bf
5
5
  SHA512:
6
- metadata.gz: db827973c88ea9fe1779a0105d18ef1d0f93868fa4a59147a029c504005ec5f5e50378a7f90c490097e24d20c62fb33531a8fe277fb7620ed3cd17debdd2adaf
7
- data.tar.gz: 90045bda2ce6c2605ca3708527c44ad7811cacb6cb8d4f72d83f3a875a3a043a84503dc48f24f716d94f5e92e367d6a4bfb2fa556982d14db65125623d885252
6
+ metadata.gz: 24717f565b570a808908949176ba5b006fc1b176018776a85f62df04d63e152bb8fc6e5c88badca9d7ede0911cffe79b65c908de9fec356af56f4fc7ba0a7b2e
7
+ data.tar.gz: a3203db4195bf6f4a5d32529febd708ab574c07e0d7b93dfdb64674d1e0bdd37c5a2d5965e8506d023ea90c70d620622c38d43b3a02072f1fc4c014483b65590
@@ -17,7 +17,7 @@ module Lutaml
17
17
 
18
18
  begin
19
19
  results << YAML.safe_load(yaml, aliases: true)
20
- rescue Psych::SyntaxError => e
20
+ rescue *Lutaml::Model::Utils.yaml_parse_errors => e
21
21
  warn "Skipping invalid yaml: #{e.message}"
22
22
  end
23
23
  end
@@ -34,6 +34,17 @@ module Lutaml
34
34
  false
35
35
  end
36
36
 
37
+ # Parse-error classes of every YAML engine that may be answering
38
+ # ::YAML in this process. A host bundle can rebind ::YAML (a
39
+ # Psych drop-in), so the multi-document skip-rescues must cover
40
+ # each loaded engine's parse error, not only Psych's (#1053: a
41
+ # raw Yeptris::ParseError escaped from_yamls).
42
+ def yaml_parse_errors
43
+ errors = [Psych::SyntaxError]
44
+ errors << Yeptris::ParseError if defined?(Yeptris::ParseError)
45
+ errors
46
+ end
47
+
37
48
  # Convert string to camel case (cached)
38
49
  def camel_case(str)
39
50
  return "" if str.nil? || str.empty?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.145"
5
+ VERSION = "0.8.147"
6
6
  end
7
7
  end
@@ -94,6 +94,50 @@ module Lutaml
94
94
  instance
95
95
  end
96
96
 
97
+ # leptris#1874: snapshot rows -> the ns_data hash
98
+ # ({path => {prefix_key => {uri:, prefix:, format:}}}), the
99
+ # exact shape the recursion builds. Public: it is the
100
+ # contract the binding's snapshot rows must satisfy.
101
+ def ns_data_from_snapshot_rows(rows)
102
+ rows.each_with_object({}) do |row, result|
103
+ prefix = row[:prefix]
104
+ # One element carrying several declarations emits one
105
+ # row PER declaration with the same path — merge them,
106
+ # exactly as the recursion accumulates per path.
107
+ (result[row[:path]] ||= {})[row[:prefix] || :default] = {
108
+ uri: row[:href], prefix: prefix,
109
+ format: prefix ? :prefix : :default
110
+ }
111
+ end
112
+ end
113
+
114
+ def collect_native_namespaces(element, path = [], result = {})
115
+ if element.respond_to?(:namespace_declarations_snapshot)
116
+ return ns_data_from_snapshot_rows(
117
+ element.namespace_declarations_snapshot,
118
+ )
119
+ end
120
+
121
+ own = element.namespace_definitions
122
+ unless own.empty?
123
+ namespaces = {}
124
+ own.each do |ns|
125
+ key = ns.prefix || :default
126
+ namespaces[key] = { uri: ns.href, prefix: ns.prefix,
127
+ format: ns.prefix ? :prefix : :default }
128
+ end
129
+ result[path.dup] = namespaces
130
+ end
131
+ element.children.each do |child|
132
+ next unless child.is_a?(::Leptris::XML::Element)
133
+
134
+ path.push(child.name)
135
+ collect_native_namespaces(child, path, result)
136
+ path.pop
137
+ end
138
+ result
139
+ end
140
+
97
141
  private
98
142
 
99
143
  def record_declaration_plan(instance, node, model_class)
@@ -123,27 +167,15 @@ module Lutaml
123
167
  # Iterates children with an inline element filter:
124
168
  # element_children materializes a filtered array per node,
125
169
  # which the niso allocation gate caught at scale.
126
- def collect_native_namespaces(element, path = [], result = {})
127
- own = element.namespace_definitions
128
- unless own.empty?
129
- namespaces = {}
130
- own.each do |ns|
131
- key = ns.prefix || :default
132
- namespaces[key] = { uri: ns.href, prefix: ns.prefix,
133
- format: ns.prefix ? :prefix : :default }
134
- end
135
- result[path.dup] = namespaces
136
- end
137
- element.children.each do |child|
138
- next unless child.is_a?(::Leptris::XML::Element)
139
-
140
- path.push(child.name)
141
- collect_native_namespaces(child, path, result)
142
- path.pop
143
- end
144
- result
145
- end
146
-
170
+ #
171
+ # leptris#1874: when the binding exposes the bulk snapshot
172
+ # face (namespace_declarations_snapshot — one FFI crossing
173
+ # for the whole subtree; shipped engine-side in 1.9.387),
174
+ # the whole-subtree Ruby recursion collapses into a single
175
+ # call. Rows: {path: [names...], prefix: String|nil,
176
+ # href: String} in pre-order, paths relative to the
177
+ # receiver. The face probe keeps older bindings on the
178
+ # recursion unchanged.
147
179
  def register
148
180
  Lutaml::Model::Config.default_register
149
181
  end
@@ -86,7 +86,9 @@ module Lutaml
86
86
  "import",
87
87
  model,
88
88
  schema,
89
- namespace: schema.attributes["namespace"].value,
89
+ # An xs:import without a namespace attribute is legal
90
+ # (XSD 1.0 §3.13.2); the raw read stays nil-safe (#1051).
91
+ namespace: schema.attributes["namespace"]&.value,
90
92
  )
91
93
  end
92
94
  end
@@ -22,7 +22,7 @@ module Lutaml
22
22
  end
23
23
 
24
24
  results
25
- rescue Psych::SyntaxError => e
25
+ rescue *Lutaml::Model::Utils.yaml_parse_errors => e
26
26
  warn "Skipping invalid yaml: #{e.message}"
27
27
  parse_with_split(yamls)
28
28
  end
@@ -36,7 +36,7 @@ module Lutaml
36
36
  begin
37
37
  doc = YAML.safe_load(yaml, aliases: true)
38
38
  results << doc unless doc.nil?
39
- rescue Psych::SyntaxError => e
39
+ rescue *Lutaml::Model::Utils.yaml_parse_errors => e
40
40
  warn "Skipping invalid yaml: #{e.message}"
41
41
  end
42
42
  end
@@ -154,6 +154,38 @@ RSpec.describe "Yamls" do
154
154
  end.to output(/Skipping invalid yaml: /).to_stderr
155
155
  end
156
156
 
157
+ # #1053: a host bundle can rebind ::YAML to another engine (a Psych
158
+ # drop-in), so the multi-doc lane must treat every loaded engine's
159
+ # parse error as a skippable document error — never let a raw engine
160
+ # error escape.
161
+ describe "engine parse errors" do
162
+ it "rescues every loaded YAML engine's parse errors" do
163
+ errors = Lutaml::Model::Utils.yaml_parse_errors
164
+
165
+ expect(errors).to include(Psych::SyntaxError)
166
+ if defined?(Yeptris::ParseError)
167
+ expect(errors).to include(Yeptris::ParseError)
168
+ end
169
+ end
170
+
171
+ it "skips an invalid document when the active engine raises its own parse error" do
172
+ skip "yeptris not loadable in this process" unless Lutaml::Model::Utils.safe_load("yeptris", :Yeptris)
173
+
174
+ allow(YAML).to receive(:load_stream).and_raise(
175
+ Yeptris::ParseError.new("parse failed: unexpected parser state at line 11, column 26"),
176
+ )
177
+
178
+ expect do
179
+ expect(Lutaml::Yamls::Adapter::StandardAdapter.parse(invalid_yamls_content)).to eq(
180
+ [
181
+ { "full_name" => "John", "age" => 30, "address" => { "city" => "New York" } },
182
+ { "full_name" => "Bob", "age" => 35, "address" => { "city" => "Paris" } },
183
+ ],
184
+ )
185
+ end.to output(/Skipping invalid yaml: /).to_stderr
186
+ end
187
+ end
188
+
157
189
  describe "parsing" do
158
190
  it "handles empty documents" do
159
191
  yamls_with_empty_docs = <<~YAMLS
@@ -0,0 +1,61 @@
1
+ require "spec_helper"
2
+ require_relative "../../../../lib/lutaml/model"
3
+
4
+ # leptris#1874: the plan hydrator's namespace collection collapses the
5
+ # whole-subtree Ruby recursion (one namespace_definitions FFI crossing
6
+ # per node) into the engine's bulk snapshot face when the binding
7
+ # exposes it. These specs pin the ROW CONTRACT the binding must
8
+ # satisfy and the dispatch, so the binding PR can be verified without
9
+ # waiting for the native pin bump.
10
+ # A real lightweight struct standing in for the future binding
11
+ # element: responds to the bulk face with fixed rows, carries no
12
+ # behavior of its own.
13
+ SnapshotFaceElement = Struct.new(:rows) do
14
+ def namespace_declarations_snapshot
15
+ rows
16
+ end
17
+ end
18
+
19
+ RSpec.describe Lutaml::Xml::Leptris::PlanHydrator do
20
+ describe ".ns_data_from_snapshot_rows" do
21
+ it "builds the ns_data hash from pre-order snapshot rows" do
22
+ rows = [
23
+ { path: [], prefix: nil, href: "urn:d" },
24
+ { path: [], prefix: "a", href: "urn:a" },
25
+ { path: ["c1"], prefix: "b", href: "urn:b" },
26
+ { path: ["c2"], prefix: nil, href: "urn:d2" },
27
+ ]
28
+
29
+ result = described_class.ns_data_from_snapshot_rows(rows)
30
+
31
+ expect(result).to eq(
32
+ [] => { default: { uri: "urn:d", prefix: nil,
33
+ format: :default },
34
+ "a" => { uri: "urn:a", prefix: "a", format: :prefix } },
35
+ ["c1"] => { "b" => { uri: "urn:b", prefix: "b",
36
+ format: :prefix } },
37
+ ["c2"] => { default: { uri: "urn:d2", prefix: nil,
38
+ format: :default } },
39
+ )
40
+ end
41
+
42
+ it "returns an empty hash for a declaration-free subtree" do
43
+ expect(described_class.ns_data_from_snapshot_rows([])).to eq({})
44
+ end
45
+ end
46
+
47
+ describe "the snapshot face probe in collect_native_namespaces" do
48
+ it "prefers the bulk face when the binding exposes it" do
49
+ element = SnapshotFaceElement.new(
50
+ [{ path: ["x"], prefix: "p", href: "urn:p" }],
51
+ )
52
+
53
+ result = described_class.collect_native_namespaces(element)
54
+
55
+ expect(result).to eq(
56
+ ["x"] => { "p" => { uri: "urn:p", prefix: "p",
57
+ format: :prefix } },
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require "fileutils"
5
+ require_relative "../../../../spec_helper"
6
+ require "lutaml/xml/schema/xsd"
7
+
8
+ # #1051: an xs:import without a namespace attribute is legal
9
+ # (XSD 1.0 §3.13.2 — it imports a schema document with no target
10
+ # namespace). The schema walk read the attribute store unguarded, so
11
+ # a follow-up schema document carrying such an import crashed the
12
+ # parse with "undefined method 'value' for nil".
13
+ RSpec.describe "Xsd import without a namespace attribute" do
14
+ let(:dir) { Dir.mktmpdir }
15
+
16
+ before do
17
+ File.write(File.join(dir, "a.xsd"), <<~XSD)
18
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
19
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="b.xsd"/>
20
+ <xs:element name="root"/>
21
+ </xs:schema>
22
+ XSD
23
+ File.write(File.join(dir, "b.xsd"), <<~XSD)
24
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
25
+ <xs:import schemaLocation="c.xsd"/>
26
+ <xs:attribute name="lang" type="xs:language"/>
27
+ </xs:schema>
28
+ XSD
29
+ File.write(File.join(dir, "c.xsd"), <<~XSD)
30
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
31
+ <xs:element name="helper"/>
32
+ </xs:schema>
33
+ XSD
34
+ end
35
+
36
+ after { FileUtils.remove_entry(dir) }
37
+
38
+ it "parses the whole chain" do
39
+ parsed = Lutaml::Xml::Schema::Xsd.parse(
40
+ File.read(File.join(dir, "a.xsd")),
41
+ location: dir,
42
+ )
43
+
44
+ expect(parsed).to be_a(Lutaml::Xml::Schema::Xsd::Schema)
45
+ expect(parsed.imports.first.schema_path).to eq("b.xsd")
46
+ expect(parsed.import.first).to be_a(Lutaml::Xml::Schema::Xsd::Schema)
47
+ end
48
+
49
+ it "keeps the namespace-less import unnamed" do
50
+ parsed = Lutaml::Xml::Schema::Xsd.parse(
51
+ <<~XSD,
52
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
53
+ <xs:import schemaLocation="c.xsd"/>
54
+ </xs:schema>
55
+ XSD
56
+ location: dir,
57
+ )
58
+
59
+ expect(parsed.imports.first.namespace).to be_nil
60
+ end
61
+ 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.145
4
+ version: 0.8.147
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-28 00:00:00.000000000 Z
11
+ date: 2026-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -1990,6 +1990,7 @@ files:
1990
1990
  - spec/lutaml/xml/general_namespace_spec.rb
1991
1991
  - spec/lutaml/xml/html_entities_spec.rb
1992
1992
  - spec/lutaml/xml/indent_spec.rb
1993
+ - spec/lutaml/xml/leptris/plan_hydrator_ns_snapshot_spec.rb
1993
1994
  - spec/lutaml/xml/leptris_string_encoding_spec.rb
1994
1995
  - spec/lutaml/xml/line_ending_spec.rb
1995
1996
  - spec/lutaml/xml/mapping_finalization_guard_spec.rb
@@ -2054,6 +2055,7 @@ files:
2054
2055
  - spec/lutaml/xml/schema/xsd/errors/suggestion_spec.rb
2055
2056
  - spec/lutaml/xml/schema/xsd/file_validation_result_spec.rb
2056
2057
  - spec/lutaml/xml/schema/xsd/glob_spec.rb
2058
+ - spec/lutaml/xml/schema/xsd/import_without_namespace_spec.rb
2057
2059
  - spec/lutaml/xml/schema/xsd/keyref_refer_spec.rb
2058
2060
  - spec/lutaml/xml/schema/xsd/liquid_methods/attribute_and_group_spec.rb
2059
2061
  - spec/lutaml/xml/schema/xsd/liquid_methods/complex_type_spec.rb