lutaml-model 0.8.65 → 0.8.66
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 +4 -4
- data/docs/_guides/native-engines.adoc +7 -0
- data/lib/lutaml/json/adapter/yeptris_adapter.rb +24 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/yaml/adapter/yeptris_adapter.rb +34 -2
- data/spec/lutaml/json/yeptris_adapter_spec.rb +7 -2
- data/spec/lutaml/model/yeptris_error_parity_spec.rb +95 -0
- data/spec/lutaml/yaml/yeptris_adapter_spec.rb +29 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1a4f0f42471c24f887cb31b50be356825d1bb538ca568b30d15a703928c7d0b
|
|
4
|
+
data.tar.gz: b2b5227d2f505a10921d41123caf0f04b2cafec68ab0318db49690d394d1309a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 191a88d3dc8eacc1e94c8ea2b033500dbb4ce77345feefb6e7328527c4648c67575d10db7f9cb9e4d9611fada0902210077ae5c6ec0fcc4ff457b8637f35e2ef
|
|
7
|
+
data.tar.gz: fe6e34251e26d5593ec07873e5f0320a0802e0cd3c86ec89b8492c9add49c11f919dedd4aef225f6b8b63ca6564c0d3841f88d16b614fe265c8ebf3fb27fdb00
|
|
@@ -106,6 +106,13 @@ Lutaml::Model::Config.toml_adapter_type = :teptris
|
|
|
106
106
|
raising `Psych::DisallowedClass`.
|
|
107
107
|
* **JSON**: `yeptris` parsing targets exact `JSON.parse` semantics;
|
|
108
108
|
generation stays on the `json` gem.
|
|
109
|
+
* **Error classes**: parse failures keep the standard adapters' contract
|
|
110
|
+
on every engine — the yeptris adapters re-raise the engine's syntax
|
|
111
|
+
errors as `Psych::SyntaxError` (YAML, with the engine's line, column,
|
|
112
|
+
and problem text) and `JSON::ParserError` (JSON), and the model layer
|
|
113
|
+
wraps them as `InvalidFormatError` just as it does for Psych and the
|
|
114
|
+
`json` gem. Code that rescues those classes does not change when a
|
|
115
|
+
native engine is added to the bundle.
|
|
109
116
|
* **TOML**: the teptris adapter mirrors tomlib value semantics — offset
|
|
110
117
|
and local datetimes become `Time`, dates become `Date`, local times
|
|
111
118
|
stay `String`. Parse failures raise `Teptris::ParseError` with line
|
|
@@ -12,9 +12,32 @@ module Lutaml
|
|
|
12
12
|
# load-only), so everything else inherits from the standard adapter.
|
|
13
13
|
class YeptrisAdapter < StandardAdapter
|
|
14
14
|
def self.parse(json, _options = {})
|
|
15
|
-
|
|
15
|
+
require_engine
|
|
16
16
|
::Yeptris::JSON.load(json)
|
|
17
|
+
rescue ::Yeptris::JSON::ParseError => e
|
|
18
|
+
raise parser_error(e)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The engine require sits outside parse's rescue so a broken
|
|
22
|
+
# install still surfaces its LoadError (the detection fallback's
|
|
23
|
+
# contract) instead of a NameError from the rescue clause.
|
|
24
|
+
def self.require_engine
|
|
25
|
+
require "yeptris"
|
|
26
|
+
end
|
|
27
|
+
private_class_method :require_engine
|
|
28
|
+
|
|
29
|
+
# The parse-error contract is the standard adapter's: invalid
|
|
30
|
+
# input raises JSON::ParserError regardless of the engine, so
|
|
31
|
+
# consumers' rescue ladders (and this gem's own
|
|
32
|
+
# format_error_types conversion to InvalidFormatError) hold on
|
|
33
|
+
# every adapter. Without the json gem the engine error
|
|
34
|
+
# propagates unchanged.
|
|
35
|
+
def self.parser_error(error)
|
|
36
|
+
return error unless defined?(::JSON::ParserError)
|
|
37
|
+
|
|
38
|
+
::JSON::ParserError.new(error.message)
|
|
17
39
|
end
|
|
40
|
+
private_class_method :parser_error
|
|
18
41
|
end
|
|
19
42
|
end
|
|
20
43
|
end
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -14,18 +14,50 @@ module Lutaml
|
|
|
14
14
|
# Semantic notes vs the standard adapter's Psych.safe_load:
|
|
15
15
|
# implicit typing is parity-verified (compat_11 schema); anchors
|
|
16
16
|
# and aliases always resolve; a tagged value without a core type
|
|
17
|
-
# materializes as plain data instead of raising DisallowedClass
|
|
17
|
+
# materializes as plain data instead of raising DisallowedClass;
|
|
18
|
+
# parse failures surface as Psych::SyntaxError like the standard
|
|
19
|
+
# adapter's (the engine's own ParseError is re-raised under the
|
|
20
|
+
# standard class).
|
|
18
21
|
class YeptrisAdapter < StandardAdapter
|
|
19
22
|
# yeptris is required lazily: the ruby-platform variant installs
|
|
20
23
|
# everywhere but only loads where libyeptris exists (no Windows
|
|
21
24
|
# prebuilts) — a broken install must degrade to detection
|
|
22
25
|
# fallback, never crash loads.
|
|
23
26
|
def self.parse(yaml, _options = {})
|
|
24
|
-
|
|
27
|
+
require_engine
|
|
25
28
|
require "yeptris/yaml"
|
|
26
29
|
# rubocop:disable-next Naming/VariableNumber -- upstream schema literal
|
|
27
30
|
Yeptris::YAML.load(yaml, schema: :compat_11)
|
|
31
|
+
rescue Yeptris::ParseError => e
|
|
32
|
+
raise syntax_error(e)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The engine require sits outside parse's rescue so a broken
|
|
36
|
+
# install still surfaces its LoadError (the detection fallback's
|
|
37
|
+
# contract) instead of a NameError from the rescue clause.
|
|
38
|
+
def self.require_engine
|
|
39
|
+
require "yeptris"
|
|
40
|
+
end
|
|
41
|
+
private_class_method :require_engine
|
|
42
|
+
|
|
43
|
+
# The parse-error contract is the standard adapter's: invalid
|
|
44
|
+
# input raises Psych::SyntaxError regardless of the engine, so
|
|
45
|
+
# consumers' rescue ladders (and this gem's own
|
|
46
|
+
# format_error_types conversion to InvalidFormatError) hold on
|
|
47
|
+
# every adapter. The C parser reports the position inside the
|
|
48
|
+
# error message ("... at line L, column C"); lift it into the
|
|
49
|
+
# structured fields. Without psych (Opal) the engine error
|
|
50
|
+
# propagates unchanged.
|
|
51
|
+
def self.syntax_error(error)
|
|
52
|
+
return error unless defined?(::Psych::SyntaxError)
|
|
53
|
+
|
|
54
|
+
match = /\bline (\d+),? column (\d+)/.match(error.message)
|
|
55
|
+
line = match ? match[1].to_i : 0
|
|
56
|
+
column = match ? match[2].to_i : 0
|
|
57
|
+
problem = error.message.sub(/\s+at\s+line\s+\d+,?\s+column\s+\d+\z/, "")
|
|
58
|
+
::Psych::SyntaxError.new(nil, line, column, 0, problem, nil)
|
|
28
59
|
end
|
|
60
|
+
private_class_method :syntax_error
|
|
29
61
|
|
|
30
62
|
def to_yaml(_options = {})
|
|
31
63
|
attributes_to_serialize = if @attributes.is_a?(Lutaml::KeyValue::DataModel::Element)
|
|
@@ -25,9 +25,9 @@ RSpec.describe(YEPTRIS_AVAILABLE ? Lutaml::Json::Adapter::YeptrisAdapter : Objec
|
|
|
25
25
|
.to eq("a" => 1.5, "b" => nil, "c" => true, "d" => "1e3")
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
it "raises on invalid JSON" do
|
|
28
|
+
it "raises JSON::ParserError on invalid JSON, like the standard adapter" do
|
|
29
29
|
expect { described_class.parse("{nope") }
|
|
30
|
-
.to raise_error(
|
|
30
|
+
.to raise_error(JSON::ParserError)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -59,5 +59,10 @@ RSpec.describe(YEPTRIS_AVAILABLE ? Lutaml::Json::Adapter::YeptrisAdapter : Objec
|
|
|
59
59
|
expect(model.roles).to eq(%w[admin dev])
|
|
60
60
|
expect(model_class.from_json(model.to_json).roles).to eq(%w[admin dev])
|
|
61
61
|
end
|
|
62
|
+
|
|
63
|
+
it "surfaces invalid JSON as InvalidFormatError" do
|
|
64
|
+
expect { model_class.from_json("{nope") }
|
|
65
|
+
.to raise_error(Lutaml::Model::InvalidFormatError)
|
|
66
|
+
end
|
|
62
67
|
end
|
|
63
68
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "spec_helper"
|
|
5
|
+
|
|
6
|
+
# The suite deliberately never loads the optional yeptris engine
|
|
7
|
+
# in-process (adapter_resolver_configured_spec asserts the resolver's
|
|
8
|
+
# lazy-detection guarantee), so the error-class contract is exercised
|
|
9
|
+
# in a subprocess: wherever the engine is installed it is the
|
|
10
|
+
# preferred adapter, and its parse failures must surface under the
|
|
11
|
+
# standard adapters' error classes — Psych::SyntaxError and
|
|
12
|
+
# JSON::ParserError from the adapters, Lutaml::Model::InvalidFormatError
|
|
13
|
+
# through a model — so downstream rescue ladders hold on every engine.
|
|
14
|
+
RSpec.describe "yeptris adapter error parity" do
|
|
15
|
+
# A direct ruby invocation with explicit load paths and NO shell:
|
|
16
|
+
# a nested `bundle exec` produced empty output on some CI legs, and
|
|
17
|
+
# backticks route through cmd.exe on Windows where POSIX quoting
|
|
18
|
+
# mangles the command. Open3 with array arguments is shell-free on
|
|
19
|
+
# every platform.
|
|
20
|
+
subject(:outcomes) do
|
|
21
|
+
libs = %w[lutaml-model yeptris yeptris-ruby moxml leptris].filter_map do |g|
|
|
22
|
+
"#{Gem::Specification.find_by_name(g).gem_dir}/lib"
|
|
23
|
+
rescue Gem::LoadError
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
args = libs.flat_map { |l| ["-I", l] } + ["-e", probe]
|
|
27
|
+
Open3.capture2e(Gem.ruby, *args).first
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def yeptris_installed?
|
|
31
|
+
Gem::Specification.find_by_name("yeptris")
|
|
32
|
+
true
|
|
33
|
+
rescue Gem::LoadError
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The probe requires the engine explicitly (the resolved adapters
|
|
38
|
+
# prove the preference), then reports one outcome line per surface.
|
|
39
|
+
let(:probe) do
|
|
40
|
+
<<~RUBY
|
|
41
|
+
require "lutaml/model"
|
|
42
|
+
|
|
43
|
+
def outcome
|
|
44
|
+
yield
|
|
45
|
+
"NO-ERROR"
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
"\#{e.class.name}: \#{e.message}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts "yaml-adapter: \#{Lutaml::Model::AdapterResolver.adapter_for(:yaml).name}"
|
|
51
|
+
puts "json-adapter: \#{Lutaml::Model::AdapterResolver.adapter_for(:json).name}"
|
|
52
|
+
|
|
53
|
+
puts "adapter-yaml: \#{outcome do
|
|
54
|
+
Lutaml::Yaml::Adapter::YeptrisAdapter.parse("name: test\\n invalid: [unclosed\\n")
|
|
55
|
+
end }"
|
|
56
|
+
puts "adapter-json: \#{outcome do
|
|
57
|
+
Lutaml::Json::Adapter::YeptrisAdapter.parse("{nope")
|
|
58
|
+
end }"
|
|
59
|
+
|
|
60
|
+
model_class = Class.new(Lutaml::Model::Serializable) do
|
|
61
|
+
attribute :name, :string
|
|
62
|
+
key_value { map "name", to: :name }
|
|
63
|
+
end
|
|
64
|
+
puts "model-yaml: \#{outcome do
|
|
65
|
+
model_class.from_yaml("name: test\\n invalid: [unclosed\\n")
|
|
66
|
+
end }"
|
|
67
|
+
puts "model-json: \#{outcome do
|
|
68
|
+
model_class.from_json("{nope")
|
|
69
|
+
end }"
|
|
70
|
+
RUBY
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
before { skip "yeptris is not installed" unless yeptris_installed? }
|
|
74
|
+
|
|
75
|
+
it "prefers the yeptris adapters when the engine is installed" do
|
|
76
|
+
expect(outcomes).to include("yaml-adapter: Lutaml::Yaml::Adapter::YeptrisAdapter")
|
|
77
|
+
expect(outcomes).to include("json-adapter: Lutaml::Json::Adapter::YeptrisAdapter")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "raises Psych::SyntaxError from the yaml adapter for invalid YAML" do
|
|
81
|
+
expect(outcomes).to match(/^adapter-yaml: Psych::SyntaxError: .+ at line \d+/)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "raises JSON::ParserError from the json adapter for invalid JSON" do
|
|
85
|
+
expect(outcomes).to match(/^adapter-json: JSON::ParserError: /)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "raises InvalidFormatError through a model for invalid YAML" do
|
|
89
|
+
expect(outcomes).to match(/^model-yaml: Lutaml::Model::InvalidFormatError: /)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "raises InvalidFormatError through a model for invalid JSON" do
|
|
93
|
+
expect(outcomes).to match(/^model-json: Lutaml::Model::InvalidFormatError: /)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -30,6 +30,30 @@ RSpec.describe(YEPTRIS_AVAILABLE ? Lutaml::Yaml::Adapter::YeptrisAdapter : Objec
|
|
|
30
30
|
expect(described_class.parse("d: 2020-01-01\n"))
|
|
31
31
|
.to eq("d" => Date.new(2020, 1, 1))
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
it "raises Psych::SyntaxError for invalid YAML, like the standard adapter" do
|
|
35
|
+
expect { described_class.parse("name: test\n invalid: [unclosed\n") }
|
|
36
|
+
.to raise_error(Psych::SyntaxError)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "raises Psych::SyntaxError for a mapping value inside a plain scalar" do
|
|
40
|
+
# The dependent-CI shape: one line, a second "key:" colon after a
|
|
41
|
+
# plain scalar run. Psych rejects it with "mapping values are not
|
|
42
|
+
# allowed in this context"; the engine must surface under the
|
|
43
|
+
# same class so downstream rescue ladders hold.
|
|
44
|
+
expect { described_class.parse('name: test\n invalid: [unclosed') }
|
|
45
|
+
.to raise_error(Psych::SyntaxError)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "carries the engine's position on the syntax error" do
|
|
49
|
+
expect { described_class.parse("name: test\n invalid: [unclosed\n") }
|
|
50
|
+
.to raise_error(Psych::SyntaxError) do |error|
|
|
51
|
+
expect(error.line).to be >= 1
|
|
52
|
+
expect(error.problem).to be_a(String)
|
|
53
|
+
expect(error.problem).not_to be_empty
|
|
54
|
+
expect(error.message).to include("at line #{error.line}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
33
57
|
end
|
|
34
58
|
|
|
35
59
|
describe "#to_yaml" do
|
|
@@ -59,5 +83,10 @@ RSpec.describe(YEPTRIS_AVAILABLE ? Lutaml::Yaml::Adapter::YeptrisAdapter : Objec
|
|
|
59
83
|
expect(model.roles).to eq(%w[admin dev])
|
|
60
84
|
expect(model_class.from_yaml(model.to_yaml).roles).to eq(%w[admin dev])
|
|
61
85
|
end
|
|
86
|
+
|
|
87
|
+
it "surfaces invalid YAML as InvalidFormatError" do
|
|
88
|
+
expect { model_class.from_yaml("name: test\n invalid: [unclosed\n") }
|
|
89
|
+
.to raise_error(Lutaml::Model::InvalidFormatError)
|
|
90
|
+
end
|
|
62
91
|
end
|
|
63
92
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.66
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -1880,6 +1880,7 @@ files:
|
|
|
1880
1880
|
- spec/lutaml/model/yamls_range_spec.rb
|
|
1881
1881
|
- spec/lutaml/model/yamls_sequence_spec.rb
|
|
1882
1882
|
- spec/lutaml/model/yamls_spec.rb
|
|
1883
|
+
- spec/lutaml/model/yeptris_error_parity_spec.rb
|
|
1883
1884
|
- spec/lutaml/model_spec.rb
|
|
1884
1885
|
- spec/lutaml/rdf/context_spec.rb
|
|
1885
1886
|
- spec/lutaml/rdf/graph_serialization_spec.rb
|