lutaml-model 0.8.117 → 0.8.119
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/lib/lutaml/json/adapter/yeptris_adapter.rb +21 -4
- data/lib/lutaml/model/adapter_resolver.rb +13 -5
- data/lib/lutaml/model/attribute.rb +18 -2
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/yaml/adapter/yeptris_adapter.rb +22 -4
- data/spec/lutaml/model/cast_element_form_spec.rb +54 -0
- data/spec/lutaml/model/yeptris_native_detection_spec.rb +83 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8df481bfb5303c05153a8bda8eaa4996157a6060d34d70dd8eb835e461aff9f5
|
|
4
|
+
data.tar.gz: a23f93de4f89b1a2b7925833786e9d0337ec5e94ec172723f5b2b70f78347d3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: adbc819876bf696f60009c53042dba835e5e557f32057f1aba4f58da56831c15f347d35dcf0160536a877f29f2d17ba59aadb8b1ac38b7fa9572b2d12d6c6dd2
|
|
7
|
+
data.tar.gz: 9af887394f9b2e241f299cb8eabcc3e0202ec51dba5129477a16cb85234105f51c7198fc44b9c20c916fa89a1356918c4752bbd73afa8f83a5a49ffd3032f08d
|
|
@@ -27,16 +27,33 @@ module Lutaml
|
|
|
27
27
|
private_class_method :require_engine
|
|
28
28
|
|
|
29
29
|
# True where the yeptris engine loads (libyeptris present for
|
|
30
|
-
# this platform)
|
|
31
|
-
#
|
|
32
|
-
#
|
|
30
|
+
# this platform) AND answers a probe parse. A require alone
|
|
31
|
+
# does not prove the engine: a half-initialized Yeptris
|
|
32
|
+
# constant (the poison-process state the gem documents)
|
|
33
|
+
# passes const_defined? and require while every parse fails,
|
|
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.
|
|
33
38
|
def self.available?
|
|
34
39
|
require_engine
|
|
35
|
-
|
|
40
|
+
native_ready?
|
|
36
41
|
rescue LoadError
|
|
37
42
|
false
|
|
38
43
|
end
|
|
39
44
|
|
|
45
|
+
def self.native_ready?
|
|
46
|
+
return @native_ready unless @native_ready.nil?
|
|
47
|
+
|
|
48
|
+
@native_ready = begin
|
|
49
|
+
::Yeptris::JSON.load('{"native_probe": 1}')
|
|
50
|
+
true
|
|
51
|
+
rescue ::Yeptris::Error, LoadError, NoMemoryError
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
private_class_method :native_ready?
|
|
56
|
+
|
|
40
57
|
# The parse-error contract is the standard adapter's: invalid
|
|
41
58
|
# input raises JSON::ParserError regardless of the engine, so
|
|
42
59
|
# consumers' rescue ladders (and this gem's own
|
|
@@ -387,13 +387,21 @@ module Lutaml
|
|
|
387
387
|
end
|
|
388
388
|
|
|
389
389
|
# Native key-value engine preference: yeptris (libyeptris C11)
|
|
390
|
-
# when its gem is in the bundle
|
|
391
|
-
# adapter. Opt-in by bundle
|
|
392
|
-
# consumers that do not add
|
|
390
|
+
# when its gem is in the bundle AND the native engine answers a
|
|
391
|
+
# probe, else the format's standard adapter. Opt-in by bundle
|
|
392
|
+
# contents — nothing is required of consumers that do not add
|
|
393
|
+
# yeptris. The probe matters because a half-initialized Yeptris
|
|
394
|
+
# constant (the poison-process state the gem documents) passes
|
|
395
|
+
# const_defined? and require while every parse fails; without
|
|
396
|
+
# it the lane selection flips with process load order and the
|
|
397
|
+
# failure surfaces as a native parse error on valid input
|
|
398
|
+
# instead of a clean standard-lane parse (#978).
|
|
393
399
|
def detect_yeptris_kv_adapter(format)
|
|
394
|
-
|
|
400
|
+
default = metadata.dig(format, :default)
|
|
401
|
+
return default if Lutaml::Model.opal?
|
|
402
|
+
return default unless Utils.safe_load("yeptris", :Yeptris)
|
|
395
403
|
|
|
396
|
-
|
|
404
|
+
load_adapter(format, :yeptris).available? ? :yeptris : default
|
|
397
405
|
end
|
|
398
406
|
|
|
399
407
|
# Detect available XML adapter.
|
|
@@ -5,6 +5,20 @@ module Lutaml
|
|
|
5
5
|
class Attribute
|
|
6
6
|
attr_reader :name, :options
|
|
7
7
|
|
|
8
|
+
# Format scalars a mapped-model attribute may be handed in the
|
|
9
|
+
# key-value lanes (#978). Adapter element nodes and other object
|
|
10
|
+
# forms belong to the mapping lane and must not divert.
|
|
11
|
+
SCALAR_CAST_TYPES = [
|
|
12
|
+
::String,
|
|
13
|
+
::Numeric,
|
|
14
|
+
::Symbol,
|
|
15
|
+
::Time,
|
|
16
|
+
::Date,
|
|
17
|
+
::DateTime,
|
|
18
|
+
::TrueClass,
|
|
19
|
+
::FalseClass,
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
8
22
|
include CollectionHandler
|
|
9
23
|
include DeepDupable
|
|
10
24
|
include RestrictionValidation
|
|
@@ -982,9 +996,11 @@ instance_object = nil)
|
|
|
982
996
|
# String, #978). A type declaring its own .cast owns scalar
|
|
983
997
|
# construction; base-cast identity types fall through to the
|
|
984
998
|
# legacy conversion chain, whose parse error names the token.
|
|
999
|
+
# Non-scalar input (adapter elements, hashes, arrays) rides
|
|
1000
|
+
# the mapping lane untouched.
|
|
985
1001
|
if serializable_type?(register) && !value.nil? &&
|
|
986
|
-
|
|
987
|
-
|
|
1002
|
+
SCALAR_CAST_TYPES.any? { |type| value.is_a?(type) } &&
|
|
1003
|
+
klass.respond_to?(:cast) &&
|
|
988
1004
|
klass.method(:cast).owner == klass.singleton_class
|
|
989
1005
|
casted = klass.cast(value)
|
|
990
1006
|
return casted if casted.is_a?(klass)
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -41,16 +41,34 @@ module Lutaml
|
|
|
41
41
|
private_class_method :require_engine
|
|
42
42
|
|
|
43
43
|
# True where the yeptris engine loads (libyeptris present for
|
|
44
|
-
# this platform)
|
|
45
|
-
#
|
|
46
|
-
#
|
|
44
|
+
# this platform) AND answers a probe parse. A require alone
|
|
45
|
+
# does not prove the engine: a half-initialized Yeptris
|
|
46
|
+
# constant (the poison-process state the gem documents)
|
|
47
|
+
# passes const_defined? and require while every parse fails,
|
|
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.
|
|
47
52
|
def self.available?
|
|
48
53
|
require_engine
|
|
49
|
-
|
|
54
|
+
native_ready?
|
|
50
55
|
rescue LoadError
|
|
51
56
|
false
|
|
52
57
|
end
|
|
53
58
|
|
|
59
|
+
def self.native_ready?
|
|
60
|
+
return @native_ready unless @native_ready.nil?
|
|
61
|
+
|
|
62
|
+
@native_ready = begin
|
|
63
|
+
# rubocop:disable-next Naming/VariableNumber -- upstream schema literal
|
|
64
|
+
::Yeptris::YAML.load("native_probe: 1", schema: :compat_11)
|
|
65
|
+
true
|
|
66
|
+
rescue ::Yeptris::Error, LoadError, NoMemoryError
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :native_ready?
|
|
71
|
+
|
|
54
72
|
# The parse-error contract is the standard adapter's: invalid
|
|
55
73
|
# input raises Psych::SyntaxError regardless of the engine, so
|
|
56
74
|
# consumers' rescue ladders (and this gem's own
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# The scalar-cast diversion (#978) must not capture element-form input:
|
|
6
|
+
# a type declaring its own .cast still receives parsed child elements
|
|
7
|
+
# through the mapping lane, which builds the model from its XML
|
|
8
|
+
# mapping. Diverting those into .cast raised IncorrectModelError
|
|
9
|
+
# downstream (uniword shared-schemas round-trips on 0.8.116/0.8.117).
|
|
10
|
+
RSpec.describe "cast routing for element-form input" do
|
|
11
|
+
let(:styled_class) do
|
|
12
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
13
|
+
attribute :value, :string
|
|
14
|
+
|
|
15
|
+
xml do
|
|
16
|
+
element "style"
|
|
17
|
+
map_attribute "value", to: :value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
key_value do
|
|
21
|
+
map "value", to: :value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.cast(value)
|
|
25
|
+
return value if value.is_a?(self)
|
|
26
|
+
|
|
27
|
+
new(value: value.to_s)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
let(:host_class) do
|
|
33
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
34
|
+
attribute :style, "CastStyled"
|
|
35
|
+
|
|
36
|
+
xml do
|
|
37
|
+
element "host"
|
|
38
|
+
map_element "style", to: :style
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
before do
|
|
44
|
+
stub_const("CastStyled", styled_class)
|
|
45
|
+
stub_const("CastHost", host_class)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "builds element-form input through the mapping lane" do
|
|
49
|
+
model = CastHost.from_xml('<host><style value="normal"/></host>')
|
|
50
|
+
|
|
51
|
+
expect(model.style).to be_a(CastStyled)
|
|
52
|
+
expect(model.style.value).to eq("normal")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "lutaml/json/adapter/yeptris_adapter"
|
|
6
|
+
|
|
7
|
+
# Surface 1 of #978: the yeptris lane was selected from a bare
|
|
8
|
+
# constant check, so a process holding a half-initialized Yeptris
|
|
9
|
+
# module (the poison-process state the gem documents) committed to
|
|
10
|
+
# the native lane and failed pre-mapping on valid JSON — while other
|
|
11
|
+
# processes fell back to the standard lane, flipping the adapter
|
|
12
|
+
# choice across runs. Detection now probes the native engine; a
|
|
13
|
+
# broken native deterministically rides the standard lane.
|
|
14
|
+
RSpec.describe Lutaml::Model::AdapterResolver, ".adapter_for yeptris native detection" do
|
|
15
|
+
let(:parse_probe) do
|
|
16
|
+
<<~RUBY
|
|
17
|
+
require "lutaml/model"
|
|
18
|
+
|
|
19
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
20
|
+
attribute :v, :string
|
|
21
|
+
key_value { map "v", to: :v }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
model = klass.from_json('{"v": "scalar"}')
|
|
25
|
+
adapter = Lutaml::Model::AdapterResolver.adapter_for(:json)
|
|
26
|
+
puts "ADAPTER=\#{adapter.name.split('::').last} PARSED=\#{model.v}"
|
|
27
|
+
RUBY
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def subprocess_yeptris_capable?
|
|
31
|
+
probe = <<~RUBY
|
|
32
|
+
begin
|
|
33
|
+
require "yeptris"
|
|
34
|
+
exit(0)
|
|
35
|
+
rescue LoadError
|
|
36
|
+
exit(1)
|
|
37
|
+
end
|
|
38
|
+
RUBY
|
|
39
|
+
_out, _err, status = Open3.capture3(RbConfig.ruby, "-I", "lib", "-e", probe)
|
|
40
|
+
status.success?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "resolves the yeptris lane when the native engine answers a probe" do
|
|
44
|
+
skip "optional yeptris engine not resolvable outside the bundle" unless subprocess_yeptris_capable?
|
|
45
|
+
|
|
46
|
+
stdout, stderr, status = Open3.capture3(RbConfig.ruby, "-I", "lib", "-e", parse_probe)
|
|
47
|
+
expect(status).to be_success, stderr
|
|
48
|
+
expect(stdout).to include("ADAPTER=YeptrisAdapter")
|
|
49
|
+
expect(stdout).to include("PARSED=scalar")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "falls back to the standard lane when the native engine cannot parse" do
|
|
53
|
+
poison = <<~RUBY
|
|
54
|
+
require "lutaml/model"
|
|
55
|
+
require "yeptris"
|
|
56
|
+
require "lutaml/json/adapter/yeptris_adapter"
|
|
57
|
+
|
|
58
|
+
# The poison state: the module is defined and require succeeds,
|
|
59
|
+
# but every native parse fails. Whatever the cause (a stale
|
|
60
|
+
# libyeptris, a half-initialized load), the lane must not be
|
|
61
|
+
# selected.
|
|
62
|
+
Lutaml::Json::Adapter::YeptrisAdapter
|
|
63
|
+
.define_singleton_method(:native_ready?) { false }
|
|
64
|
+
|
|
65
|
+
#{parse_probe}
|
|
66
|
+
RUBY
|
|
67
|
+
|
|
68
|
+
stdout, stderr, status = Open3.capture3(RbConfig.ruby, "-I", "lib", "-e", poison)
|
|
69
|
+
expect(status).to be_success, stderr
|
|
70
|
+
expect(stdout).to include("ADAPTER=StandardAdapter")
|
|
71
|
+
expect(stdout).to include("PARSED=scalar")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "reports the adapter unavailable when the native probe fails" do
|
|
75
|
+
allow(Lutaml::Json::Adapter::YeptrisAdapter)
|
|
76
|
+
.to receive(:native_ready?).and_return(false)
|
|
77
|
+
|
|
78
|
+
expect(Lutaml::Json::Adapter::YeptrisAdapter.available?).to be(false)
|
|
79
|
+
ensure
|
|
80
|
+
allow(Lutaml::Json::Adapter::YeptrisAdapter)
|
|
81
|
+
.to receive(:native_ready?).and_call_original
|
|
82
|
+
end
|
|
83
|
+
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.
|
|
4
|
+
version: 0.8.119
|
|
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-
|
|
11
|
+
date: 2026-10-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -1713,6 +1713,7 @@ files:
|
|
|
1713
1713
|
- spec/lutaml/model/boot_manifest_spec.rb
|
|
1714
1714
|
- spec/lutaml/model/cached_type_resolver_spec.rb
|
|
1715
1715
|
- spec/lutaml/model/cardinality_strict_spec.rb
|
|
1716
|
+
- spec/lutaml/model/cast_element_form_spec.rb
|
|
1716
1717
|
- spec/lutaml/model/cdata_spec.rb
|
|
1717
1718
|
- spec/lutaml/model/choice_restrict_spec.rb
|
|
1718
1719
|
- spec/lutaml/model/choice_spec.rb
|
|
@@ -1899,6 +1900,7 @@ files:
|
|
|
1899
1900
|
- spec/lutaml/model/yamls_sequence_spec.rb
|
|
1900
1901
|
- spec/lutaml/model/yamls_spec.rb
|
|
1901
1902
|
- spec/lutaml/model/yeptris_error_parity_spec.rb
|
|
1903
|
+
- spec/lutaml/model/yeptris_native_detection_spec.rb
|
|
1902
1904
|
- spec/lutaml/model_spec.rb
|
|
1903
1905
|
- spec/lutaml/rdf/context_spec.rb
|
|
1904
1906
|
- spec/lutaml/rdf/graph_serialization_spec.rb
|