lutaml-model 0.8.0 → 0.8.2
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/.github/workflows/dependent-repos.json +9 -0
- data/.github/workflows/downstream-performance.yml +0 -3
- data/.rubocop_todo.yml +18 -186
- data/README.adoc +212 -15
- data/bench/bench_xmi.rb +6 -6
- data/bench/gate_config.rb +2 -9
- data/docs/_pages/configuration.adoc +155 -41
- data/docs/_pages/serialization_adapters.adoc +65 -14
- data/docs/index.adoc +3 -1
- data/docs/yamls_sequence.adoc +335 -0
- data/lib/lutaml/hash_format.rb +4 -0
- data/lib/lutaml/json/adapter/multi_json_adapter.rb +4 -2
- data/lib/lutaml/json/adapter/oj_adapter.rb +4 -2
- data/lib/lutaml/json.rb +4 -0
- data/lib/lutaml/key_value/adapter/json/multi_json_adapter.rb +4 -2
- data/lib/lutaml/key_value/adapter/json/oj_adapter.rb +4 -2
- data/lib/lutaml/model/adapter_resolver.rb +410 -0
- data/lib/lutaml/model/adapter_scope.rb +64 -0
- data/lib/lutaml/model/config.rb +84 -21
- data/lib/lutaml/model/configuration.rb +17 -249
- data/lib/lutaml/model/format_registry.rb +44 -117
- data/lib/lutaml/model/mapping/listener.rb +4 -2
- data/lib/lutaml/model/serialize/format_conversion.rb +42 -3
- data/lib/lutaml/model/serialize.rb +4 -2
- data/lib/lutaml/model/services/base.rb +4 -2
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +2 -0
- data/lib/lutaml/toml.rb +10 -3
- data/lib/lutaml/xml/serialization/instance_methods.rb +6 -0
- data/lib/lutaml/xml.rb +3 -4
- data/lib/lutaml/yaml.rb +4 -0
- data/lib/lutaml/yamls/adapter/mapping.rb +7 -0
- data/lib/lutaml/yamls/adapter/standard_adapter.rb +23 -2
- data/lib/lutaml/yamls/adapter/transform.rb +105 -7
- data/lib/lutaml/yamls/adapter/yamls_sequence.rb +20 -0
- data/lib/lutaml/yamls/adapter/yamls_sequence_rule.rb +48 -0
- data/lib/lutaml/yamls/adapter.rb +2 -0
- data/spec/fixtures/geolexica_v2_concept.rb +136 -0
- data/spec/fixtures/geolexica_v2_sample.yaml +36 -0
- data/spec/fixtures/geolexica_v2_sample2.yaml +38 -0
- data/spec/fixtures/yamls_range_concept.rb +139 -0
- data/spec/lutaml/model/xml_decoupling_spec.rb +5 -4
- data/spec/lutaml/model/yamls_range_spec.rb +393 -0
- data/spec/lutaml/model/yamls_sequence_spec.rb +245 -0
- data/spec/spec_helper.rb +5 -0
- metadata +13 -3
- data/bench/bench_uniword.rb +0 -69
|
@@ -2,75 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Model
|
|
5
|
-
# Single source of truth for Lutaml::Model configuration
|
|
5
|
+
# Single source of truth for Lutaml::Model configuration.
|
|
6
|
+
#
|
|
7
|
+
# Adapter methods delegate to AdapterResolver. This class retains
|
|
8
|
+
# the configure block API, register, and non-adapter settings.
|
|
6
9
|
#
|
|
7
10
|
# @example Basic configuration
|
|
8
11
|
# Lutaml::Model::Configuration.configure do |config|
|
|
9
12
|
# config.xml_adapter = :nokogiri
|
|
10
13
|
# config.json_adapter = :standard
|
|
11
|
-
# config.toml_adapter = :toml_rb
|
|
12
14
|
# end
|
|
13
15
|
#
|
|
14
16
|
class Configuration
|
|
15
|
-
|
|
16
|
-
# per-call runtime checks that tests may stub.
|
|
17
|
-
OPAL_RUNTIME = Lutaml::Model::RuntimeCompatibility.opal?
|
|
18
|
-
WINDOWS_PLATFORM = Lutaml::Model::RuntimeCompatibility.windows?
|
|
19
|
-
|
|
20
|
-
# Bootstrap format list for key-value adapters.
|
|
21
|
-
# NOTE: XML is NOT listed here — it registers dynamically via FormatRegistry.
|
|
22
|
-
AVAILABLE_FORMATS = %i[json jsonl yaml toml hash].freeze
|
|
23
|
-
KEY_VALUE_FORMATS = AVAILABLE_FORMATS
|
|
24
|
-
|
|
25
|
-
# Available key-value formats and their adapters.
|
|
26
|
-
# XML adapter metadata is registered dynamically via FormatRegistry
|
|
27
|
-
# when `require "lutaml/xml"` is called.
|
|
28
|
-
ADAPTERS = begin
|
|
29
|
-
h = {
|
|
30
|
-
json: {
|
|
31
|
-
available: %i[standard standard_json multi_json oj],
|
|
32
|
-
default: :standard,
|
|
33
|
-
},
|
|
34
|
-
yaml: {
|
|
35
|
-
available: %i[standard standard_yaml],
|
|
36
|
-
default: :standard,
|
|
37
|
-
},
|
|
38
|
-
toml: {
|
|
39
|
-
available: OPAL_RUNTIME ? [] : %i[tomlib toml_rb],
|
|
40
|
-
default: if OPAL_RUNTIME
|
|
41
|
-
nil
|
|
42
|
-
elsif WINDOWS_PLATFORM
|
|
43
|
-
:toml_rb
|
|
44
|
-
else
|
|
45
|
-
:tomlib
|
|
46
|
-
end,
|
|
47
|
-
},
|
|
48
|
-
hash: {
|
|
49
|
-
available: %i[standard standard_hash],
|
|
50
|
-
default: :standard,
|
|
51
|
-
},
|
|
52
|
-
jsonl: {
|
|
53
|
-
available: %i[standard],
|
|
54
|
-
default: :standard,
|
|
55
|
-
},
|
|
56
|
-
yamls: {
|
|
57
|
-
available: %i[standard],
|
|
58
|
-
default: :standard,
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
h.freeze
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
attr_reader :adapter_types
|
|
17
|
+
attr_reader :default_register
|
|
65
18
|
|
|
66
19
|
def initialize
|
|
67
|
-
@adapter_types = {}
|
|
68
|
-
@adapters = {}
|
|
69
20
|
@default_register = :default
|
|
70
21
|
@configured = false
|
|
71
22
|
end
|
|
72
23
|
|
|
73
|
-
# Configure the library using a block
|
|
74
24
|
def configure
|
|
75
25
|
yield self if block_given?
|
|
76
26
|
@configured = true
|
|
@@ -81,48 +31,21 @@ module Lutaml
|
|
|
81
31
|
@configured
|
|
82
32
|
end
|
|
83
33
|
|
|
84
|
-
#
|
|
85
|
-
def self.windows_platform?
|
|
86
|
-
WINDOWS_PLATFORM
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Dynamic accessor for adapter types
|
|
34
|
+
# Dynamic accessor for adapter types — delegates to AdapterResolver
|
|
90
35
|
def adapter_for(format)
|
|
91
|
-
|
|
36
|
+
AdapterResolver.configured_type(format)
|
|
92
37
|
end
|
|
93
38
|
|
|
94
|
-
# Dynamic setter for adapter types
|
|
39
|
+
# Dynamic setter for adapter types — delegates to AdapterResolver
|
|
95
40
|
def set_adapter(format, adapter_type)
|
|
96
|
-
format
|
|
97
|
-
adapter_type = adapter_type.to_sym
|
|
98
|
-
|
|
99
|
-
validate_adapter!(format, adapter_type)
|
|
100
|
-
@adapter_types[format] = adapter_type
|
|
101
|
-
|
|
102
|
-
# Load the adapter immediately
|
|
103
|
-
load_adapter(format, adapter_type)
|
|
41
|
+
AdapterResolver.set_adapter_type(format, adapter_type)
|
|
104
42
|
end
|
|
105
43
|
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
define_method(:"#{format}_adapter") do
|
|
110
|
-
@adapter_types[format] || ADAPTERS[format][:default]
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Setter method: config.xml_adapter = :nokogiri
|
|
114
|
-
define_method(:"#{format}_adapter=") do |adapter_type|
|
|
115
|
-
set_adapter(format, adapter_type)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Aliased setter with _type suffix: config.xml_adapter_type = :nokogiri
|
|
119
|
-
alias_method :"#{format}_adapter_type=", :"#{format}_adapter="
|
|
120
|
-
alias_method :"#{format}_adapter_type", :"#{format}_adapter"
|
|
44
|
+
# Get adapter class for a format
|
|
45
|
+
def get_adapter(format)
|
|
46
|
+
AdapterResolver.adapter_for(format)
|
|
121
47
|
end
|
|
122
48
|
|
|
123
|
-
# Default register/context ID accessor
|
|
124
|
-
attr_reader :default_register
|
|
125
|
-
|
|
126
49
|
def default_register=(value)
|
|
127
50
|
@default_register = case value
|
|
128
51
|
when Symbol then value
|
|
@@ -134,190 +57,35 @@ module Lutaml
|
|
|
134
57
|
end
|
|
135
58
|
end
|
|
136
59
|
|
|
137
|
-
# Alias for terminology alignment
|
|
138
60
|
alias default_context_id default_register
|
|
139
61
|
alias default_context_id= default_register=
|
|
140
62
|
|
|
141
|
-
# Get adapter class for a format
|
|
142
|
-
def get_adapter(format)
|
|
143
|
-
@adapters[format.to_sym]
|
|
144
|
-
end
|
|
145
|
-
|
|
146
63
|
# Reset configuration to defaults
|
|
147
64
|
def reset!
|
|
148
|
-
@adapter_types = {}
|
|
149
|
-
@adapters = {}
|
|
150
65
|
@default_register = :default
|
|
151
66
|
@configured = false
|
|
67
|
+
AdapterResolver.reset!
|
|
68
|
+
AdapterScope.reset!
|
|
152
69
|
end
|
|
153
70
|
|
|
154
|
-
# Get all current settings as a hash
|
|
155
71
|
def to_h
|
|
156
72
|
{
|
|
157
|
-
adapter_types: @adapter_types.dup,
|
|
158
73
|
default_register: @default_register,
|
|
159
74
|
configured: @configured,
|
|
160
75
|
}
|
|
161
76
|
end
|
|
162
77
|
|
|
163
|
-
# Mappings class for a format
|
|
164
78
|
def mappings_class_for(format)
|
|
165
79
|
Lutaml::Model::FormatRegistry.mappings_class_for(format)
|
|
166
80
|
end
|
|
167
81
|
|
|
168
|
-
# Transformer for a format
|
|
169
82
|
def transformer_for(format)
|
|
170
83
|
Lutaml::Model::FormatRegistry.transformer_for(format)
|
|
171
84
|
end
|
|
172
85
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
# Checks both static ADAPTERS hash and FormatRegistry for dynamically
|
|
177
|
-
# registered formats (e.g., XML).
|
|
178
|
-
def validate_adapter!(format, adapter_type)
|
|
179
|
-
adapter_config = ADAPTERS[format] || FormatRegistry.adapter_options_for(format)
|
|
180
|
-
|
|
181
|
-
unless adapter_config
|
|
182
|
-
all_formats = (ADAPTERS.keys + FormatRegistry.formats).uniq
|
|
183
|
-
available_formats = all_formats.map { |f| "`:#{f}`" }.join(", ")
|
|
184
|
-
raise ArgumentError,
|
|
185
|
-
"Unknown format: `:#{format}`. Available formats: #{available_formats}."
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Check for Windows + tomlib incompatibility
|
|
189
|
-
if format == :toml && adapter_type == :tomlib && self.class.windows_platform?
|
|
190
|
-
raise ArgumentError,
|
|
191
|
-
"The `:tomlib` adapter is not supported on Windows due to " \
|
|
192
|
-
"segmentation fault issues. Please use `:toml_rb` instead."
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
available = adapter_config[:available]
|
|
196
|
-
return unless available
|
|
197
|
-
return if available.include?(adapter_type)
|
|
198
|
-
|
|
199
|
-
available_list = available.map { |a| "`:#{a}`" }.join(", ")
|
|
200
|
-
closest = find_suggestion(adapter_type.to_s, available.map(&:to_s))
|
|
201
|
-
|
|
202
|
-
msg = "Unknown adapter: `:#{adapter_type}` for `:#{format}` format. " \
|
|
203
|
-
"Available adapters: #{available_list}."
|
|
204
|
-
msg += " Did you mean `:#{closest}`?" if closest
|
|
205
|
-
|
|
206
|
-
raise ArgumentError, msg
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Load an adapter for a format
|
|
210
|
-
def load_adapter(format, adapter_type)
|
|
211
|
-
adapter = format.to_s
|
|
212
|
-
type = normalize_type_name(adapter_type, format)
|
|
213
|
-
|
|
214
|
-
load_adapter_file(adapter, type)
|
|
215
|
-
load_moxml_adapter(adapter_type, format)
|
|
216
|
-
|
|
217
|
-
adapter_class = class_for(adapter, type)
|
|
218
|
-
@adapters[format] = adapter_class
|
|
219
|
-
|
|
220
|
-
# Also set in Config's adapters for FormatRegistry compatibility
|
|
221
|
-
Config.set_adapter_for(format, adapter_class)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def normalize_type_name(type_name, adapter_name)
|
|
225
|
-
if type_name.to_s.start_with?("multi_json")
|
|
226
|
-
"multi_json_adapter"
|
|
227
|
-
else
|
|
228
|
-
"#{type_name.to_s.gsub("_#{adapter_name}", '')}_adapter"
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def load_adapter_file(adapter, type)
|
|
233
|
-
# Check FormatRegistry for a custom adapter loader
|
|
234
|
-
loader = FormatRegistry.adapter_loader_for(adapter.to_sym)
|
|
235
|
-
if loader.respond_to?(:load_adapter_file)
|
|
236
|
-
loader.load_adapter_file(adapter, type)
|
|
237
|
-
return
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Default key-value adapter loading
|
|
241
|
-
adapter_path = if Lutaml::Model::RuntimeCompatibility.opal?
|
|
242
|
-
"lutaml/key_value/adapter/#{adapter}/#{type}"
|
|
243
|
-
else
|
|
244
|
-
File.join(__dir__, "../key_value/adapter", adapter,
|
|
245
|
-
type)
|
|
246
|
-
end
|
|
247
|
-
require adapter_path
|
|
248
|
-
rescue LoadError
|
|
249
|
-
raise UnknownAdapterTypeError.new(adapter, type), cause: nil
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def load_moxml_adapter(type_name, adapter_name)
|
|
253
|
-
# Delegate to FormatRegistry adapter loader if available
|
|
254
|
-
loader = FormatRegistry.adapter_loader_for(adapter_name)
|
|
255
|
-
if loader.respond_to?(:load_moxml_adapter)
|
|
256
|
-
loader.load_moxml_adapter(type_name, adapter_name)
|
|
257
|
-
nil
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
# Key-value formats don't need moxml loading
|
|
261
|
-
# Non-key-value formats without a registered loader are no-ops
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
def class_for(adapter, type)
|
|
265
|
-
# Check FormatRegistry for a custom adapter loader
|
|
266
|
-
loader = FormatRegistry.adapter_loader_for(adapter.to_sym)
|
|
267
|
-
if loader.respond_to?(:class_for)
|
|
268
|
-
return loader.class_for(adapter, type)
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# Default key-value adapter class resolution
|
|
272
|
-
Lutaml::KeyValue::Adapter.const_get(to_class_name(adapter))
|
|
273
|
-
.const_get(to_class_name(type))
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def to_class_name(str)
|
|
277
|
-
str.to_s.split("_").map(&:capitalize).join
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# Find closest string match for suggestions
|
|
281
|
-
def find_suggestion(input, candidates)
|
|
282
|
-
return nil if input.nil? || input.empty?
|
|
283
|
-
|
|
284
|
-
candidates.min_by do |candidate|
|
|
285
|
-
levenshtein_distance(input.downcase, candidate.downcase)
|
|
286
|
-
end.tap do |closest|
|
|
287
|
-
max_dist = ([input.length, closest.length].max / 2) + 1
|
|
288
|
-
return nil if closest && levenshtein_distance(input.downcase,
|
|
289
|
-
closest.downcase) > max_dist
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
# Calculate Levenshtein distance between two strings
|
|
294
|
-
def levenshtein_distance(a, b)
|
|
295
|
-
return a.length if b.empty?
|
|
296
|
-
return b.length if a.empty?
|
|
297
|
-
|
|
298
|
-
matrix = Array.new(a.length + 1) do |i|
|
|
299
|
-
Array.new(b.length + 1) do |j|
|
|
300
|
-
if i.zero?
|
|
301
|
-
j
|
|
302
|
-
else
|
|
303
|
-
(j.zero? ? i : 0)
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
(1..a.length).each do |i|
|
|
309
|
-
(1..b.length).each do |j|
|
|
310
|
-
cost = a[i - 1] == b[j - 1] ? 0 : 1
|
|
311
|
-
matrix[i][j] = [
|
|
312
|
-
matrix[i - 1][j] + 1,
|
|
313
|
-
matrix[i][j - 1] + 1,
|
|
314
|
-
matrix[i - 1][j - 1] + cost,
|
|
315
|
-
].min
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
matrix[a.length][b.length]
|
|
320
|
-
end
|
|
86
|
+
# Adapter accessors are defined dynamically by FormatRegistry.register
|
|
87
|
+
# via define_method. These forward to AdapterResolver.
|
|
88
|
+
# The generic set_adapter/get_adapter handle any format.
|
|
321
89
|
end
|
|
322
90
|
end
|
|
323
91
|
end
|
|
@@ -2,37 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Model
|
|
5
|
-
# Registry for serialization formats and their associated components
|
|
5
|
+
# Registry for serialization formats and their associated components.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# @example Registering a custom format
|
|
11
|
-
# Lutaml::Model::FormatRegistry.register(:custom,
|
|
12
|
-
# mapping_class: MyMapping,
|
|
13
|
-
# adapter_class: MyAdapter,
|
|
14
|
-
# transformer: MyTransformer
|
|
15
|
-
# )
|
|
16
|
-
#
|
|
17
|
-
# @example Checking if a format is registered
|
|
18
|
-
# Lutaml::Model::FormatRegistry.registered?(:xml) #=> true
|
|
19
|
-
#
|
|
20
|
-
# @example Listing all registered formats
|
|
21
|
-
# Lutaml::Model::FormatRegistry.formats #=> [:xml, :json, :yaml, ...]
|
|
7
|
+
# Manages the registration of formats (xml, json, yaml, etc.) and their
|
|
8
|
+
# mapping classes, transformers, and adapter metadata. Adapter loading
|
|
9
|
+
# and resolution is delegated to AdapterResolver.
|
|
22
10
|
#
|
|
23
11
|
class FormatRegistry
|
|
24
12
|
class << self
|
|
25
|
-
# Register a new format with its associated components
|
|
13
|
+
# Register a new format with its associated components.
|
|
26
14
|
#
|
|
27
15
|
# @param format [Symbol] the format name (e.g., :xml, :json)
|
|
28
16
|
# @param mapping_class [Class] the mapping class for this format
|
|
29
|
-
# @param adapter_class [Class, nil] the adapter class (nil for
|
|
17
|
+
# @param adapter_class [Class, nil] the adapter class (nil for selectable formats)
|
|
30
18
|
# @param transformer [Class] the transformer class for serialization
|
|
31
|
-
# @
|
|
32
|
-
# @
|
|
33
|
-
# @param
|
|
19
|
+
# @param adapter_loader [Module, nil] optional module with load_adapter_file and class_for
|
|
20
|
+
# @param castable_type [Class, nil] the castable type class
|
|
21
|
+
# @param key_value [Boolean] whether this is a key-value format
|
|
22
|
+
# @param error_types [Array<String, Class>] error types for this format
|
|
23
|
+
# @param adapter_options [Hash, nil] { available: [...], default: :name }
|
|
34
24
|
def register(format, mapping_class:, adapter_class:, transformer:,
|
|
35
|
-
adapter_loader: nil, castable_type: nil, key_value: nil,
|
|
25
|
+
adapter_loader: nil, castable_type: nil, key_value: nil,
|
|
26
|
+
error_types: nil, adapter_options: nil)
|
|
36
27
|
validate_registration!(format, mapping_class, transformer)
|
|
37
28
|
|
|
38
29
|
registered_formats[format] = {
|
|
@@ -47,206 +38,142 @@ adapter_loader: nil, castable_type: nil, key_value: nil, error_types: nil, adapt
|
|
|
47
38
|
registered_at: Time.now,
|
|
48
39
|
}
|
|
49
40
|
|
|
41
|
+
# Register type methods on model classes
|
|
50
42
|
::Lutaml::Model::Type::Value.register_format_to_from_methods(format)
|
|
51
43
|
::Lutaml::Model::Serialize.register_format_mapping_method(format)
|
|
52
44
|
::Lutaml::Model::Serialize.register_from_format_method(format)
|
|
53
45
|
::Lutaml::Model::Serialize.register_to_format_method(format)
|
|
54
46
|
|
|
55
|
-
# Push format-specific serialization method name to warn list
|
|
56
47
|
::Lutaml::Model::Attribute.format_specific_warn_names.push(:"to_#{format}")
|
|
57
48
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@adapters ||= {}
|
|
67
|
-
@adapters[format] = adapter_klass
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Define _type suffixed methods on Config module (delegate to Configuration#set_adapter)
|
|
71
|
-
Lutaml::Model::Config.define_singleton_method(:"#{format}_adapter_type=") do |type_name|
|
|
72
|
-
instance.set_adapter(format, type_name)
|
|
49
|
+
# Register adapter metadata with AdapterResolver
|
|
50
|
+
if adapter_options
|
|
51
|
+
# Selectable adapters — have multiple options (xml, toml, json, yaml, hash)
|
|
52
|
+
AdapterResolver.register_metadata(format, adapter_options)
|
|
53
|
+
elsif adapter_class
|
|
54
|
+
# Fixed adapter (jsonl, yamls) — register as sole available adapter
|
|
55
|
+
adapter_name = derive_adapter_name(adapter_class)
|
|
56
|
+
AdapterResolver.register_fixed(format, adapter_class, adapter_name)
|
|
73
57
|
end
|
|
74
58
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
59
|
+
# Define adapter type accessor methods on Config module
|
|
60
|
+
::Lutaml::Model::Config.define_adapter_type_methods(format)
|
|
78
61
|
|
|
79
|
-
# Define adapter methods on Configuration class
|
|
62
|
+
# Define adapter accessor methods on Configuration class
|
|
80
63
|
define_configuration_adapter_methods(format, adapter_options)
|
|
81
64
|
|
|
82
65
|
registered_formats[format]
|
|
83
66
|
end
|
|
84
67
|
|
|
85
|
-
# Unregister a format
|
|
86
|
-
#
|
|
87
|
-
# @param format [Symbol] the format to unregister
|
|
88
|
-
# @return [Hash, nil] the removed format configuration or nil if not found
|
|
89
68
|
def unregister(format)
|
|
90
69
|
registered_formats.delete(format)
|
|
91
70
|
end
|
|
92
71
|
|
|
93
|
-
# Check if a format is registered
|
|
94
|
-
#
|
|
95
|
-
# @param format [Symbol] the format to check
|
|
96
|
-
# @return [Boolean]
|
|
97
72
|
def registered?(format)
|
|
98
73
|
registered_formats.key?(format)
|
|
99
74
|
end
|
|
100
75
|
|
|
101
|
-
# Get the mapping class for a format
|
|
102
|
-
#
|
|
103
|
-
# @param format [Symbol] the format name
|
|
104
|
-
# @return [Class, nil] the mapping class or nil if not registered
|
|
105
76
|
def mappings_class_for(format)
|
|
106
77
|
registered_formats.dig(format, :mapping_class)
|
|
107
78
|
end
|
|
108
79
|
|
|
109
|
-
# Get the transformer for a format
|
|
110
|
-
#
|
|
111
|
-
# @param format [Symbol] the format name
|
|
112
|
-
# @return [Class, nil] the transformer class or nil if not registered
|
|
113
80
|
def transformer_for(format)
|
|
114
81
|
registered_formats.dig(format, :transformer)
|
|
115
82
|
end
|
|
116
83
|
|
|
117
|
-
# Get the adapter class for a format
|
|
118
|
-
#
|
|
119
|
-
# @param format [Symbol] the format name
|
|
120
|
-
# @return [Class, nil] the adapter class or nil if not registered
|
|
121
84
|
def adapter_class_for(format)
|
|
122
85
|
registered_formats.dig(format, :adapter_class)
|
|
123
86
|
end
|
|
124
87
|
|
|
125
|
-
# Get the adapter loader for a format
|
|
126
|
-
#
|
|
127
|
-
# @param format [Symbol] the format name
|
|
128
|
-
# @return [Module, nil] the adapter loader or nil
|
|
129
88
|
def adapter_loader_for(format)
|
|
130
89
|
registered_formats.dig(format, :adapter_loader)
|
|
131
90
|
end
|
|
132
91
|
|
|
133
|
-
# Get the adapter options for a format
|
|
134
|
-
#
|
|
135
|
-
# @param format [Symbol] the format name
|
|
136
|
-
# @return [Hash, nil] { available: [...], default: :name } or nil
|
|
137
92
|
def adapter_options_for(format)
|
|
138
93
|
registered_formats.dig(format, :adapter_options)
|
|
139
94
|
end
|
|
140
95
|
|
|
141
|
-
# Get the castable type for a format
|
|
142
|
-
#
|
|
143
|
-
# @param format [Symbol] the format name
|
|
144
|
-
# @return [Class, nil] the castable type or nil
|
|
145
96
|
def castable_type_for(format)
|
|
146
97
|
registered_formats.dig(format, :castable_type)
|
|
147
98
|
end
|
|
148
99
|
|
|
149
|
-
# Get all registered format names
|
|
150
|
-
#
|
|
151
|
-
# @return [Array<Symbol>]
|
|
152
100
|
def formats
|
|
153
101
|
registered_formats.keys
|
|
154
102
|
end
|
|
155
103
|
|
|
156
|
-
# Get all key-value format names (non-XML formats)
|
|
157
|
-
#
|
|
158
|
-
# @return [Array<Symbol>]
|
|
159
104
|
def key_value_formats
|
|
160
105
|
registered_formats.select { |_, info| info[:key_value] }.keys
|
|
161
106
|
end
|
|
162
107
|
|
|
163
|
-
# Check if a format is a key-value format
|
|
164
|
-
#
|
|
165
|
-
# @param format [Symbol] the format name
|
|
166
|
-
# @return [Boolean]
|
|
167
108
|
def key_value?(format)
|
|
168
109
|
registered_formats.dig(format, :key_value) == true
|
|
169
110
|
end
|
|
170
111
|
|
|
171
|
-
# Get format-specific error types
|
|
172
|
-
#
|
|
173
|
-
# @param format [Symbol] the format name
|
|
174
|
-
# @return [Array<Class>, nil] error types for this format
|
|
175
112
|
def error_types_for(format)
|
|
176
113
|
registered_formats.dig(format, :error_types)
|
|
177
114
|
end
|
|
178
115
|
|
|
179
|
-
# Get all registered error types across all formats
|
|
180
|
-
#
|
|
181
|
-
# @return [Array<Class>]
|
|
182
116
|
def all_error_types
|
|
183
117
|
registered_formats.values.filter_map do |info|
|
|
184
118
|
info[:error_types]
|
|
185
119
|
end.flatten.compact
|
|
186
120
|
end
|
|
187
121
|
|
|
188
|
-
# Get registration info for a format
|
|
189
|
-
#
|
|
190
|
-
# @param format [Symbol] the format name
|
|
191
|
-
# @return [Hash, nil] the registration info or nil if not found
|
|
192
122
|
def info(format)
|
|
193
123
|
registered_formats[format]
|
|
194
124
|
end
|
|
195
125
|
|
|
196
|
-
# Get all registered formats with their details
|
|
197
|
-
#
|
|
198
|
-
# @return [Hash{Symbol => Hash}]
|
|
199
126
|
def all
|
|
200
127
|
registered_formats.dup
|
|
201
128
|
end
|
|
202
129
|
|
|
203
|
-
# Reset the registry (primarily for testing)
|
|
204
|
-
#
|
|
205
|
-
# @return [void]
|
|
206
130
|
def reset!
|
|
207
131
|
@registered_formats = nil
|
|
208
132
|
end
|
|
209
133
|
|
|
210
134
|
private
|
|
211
135
|
|
|
212
|
-
# Get the internal registry hash
|
|
213
|
-
#
|
|
214
|
-
# @return [Hash]
|
|
215
136
|
def registered_formats
|
|
216
137
|
@registered_formats ||= {}
|
|
217
138
|
end
|
|
218
139
|
|
|
219
|
-
# Define adapter getter/setter methods on Configuration class
|
|
220
|
-
# for a dynamically registered format.
|
|
221
|
-
#
|
|
222
|
-
# @param format [Symbol] the format name
|
|
223
|
-
# @param adapter_options [Hash, nil] { available: [...], default: :name }
|
|
224
140
|
def define_configuration_adapter_methods(format, adapter_options)
|
|
225
141
|
cfg = ::Lutaml::Model::Configuration
|
|
226
|
-
return if cfg.method_defined?(:"#{format}_adapter")
|
|
142
|
+
return if cfg.method_defined?(:"#{format}_adapter=")
|
|
227
143
|
|
|
228
|
-
|
|
144
|
+
adapter_options&.dig(:default)
|
|
229
145
|
|
|
146
|
+
# Adapter class getter on Configuration instance
|
|
230
147
|
cfg.define_method(:"#{format}_adapter") do
|
|
231
|
-
|
|
148
|
+
AdapterResolver.adapter_for(format)
|
|
232
149
|
end
|
|
233
150
|
|
|
151
|
+
# Adapter type name setter on Configuration instance
|
|
234
152
|
cfg.define_method(:"#{format}_adapter=") do |adapter_type|
|
|
235
153
|
set_adapter(format, adapter_type)
|
|
236
154
|
end
|
|
237
155
|
|
|
156
|
+
# Aliased _type methods
|
|
238
157
|
cfg.send(:alias_method, :"#{format}_adapter_type=",
|
|
239
158
|
:"#{format}_adapter=")
|
|
240
159
|
cfg.send(:alias_method, :"#{format}_adapter_type",
|
|
241
160
|
:"#{format}_adapter")
|
|
242
161
|
end
|
|
243
162
|
|
|
244
|
-
#
|
|
163
|
+
# Derive a symbol adapter name from an adapter class.
|
|
245
164
|
#
|
|
246
|
-
# @param
|
|
247
|
-
# @
|
|
248
|
-
|
|
249
|
-
|
|
165
|
+
# @param adapter_class [Class] e.g., Lutaml::Json::Adapter::StandardAdapter
|
|
166
|
+
# @return [Symbol] e.g., :standard
|
|
167
|
+
def derive_adapter_name(adapter_class)
|
|
168
|
+
name = adapter_class.name
|
|
169
|
+
return :standard unless name
|
|
170
|
+
|
|
171
|
+
# Extract the adapter type from class name: "...::StandardAdapter" → :standard
|
|
172
|
+
short = name.split("::").last
|
|
173
|
+
short = short.delete_suffix("Adapter")
|
|
174
|
+
short.downcase.to_sym
|
|
175
|
+
end
|
|
176
|
+
|
|
250
177
|
def validate_registration!(format, mapping_class, transformer)
|
|
251
178
|
unless format.is_a?(Symbol)
|
|
252
179
|
raise ArgumentError,
|
|
@@ -91,13 +91,15 @@ module Lutaml
|
|
|
91
91
|
#
|
|
92
92
|
# @param args [Array] Arguments to pass to the handler block
|
|
93
93
|
# @return [Object] Result of the handler invocation
|
|
94
|
-
|
|
94
|
+
# rubocop:disable Style/ArgumentsForwarding -- anonymous * requires Ruby 3.2+
|
|
95
|
+
def call(*args)
|
|
95
96
|
if simple?
|
|
96
97
|
raise NoHandlerError,
|
|
97
98
|
"Cannot call simple listener #{id.inspect}"
|
|
98
99
|
end
|
|
99
100
|
|
|
100
|
-
@handler.call(*)
|
|
101
|
+
@handler.call(*args)
|
|
102
|
+
# rubocop:enable Style/ArgumentsForwarding
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
# Error raised when attempting to invoke a simple listener as a complex one.
|