lutaml-model 0.8.23 → 0.8.24
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/performance.yml +1 -1
- data/Gemfile +6 -0
- data/README.adoc +16 -7
- data/docs/_guides/index.adoc +4 -0
- data/docs/_guides/native-engines.adoc +61 -0
- data/docs/_pages/configuration.adoc +11 -7
- data/docs/_pages/serialization_adapters.adoc +14 -5
- data/lib/compat/opal/lutaml_model_boot.rb +2 -0
- data/lib/lutaml/json/adapter/multi_json_adapter.rb +12 -1
- data/lib/lutaml/json/adapter/yeptris_adapter.rb +21 -0
- data/lib/lutaml/json/adapter.rb +1 -0
- data/lib/lutaml/json/format.rb +1 -1
- data/lib/lutaml/key_value/adapter/json/multi_json_adapter.rb +16 -1
- data/lib/lutaml/key_value/adapter/json/yeptris_adapter.rb +15 -0
- data/lib/lutaml/key_value/adapter/toml/teptris_adapter.rb +42 -0
- data/lib/lutaml/key_value/adapter/toml.rb +1 -0
- data/lib/lutaml/key_value/adapter/yaml/yeptris_adapter.rb +15 -0
- data/lib/lutaml/key_value/transform.rb +82 -0
- data/lib/lutaml/key_value/transformation/value_serializer.rb +25 -0
- data/lib/lutaml/key_value/transformation.rb +25 -1
- data/lib/lutaml/model/adapter_resolver.rb +23 -6
- data/lib/lutaml/model/attribute.rb +31 -6
- data/lib/lutaml/model/configuration.rb +5 -0
- data/lib/lutaml/model/error/unknown_adapter_type_error.rb +4 -4
- data/lib/lutaml/model/serialize/format_conversion.rb +37 -0
- data/lib/lutaml/model/toml.rb +10 -6
- data/lib/lutaml/model/type/date_time.rb +2 -2
- data/lib/lutaml/model/type/value.rb +24 -2
- data/lib/lutaml/model/utils.rb +6 -5
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/toml/adapter/teptris_adapter.rb +14 -0
- data/lib/lutaml/toml/adapter.rb +1 -0
- data/lib/lutaml/toml/format.rb +1 -1
- data/lib/lutaml/xml/adapter/leptris_adapter.rb +15 -0
- data/lib/lutaml/xml/adapter_element.rb +111 -3
- data/lib/lutaml/xml/builder/base.rb +3 -1
- data/lib/lutaml/xml/builder/leptris.rb +13 -0
- data/lib/lutaml/xml/builder/nokogiri.rb +3 -0
- data/lib/lutaml/xml/builder.rb +1 -0
- data/lib/lutaml/xml/format.rb +4 -1
- data/lib/lutaml/xml/leptris/element.rb +34 -0
- data/lib/lutaml/xml/plan_compiler.rb +139 -0
- data/lib/lutaml/xml/plan_hydrator.rb +80 -0
- data/lib/lutaml/xml/transformation/custom_method_wrapper.rb +8 -0
- data/lib/lutaml/xml/xml_element.rb +6 -2
- data/lib/lutaml/yaml/adapter/yeptris_adapter.rb +40 -0
- data/lib/lutaml/yaml/adapter.rb +5 -0
- data/lib/lutaml/yaml/format.rb +1 -1
- data/lib/tasks/performance.rake +5 -0
- data/lib/tasks/performance_comparator.rb +19 -3
- data/lib/tasks/performance_probe.rake +33 -0
- data/lib/tasks/performance_probe.rb +106 -0
- data/lib/tasks/performance_probe_xml.rb +54 -0
- data/lutaml-model.gemspec +1 -1
- data/spec/lutaml/json/yeptris_adapter_spec.rb +63 -0
- data/spec/lutaml/key_value/adapter/toml/teptris_adapter_spec.rb +71 -0
- data/spec/lutaml/model/json_adapter_spec.rb +6 -0
- data/spec/lutaml/model/toml_adapter_spec.rb +8 -0
- data/spec/lutaml/model/toml_spec.rb +36 -1
- data/spec/lutaml/xml/plan_fast_path_spec.rb +96 -0
- data/spec/lutaml/xml/xml_spec.rb +13 -0
- data/spec/lutaml/yaml/yeptris_adapter_spec.rb +63 -0
- data/spec/spec_helper.rb +7 -1
- metadata +23 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8aca94b467fe681429ee9863355555d3aa003d9deda8543a74fd1424087bcc8
|
|
4
|
+
data.tar.gz: 02db8dceef19fc7ef7abd3a54f0936e00ef162a1ee6477191a8b1d3477ba7860
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4da87513ed839ba070c32b28de27411ffe12ccc92f879a0dbe433aad85ea3a32a76759b36112958a131b09a747b1618c0ff8a95e92238aca40f45b736dd46950
|
|
7
|
+
data.tar.gz: ff488be2b79ef92c3017d63ee0a9bf4ac3b183549be5b17a59efcb42ea31ae00cacb2b63995f9b69fbe3628af9bd65f30dfedb2fb5d2722fe15189c7e964a93b
|
data/Gemfile
CHANGED
|
@@ -19,6 +19,7 @@ gem "benchmark-ips"
|
|
|
19
19
|
gem "bigdecimal"
|
|
20
20
|
gem "canon" # , path: "../canon"
|
|
21
21
|
gem "json-ld"
|
|
22
|
+
gem "leptris"
|
|
22
23
|
gem "liquid", "~> 5"
|
|
23
24
|
gem "multi_json"
|
|
24
25
|
gem "nokogiri"
|
|
@@ -26,8 +27,11 @@ gem "oj"
|
|
|
26
27
|
gem "openssl", "~> 3.0"
|
|
27
28
|
gem "ox"
|
|
28
29
|
gem "rake"
|
|
30
|
+
# yeptris ships prebuilt platform gems for linux/darwin only; a source
|
|
31
|
+
# build is not wired for windows runners.
|
|
29
32
|
gem "rdf-turtle"
|
|
30
33
|
gem "rexml"
|
|
34
|
+
gem "yeptris" unless Gem.win_platform?
|
|
31
35
|
# TODO: revert rng branch to main when lutaml/rng#32 is merged
|
|
32
36
|
gem "rng", git: "https://github.com/lutaml/rng", branch: "main"
|
|
33
37
|
gem "rspec"
|
|
@@ -35,6 +39,8 @@ gem "rubocop"
|
|
|
35
39
|
gem "rubocop-performance", require: false
|
|
36
40
|
gem "rubocop-rake", require: false
|
|
37
41
|
gem "rubocop-rspec", require: false
|
|
42
|
+
# teptris ships prebuilt gems for every platform (incl. mingw-ucrt)
|
|
43
|
+
gem "teptris"
|
|
38
44
|
gem "tomlib"
|
|
39
45
|
gem "toml-rb"
|
|
40
46
|
|
data/README.adoc
CHANGED
|
@@ -16223,7 +16223,7 @@ Adapters are auto-detected on first use. The defaults when gems are available:
|
|
|
16223
16223
|
* YAML: `:standard` (alias: `:standard_yaml`)
|
|
16224
16224
|
* JSON: `:standard` (alias: `:standard_json`)
|
|
16225
16225
|
* Hash: `:standard` (alias: `:standard_hash`)
|
|
16226
|
-
* TOML: `:tomlib` on non-Windows, `:toml_rb` on Windows
|
|
16226
|
+
* TOML: `:teptris` if available, else `:tomlib` on non-Windows, `:toml_rb` on Windows
|
|
16227
16227
|
|
|
16228
16228
|
|
|
16229
16229
|
==== Configure adapters through symbol choices
|
|
@@ -16246,7 +16246,7 @@ Lutaml::Model::Config.configure do |config|
|
|
|
16246
16246
|
config.hash_adapter = :standard
|
|
16247
16247
|
config.yaml_adapter = :standard
|
|
16248
16248
|
config.json_adapter = :standard # can be one of [:standard, :multi_json, :oj]
|
|
16249
|
-
config.toml_adapter = :toml_rb # can be one of [:toml_rb, :tomlib]
|
|
16249
|
+
config.toml_adapter = :toml_rb # can be one of [:teptris, :toml_rb, :tomlib]
|
|
16250
16250
|
end
|
|
16251
16251
|
----
|
|
16252
16252
|
|
|
@@ -16388,22 +16388,29 @@ end
|
|
|
16388
16388
|
|
|
16389
16389
|
Lutaml::Model supports the following TOML adapters:
|
|
16390
16390
|
|
|
16391
|
+
Teptris::
|
|
16392
|
+
(default when the `teptris` gem is in the bundle — every platform)
|
|
16393
|
+
The native TOML engine (libteptris via FFI). Value semantics mirror
|
|
16394
|
+
tomlib: offset and local datetimes become `Time`, dates become `Date`,
|
|
16395
|
+
local times stay `String`.
|
|
16396
|
+
Requires the `teptris` gem.
|
|
16397
|
+
|
|
16391
16398
|
Toml-rb::
|
|
16392
|
-
(default on Windows)
|
|
16399
|
+
(default on Windows without teptris)
|
|
16393
16400
|
A TOML parser and serializer for Ruby that is compatible with the TOML v1.0.0
|
|
16394
16401
|
specification.
|
|
16395
16402
|
Requires the `toml-rb` gem.
|
|
16396
16403
|
|
|
16397
16404
|
Tomlib::
|
|
16398
|
-
(default on non-Windows platforms)
|
|
16405
|
+
(default on non-Windows platforms without teptris)
|
|
16399
16406
|
Toml-rb fork that is compatible with the TOML v1.0.0 specification, but with
|
|
16400
16407
|
additional features.
|
|
16401
16408
|
Requires the `tomlib` gem.
|
|
16402
16409
|
|
|
16403
16410
|
IMPORTANT: The Tomlib adapter is not available on Windows due to segmentation
|
|
16404
|
-
fault issues. On Windows, the `
|
|
16405
|
-
default. If you explicitly
|
|
16406
|
-
`ConfigurationError` will be raised.
|
|
16411
|
+
fault issues. On Windows, the `teptris` adapter (when bundled) or the
|
|
16412
|
+
`toml-rb` adapter is used automatically as the default. If you explicitly
|
|
16413
|
+
configure `tomlib` on Windows, a `ConfigurationError` will be raised.
|
|
16407
16414
|
|
|
16408
16415
|
|
|
16409
16416
|
.Using a TOML adapter
|
|
@@ -16412,6 +16419,8 @@ default. If you explicitly configure `tomlib` on Windows, a
|
|
|
16412
16419
|
require 'lutaml/model'
|
|
16413
16420
|
|
|
16414
16421
|
Lutaml::Model::Config.configure do |config|
|
|
16422
|
+
config.toml_adapter = :teptris
|
|
16423
|
+
# or
|
|
16415
16424
|
config.toml_adapter = :toml_rb
|
|
16416
16425
|
# or (not available on Windows)
|
|
16417
16426
|
config.toml_adapter = :tomlib
|
data/docs/_guides/index.adoc
CHANGED
|
@@ -22,6 +22,10 @@ Task-oriented guides for accomplishing specific goals with Lutaml::Model.
|
|
|
22
22
|
* link:../keyvalue-serialization[Key-Value Serialization] - JSON/YAML/TOML/Hash
|
|
23
23
|
* link:../collection-serialization[Collection Serialization] - JSONL and YAML Stream
|
|
24
24
|
|
|
25
|
+
== Performance
|
|
26
|
+
|
|
27
|
+
* link:../native-engines[Native engines (leptris / yeptris / teptris)] - Drop-in XML/YAML/JSON/TOML engine upgrades via bundle opt-in
|
|
28
|
+
|
|
25
29
|
== Linked Data serialization
|
|
26
30
|
|
|
27
31
|
* link:../rdf-serialization[Unified RDF Serialization] - One `rdf` block for both JSON-LD and Turtle
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
= Native engines (leptris / yeptris / teptris)
|
|
2
|
+
|
|
3
|
+
By default, `lutaml-model` serializes through the same engines every Ruby
|
|
4
|
+
install already has: Nokogiri (via moxml) for XML, Psych for YAML, the
|
|
5
|
+
`json` gem for JSON.
|
|
6
|
+
|
|
7
|
+
The leptris family of native engines is a drop-in performance upgrade:
|
|
8
|
+
|
|
9
|
+
* **leptris** — the XML engine moxml drives. When the `leptris` gem is in
|
|
10
|
+
your bundle, moxml prefers it over Nokogiri for parsing and
|
|
11
|
+
serialization.
|
|
12
|
+
* **yeptris** — the YAML/JSON engine. When the `yeptris` gem is in your
|
|
13
|
+
bundle, lutaml-model's YAML and JSON adapters resolve to the yeptris
|
|
14
|
+
engine.
|
|
15
|
+
* **teptris** — the TOML engine. When the `teptris` gem is in your
|
|
16
|
+
bundle it becomes the TOML default on every platform (0.2.12+ parses
|
|
17
|
+
~3x and dumps ~8x faster than tomlib, and the Windows mingw-ucrt
|
|
18
|
+
prebuilts are back), giving Windows native TOML and retiring the
|
|
19
|
+
tomlib-segfault workaround (previously pure-Ruby toml-rb only).
|
|
20
|
+
|
|
21
|
+
Opting in is purely a bundle decision — no code changes, no gemspec
|
|
22
|
+
requirements:
|
|
23
|
+
|
|
24
|
+
[source,sh]
|
|
25
|
+
----
|
|
26
|
+
bundle add leptris yeptris teptris
|
|
27
|
+
----
|
|
28
|
+
|
|
29
|
+
leptris and yeptris ship prebuilt platform gems for Linux and macOS
|
|
30
|
+
(yeptris has no Windows prebuilts). teptris ships prebuilt gems for
|
|
31
|
+
every platform, including Windows. When a gem is absent, lutaml-model
|
|
32
|
+
automatically falls back to the standard engine for that format.
|
|
33
|
+
|
|
34
|
+
== Selecting adapters explicitly
|
|
35
|
+
|
|
36
|
+
The engines register as ordinary adapter types, so explicit selection and
|
|
37
|
+
the availability error messages work as usual:
|
|
38
|
+
|
|
39
|
+
[source,ruby]
|
|
40
|
+
----
|
|
41
|
+
Lutaml::Model::Config.xml_adapter_type = :leptris
|
|
42
|
+
Lutaml::Model::Config.yaml_adapter_type = :yeptris
|
|
43
|
+
Lutaml::Model::Config.json_adapter_type = :yeptris
|
|
44
|
+
Lutaml::Model::Config.toml_adapter_type = :teptris
|
|
45
|
+
----
|
|
46
|
+
|
|
47
|
+
== Semantics notes
|
|
48
|
+
|
|
49
|
+
* **YAML**: the yeptris adapter uses the engine's `compat_11` schema, so
|
|
50
|
+
implicit typing matches Psych (parity-verified across the scalar
|
|
51
|
+
battery: dates, timestamps, octal, sexagesimal, `yes`/`no`, `.inf`).
|
|
52
|
+
Two deliberate differences: anchors and aliases always resolve, and a
|
|
53
|
+
tagged value without a core type materializes as plain data instead of
|
|
54
|
+
raising `Psych::DisallowedClass`.
|
|
55
|
+
* **JSON**: `yeptris` parsing targets exact `JSON.parse` semantics;
|
|
56
|
+
generation stays on the `json` gem.
|
|
57
|
+
* **TOML**: the teptris adapter mirrors tomlib value semantics — offset
|
|
58
|
+
and local datetimes become `Time`, dates become `Date`, local times
|
|
59
|
+
stay `String`. Parse failures raise `Teptris::ParseError` with line
|
|
60
|
+
and column, wrapped as `InvalidFormatError` like the other TOML
|
|
61
|
+
adapters.
|
|
@@ -23,7 +23,7 @@ The auto-detection order for each format:
|
|
|
23
23
|
|===
|
|
24
24
|
| Format | Detection order
|
|
25
25
|
| XML | `:nokogiri` → `:ox` → `:oga` → `:rexml`
|
|
26
|
-
| TOML | `:tomlib` → `:toml_rb` (Windows: `:toml_rb`
|
|
26
|
+
| TOML | `:teptris` → `:tomlib` → `:toml_rb` (Windows: `:teptris` → `:toml_rb`)
|
|
27
27
|
| JSON | `:standard` (always available)
|
|
28
28
|
| YAML | `:standard` (always available)
|
|
29
29
|
| Hash | `:standard` (always available)
|
|
@@ -109,14 +109,17 @@ When an adapter is needed for a format, `AdapterResolver` follows this chain:
|
|
|
109
109
|
|===
|
|
110
110
|
| Adapter | Description
|
|
111
111
|
|
|
112
|
+
| `:teptris` (default when bundled)
|
|
113
|
+
| Native engine (libteptris via FFI), all platforms including Windows. tomlib-compatible value semantics.
|
|
114
|
+
|
|
112
115
|
| `:tomlib` (default on non-Windows)
|
|
113
116
|
| Enhanced fork. Better performance on non-Windows platforms.
|
|
114
117
|
|
|
115
|
-
| `:toml_rb` (default on Windows)
|
|
116
|
-
| Pure Ruby. TOML v1.0.0 compatible.
|
|
118
|
+
| `:toml_rb` (default on Windows without teptris)
|
|
119
|
+
| Pure Ruby. TOML v1.0.0 compatible.
|
|
117
120
|
|===
|
|
118
121
|
|
|
119
|
-
NOTE: The `:tomlib` adapter is not available on Windows due to segmentation fault issues. On Windows, `:toml_rb` is
|
|
122
|
+
NOTE: The `:tomlib` adapter is not available on Windows due to segmentation fault issues. On Windows, `:teptris` (when bundled) or `:toml_rb` is used as the default.
|
|
120
123
|
|
|
121
124
|
== Per-operation adapter override
|
|
122
125
|
|
|
@@ -226,7 +229,7 @@ If not explicitly configured, Lutaml::Model auto-detects on first use:
|
|
|
226
229
|
* JSON: `:standard` (alias: `:standard_json`)
|
|
227
230
|
* YAML: `:standard` (alias: `:standard_yaml`)
|
|
228
231
|
* Hash: `:standard` (alias: `:standard_hash`)
|
|
229
|
-
* TOML: `:tomlib` on non-Windows, `:toml_rb` on Windows
|
|
232
|
+
* TOML: `:teptris` if available, else `:tomlib` on non-Windows, `:toml_rb` on Windows
|
|
230
233
|
|
|
231
234
|
== When to configure
|
|
232
235
|
|
|
@@ -272,8 +275,9 @@ end
|
|
|
272
275
|
|
|
273
276
|
=== Choose TOML adapter based on
|
|
274
277
|
|
|
275
|
-
* **
|
|
276
|
-
* **
|
|
278
|
+
* **Teptris**: Bundled native engine — ~3x faster parse, ~8x faster dump than tomlib, every platform
|
|
279
|
+
* **Tomlib**: Non-Windows projects without teptris (better performance)
|
|
280
|
+
* **TomlRb**: Windows platforms without teptris (required due to tomlib incompatibility)
|
|
277
281
|
|
|
278
282
|
== Opal runtime compatibility
|
|
279
283
|
|
|
@@ -48,7 +48,7 @@ more information.
|
|
|
48
48
|
Adapters are resolved lazily on first use. If no adapter is explicitly configured, `AdapterResolver` probes for available gems in a preferred order:
|
|
49
49
|
|
|
50
50
|
* **XML**: `:nokogiri` → `:ox` → `:oga` → `:rexml`
|
|
51
|
-
* **TOML**: `:tomlib` → `:toml_rb` (Windows: `:toml_rb`
|
|
51
|
+
* **TOML**: `:teptris` → `:tomlib` → `:toml_rb` (Windows: `:teptris` → `:toml_rb`)
|
|
52
52
|
* **JSON/YAML/Hash**: `:standard` (always available)
|
|
53
53
|
|
|
54
54
|
The detection result is cached after the first probe, so it only runs once per format.
|
|
@@ -84,7 +84,7 @@ There is a default configuration for adapters for commonly used formats:
|
|
|
84
84
|
* YAML: `:standard` (alias: `:standard_yaml`)
|
|
85
85
|
* JSON: `:standard` (alias: `:standard_json`)
|
|
86
86
|
* Hash: `:standard` (alias: `:standard_hash`)
|
|
87
|
-
* TOML: `:tomlib` on non-Windows, `:toml_rb` on Windows
|
|
87
|
+
* TOML: `:teptris` if available, else `:tomlib` on non-Windows, `:toml_rb` on Windows
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
==== Configure adapters through symbol choices
|
|
@@ -107,7 +107,7 @@ Lutaml::Model::Config.configure do |config|
|
|
|
107
107
|
config.hash_adapter = :standard
|
|
108
108
|
config.yaml_adapter = :standard
|
|
109
109
|
config.json_adapter = :standard # can be one of [:standard, :multi_json, :oj]
|
|
110
|
-
config.toml_adapter = :toml_rb # can be one of [:toml_rb, :tomlib] (tomlib not available on Windows)
|
|
110
|
+
config.toml_adapter = :toml_rb # can be one of [:teptris, :toml_rb, :tomlib] (tomlib not available on Windows)
|
|
111
111
|
end
|
|
112
112
|
----
|
|
113
113
|
|
|
@@ -285,8 +285,16 @@ end
|
|
|
285
285
|
|
|
286
286
|
Lutaml::Model supports the following TOML adapters:
|
|
287
287
|
|
|
288
|
+
Teptris::
|
|
289
|
+
(default when the `teptris` gem is in the bundle — every platform)
|
|
290
|
+
The native TOML engine (libteptris via FFI). Value semantics mirror
|
|
291
|
+
tomlib: offset and local datetimes become `Time`, dates become `Date`,
|
|
292
|
+
local times stay `String`. Parse failures raise `Teptris::ParseError`
|
|
293
|
+
with line and column.
|
|
294
|
+
Requires the `teptris` gem.
|
|
295
|
+
|
|
288
296
|
Tomlib::
|
|
289
|
-
(default on non-Windows)
|
|
297
|
+
(default on non-Windows when teptris is absent)
|
|
290
298
|
Toml-rb fork that is compatible with the TOML v1.0.0 specification, with
|
|
291
299
|
additional features and better performance.
|
|
292
300
|
Requires the `tomlib` gem.
|
|
@@ -348,4 +356,5 @@ raised to indicate that the input format is malformed and cannot be parsed.
|
|
|
348
356
|
|
|
349
357
|
NOTE: The `:tomlib` TOML adapter is disabled on Windows due to segmentation
|
|
350
358
|
fault issues. Attempting to configure `:tomlib` on Windows will raise an
|
|
351
|
-
`ArgumentError`. Use `:toml_rb` on
|
|
359
|
+
`ArgumentError`. Use `:teptris` (native, all platforms) or `:toml_rb` on
|
|
360
|
+
Windows instead.
|
|
@@ -167,6 +167,8 @@ require "lutaml/xml/oga"
|
|
|
167
167
|
require "lutaml/xml/parse_session"
|
|
168
168
|
require "lutaml/xml/parsed_namespace_declaration"
|
|
169
169
|
require "lutaml/xml/parsed_namespace_set"
|
|
170
|
+
require "lutaml/xml/plan_compiler"
|
|
171
|
+
require "lutaml/xml/plan_hydrator"
|
|
170
172
|
require "lutaml/xml/polymorphic_value_handler"
|
|
171
173
|
require "lutaml/xml/qualified_inheritance_strategy"
|
|
172
174
|
require "lutaml/xml/rexml"
|
|
@@ -16,6 +16,8 @@ module Lutaml
|
|
|
16
16
|
"multi_json gem is not available. Please add 'multi_json' to your Gemfile."
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
INTERNAL_LUTAML_KEYS = %i[register adapter _adapter_override].freeze
|
|
20
|
+
|
|
19
21
|
# rubocop:disable Style/ArgumentsForwarding -- anonymous * requires Ruby 3.2+
|
|
20
22
|
def to_json(*args)
|
|
21
23
|
require "multi_json"
|
|
@@ -28,7 +30,16 @@ module Lutaml
|
|
|
28
30
|
@attributes
|
|
29
31
|
end
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
# LutaML's own threading keys must not reach the backend; the
|
|
34
|
+
# rest (e.g. :pretty) are meaningful to multi_json. The json
|
|
35
|
+
# gem 3.x and Oj raise on unknown generator options, unlike
|
|
36
|
+
# json 2.x which ignored them.
|
|
37
|
+
dump_args = args.map do |arg|
|
|
38
|
+
next arg unless arg.is_a?(::Hash)
|
|
39
|
+
|
|
40
|
+
arg.except(*INTERNAL_LUTAML_KEYS)
|
|
41
|
+
end
|
|
42
|
+
MultiJson.dump(attributes_to_serialize, *dump_args)
|
|
32
43
|
# rubocop:enable Style/ArgumentsForwarding
|
|
33
44
|
rescue LoadError
|
|
34
45
|
raise LoadError,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yeptris"
|
|
4
|
+
require_relative "standard_adapter"
|
|
5
|
+
|
|
6
|
+
module Lutaml
|
|
7
|
+
module Json
|
|
8
|
+
module Adapter
|
|
9
|
+
# JSON parsing over the yeptris engine: Yeptris::JSON.load targets
|
|
10
|
+
# exact JSON.parse semantics (spec-pinned upstream), with a fused
|
|
11
|
+
# native materializer when the loaded libyeptris build carries it.
|
|
12
|
+
# Generation stays on the json gem (the yeptris JSON surface is
|
|
13
|
+
# load-only), so everything else inherits from the standard adapter.
|
|
14
|
+
class YeptrisAdapter < StandardAdapter
|
|
15
|
+
def self.parse(json, _options = {})
|
|
16
|
+
::Yeptris::JSON.load(json)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/lutaml/json/adapter.rb
CHANGED
data/lib/lutaml/json/format.rb
CHANGED
|
@@ -52,7 +52,7 @@ Lutaml::Model::FormatRegistry.register(
|
|
|
52
52
|
transformer: Lutaml::Json::Adapter::Transform,
|
|
53
53
|
key_value: true,
|
|
54
54
|
adapter_options: {
|
|
55
|
-
available: %i[standard standard_json multi_json oj],
|
|
55
|
+
available: %i[standard standard_json multi_json oj yeptris],
|
|
56
56
|
default: :standard,
|
|
57
57
|
},
|
|
58
58
|
)
|
|
@@ -20,6 +20,8 @@ module Lutaml
|
|
|
20
20
|
"multi_json gem is not available. Please add 'multi_json' to your Gemfile."
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
INTERNAL_LUTAML_KEYS = %i[register adapter _adapter_override].freeze
|
|
24
|
+
|
|
23
25
|
# rubocop:disable Style/ArgumentsForwarding -- anonymous * requires Ruby 3.2+
|
|
24
26
|
def to_json(*args)
|
|
25
27
|
require "multi_json"
|
|
@@ -32,7 +34,20 @@ module Lutaml
|
|
|
32
34
|
@attributes
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
# LutaML's own options must not reach the backend: the json
|
|
38
|
+
# gem 3.x and Oj both raise on unknown generator options
|
|
39
|
+
# (multi_json's backend choice shifts with the resolved json
|
|
40
|
+
# version), unlike json 2.x which ignored them.
|
|
41
|
+
# LutaML's own threading keys must not reach the backend; the
|
|
42
|
+
# rest (e.g. :pretty) are meaningful to multi_json. The json
|
|
43
|
+
# gem 3.x and Oj raise on unknown generator options, unlike
|
|
44
|
+
# json 2.x which ignored them.
|
|
45
|
+
dump_args = args.map do |arg|
|
|
46
|
+
next arg unless arg.is_a?(::Hash)
|
|
47
|
+
|
|
48
|
+
arg.except(*INTERNAL_LUTAML_KEYS)
|
|
49
|
+
end
|
|
50
|
+
MultiJson.dump(attributes_to_serialize, *dump_args)
|
|
36
51
|
# rubocop:enable Style/ArgumentsForwarding
|
|
37
52
|
rescue LoadError
|
|
38
53
|
raise LoadError,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../json/adapter/yeptris_adapter"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module KeyValue
|
|
7
|
+
module Adapter
|
|
8
|
+
module Json
|
|
9
|
+
# Backward-compatibility entry for the adapter resolver's default
|
|
10
|
+
# load path; the implementation lives in Lutaml::Json::Adapter.
|
|
11
|
+
YeptrisAdapter = Lutaml::Json::Adapter::YeptrisAdapter
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# TOML over the teptris engine (libteptris via FFI). teptris ships
|
|
4
|
+
# prebuilt platform gems for every platform we support — including
|
|
5
|
+
# mingw-ucrt — which retires the "no native TOML on Windows" gap that
|
|
6
|
+
# keeps tomlib off Windows. Value semantics mirror tomlib: offset and
|
|
7
|
+
# local datetimes become Time, dates become Date, local times stay
|
|
8
|
+
# String. Parse failures raise Teptris::ParseError (line/column).
|
|
9
|
+
|
|
10
|
+
module Lutaml
|
|
11
|
+
module KeyValue
|
|
12
|
+
module Adapter
|
|
13
|
+
module Toml
|
|
14
|
+
class TeptrisAdapter < Document
|
|
15
|
+
# teptris is required lazily (in parse/to_toml), NOT at file
|
|
16
|
+
# load: a platform gem whose native library is broken must
|
|
17
|
+
# degrade to detection-fallback + skipped specs, never crash
|
|
18
|
+
# unrelated loads. The gem's own require raises a clear
|
|
19
|
+
# LoadError ("no fallback by design") for that case.
|
|
20
|
+
def self.parse(toml, _options = {})
|
|
21
|
+
require "teptris"
|
|
22
|
+
Teptris::TOML.load(toml)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_toml(*)
|
|
26
|
+
require "teptris"
|
|
27
|
+
# Handle KeyValueElement input (new symmetric architecture)
|
|
28
|
+
attributes_to_serialize = if @attributes.is_a?(Lutaml::KeyValue::DataModel::Element)
|
|
29
|
+
# Unwrap __root__ wrapper to get actual content
|
|
30
|
+
@attributes.to_hash["__root__"]
|
|
31
|
+
else
|
|
32
|
+
# Legacy Hash input (backward compatibility)
|
|
33
|
+
@attributes
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Teptris::TOML.dump(attributes_to_serialize)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -12,6 +12,7 @@ module Lutaml
|
|
|
12
12
|
autoload :Transform, "#{__dir__}/toml/transform"
|
|
13
13
|
Lutaml::Model::RuntimeCompatibility.autoload_native(
|
|
14
14
|
self,
|
|
15
|
+
TeptrisAdapter: "#{__dir__}/toml/teptris_adapter",
|
|
15
16
|
TomlibAdapter: "#{__dir__}/toml/tomlib_adapter",
|
|
16
17
|
TomlRbAdapter: "#{__dir__}/toml/toml_rb_adapter",
|
|
17
18
|
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../yaml/adapter/yeptris_adapter"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module KeyValue
|
|
7
|
+
module Adapter
|
|
8
|
+
module Yaml
|
|
9
|
+
# Backward-compatibility entry for the adapter resolver's default
|
|
10
|
+
# load path; the implementation lives in Lutaml::Yaml::Adapter.
|
|
11
|
+
YeptrisAdapter = Lutaml::Yaml::Adapter::YeptrisAdapter
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -244,6 +244,13 @@ format)
|
|
|
244
244
|
rule, attr
|
|
245
245
|
)
|
|
246
246
|
|
|
247
|
+
if (plan = kv_rule_plan(format, rule, attr)) &&
|
|
248
|
+
(value = kv_fast_extract(doc, plan))
|
|
249
|
+
rule.deserialize(instance, kv_fast_cast(value, plan, instance),
|
|
250
|
+
attributes, self)
|
|
251
|
+
return
|
|
252
|
+
end
|
|
253
|
+
|
|
247
254
|
value = rule_value_extractor_class.call(rule, doc, format, attr,
|
|
248
255
|
lutaml_register, options, instance)
|
|
249
256
|
value = apply_value_map(value, rule.value_map(:from, options), attr)
|
|
@@ -269,6 +276,81 @@ format)
|
|
|
269
276
|
rule.deserialize(instance, value, attributes, self)
|
|
270
277
|
end
|
|
271
278
|
|
|
279
|
+
# Compiled rule plans (TODO.perf/07): for plain scalar rules the
|
|
280
|
+
# per-value interpretation (extraction dispatch, type resolution,
|
|
281
|
+
# per-element probes, validation inside casts) collapses to a
|
|
282
|
+
# precomputed wire name, target class, and identity-guard cast.
|
|
283
|
+
# Guard + cached target — an inline cache per rule. Complex rules
|
|
284
|
+
# (custom methods, transforms, value maps, delegates, hash mappings,
|
|
285
|
+
# unions, polymorphism) return nil and keep the interpretive path.
|
|
286
|
+
KvRulePlan = ::Struct.new(:wire, :klass, :collection)
|
|
287
|
+
|
|
288
|
+
def kv_rule_plan(format, rule, attr)
|
|
289
|
+
@kv_rule_plans ||= {}
|
|
290
|
+
@kv_rule_plans[[format, rule]] ||= build_kv_rule_plan(format, rule,
|
|
291
|
+
attr)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# `rule.polymorphic` defaults to an empty Hash, which is truthy —
|
|
295
|
+
# presence is emptiness-based, mirroring MappingRule#polymorphic_mapping?.
|
|
296
|
+
def polymorphic_rule?(rule)
|
|
297
|
+
poly = rule.polymorphic
|
|
298
|
+
poly.respond_to?(:empty?) ? !poly.empty? : !!poly
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def build_kv_rule_plan(_format, rule, attr)
|
|
302
|
+
return nil if rule.raw_mapping? ||
|
|
303
|
+
rule.has_custom_method_for_deserialization? ||
|
|
304
|
+
rule.hash_mappings || rule.multiple_mappings? ||
|
|
305
|
+
polymorphic_rule?(rule) || rule.delegate ||
|
|
306
|
+
(rule.transform.is_a?(Hash) && !rule.transform.empty?) ||
|
|
307
|
+
rule.transform.is_a?(Class) ||
|
|
308
|
+
attr.derived? ||
|
|
309
|
+
attr.union? || attr.polymorphic? ||
|
|
310
|
+
attr.custom_collection?
|
|
311
|
+
|
|
312
|
+
type = attr.type(lutaml_register)
|
|
313
|
+
return nil unless type.is_a?(Class) &&
|
|
314
|
+
type < ::Lutaml::Model::Type::Value &&
|
|
315
|
+
!attr.value_policy.whole_value?(type) &&
|
|
316
|
+
!Lutaml::Model::Attribute.custom_from_probe?(type)
|
|
317
|
+
|
|
318
|
+
KvRulePlan.new(rule.name.to_s, type, attr.collection?)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Extraction fast path: plain hash fetch for a present, non-blank
|
|
322
|
+
# value. nil/blank values keep the interpretive path — their
|
|
323
|
+
# value-map/missing-value semantics must not be duplicated here.
|
|
324
|
+
def kv_fast_extract(doc, plan)
|
|
325
|
+
return nil unless doc.is_a?(::Hash)
|
|
326
|
+
|
|
327
|
+
v = doc[plan.wire]
|
|
328
|
+
return nil if v.nil?
|
|
329
|
+
return nil if v.is_a?(::String) && v.empty?
|
|
330
|
+
return nil if v.is_a?(::Array) && (v.empty? || !plan.collection)
|
|
331
|
+
# Structured values on scalar plans keep the interpretive path —
|
|
332
|
+
# its collection guidance error and cast semantics own them.
|
|
333
|
+
return nil if v.is_a?(::Hash)
|
|
334
|
+
|
|
335
|
+
v
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Cast fast path: identity when the value already has the target
|
|
339
|
+
# class (the overwhelming case for engine-typed scalars), the type's
|
|
340
|
+
# cast otherwise. Collections cast per element.
|
|
341
|
+
def kv_fast_cast(value, plan, _instance)
|
|
342
|
+
if plan.collection
|
|
343
|
+
unless value.is_a?(::Array)
|
|
344
|
+
return value.is_a?(plan.klass) ? value : plan.klass.cast(value)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
klass = plan.klass
|
|
348
|
+
value.map { |e| e.is_a?(klass) ? e : klass.cast(e) }
|
|
349
|
+
else
|
|
350
|
+
value.is_a?(plan.klass) ? value : plan.klass.cast(value)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
272
354
|
def cast_value(value, attr, format, rule, instance)
|
|
273
355
|
cast_options = rule.polymorphic ? { polymorphic: rule.polymorphic } : {}
|
|
274
356
|
cast_options[:lutaml_parent] = instance if instance
|
|
@@ -57,6 +57,14 @@ model_class: nil)
|
|
|
57
57
|
return nil if value.nil?
|
|
58
58
|
return nil if Lutaml::Model::Utils.uninitialized?(value)
|
|
59
59
|
|
|
60
|
+
# Compiled serialize plan (TODO.perf/07): builtin scalars whose
|
|
61
|
+
# type registers no custom to_<format> serializer serialize as the
|
|
62
|
+
# value itself — Type::Value#to_<format>'s default — without the
|
|
63
|
+
# wrap-allocate-cast ceremony per field. Computed once per rule.
|
|
64
|
+
if (fast = serialize_plan(rule)) && value.instance_of?(fast)
|
|
65
|
+
return value
|
|
66
|
+
end
|
|
67
|
+
|
|
60
68
|
# Check for Reference type first - even if value is a Serializable,
|
|
61
69
|
# it should be serialized as a key, not as a nested model
|
|
62
70
|
if reference_type?(rule)
|
|
@@ -151,6 +159,23 @@ model_class: nil)
|
|
|
151
159
|
end
|
|
152
160
|
end
|
|
153
161
|
|
|
162
|
+
# Per-rule serialize plan: the type class for builtin scalars with
|
|
163
|
+
# no custom to_<format> serializer (identity serialization), nil
|
|
164
|
+
# otherwise. Memoized per [rule].
|
|
165
|
+
def serialize_plan(rule)
|
|
166
|
+
@serialize_plans ||= {}
|
|
167
|
+
@serialize_plans[rule] ||= begin
|
|
168
|
+
type = rule.attribute_type
|
|
169
|
+
if type.is_a?(::Class) && type < ::Lutaml::Model::Type::Value
|
|
170
|
+
serializer = ::Lutaml::Model::Type::Value
|
|
171
|
+
.format_type_serializer_for(@format, type)
|
|
172
|
+
has_custom_to = serializer && serializer[:to]
|
|
173
|
+
has_custom_from = ::Lutaml::Model::Attribute.custom_from_probe?(type)
|
|
174
|
+
type unless has_custom_to || has_custom_from
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
154
179
|
private
|
|
155
180
|
|
|
156
181
|
# Validate that a nested model value matches the expected type.
|
|
@@ -16,6 +16,18 @@ module Lutaml
|
|
|
16
16
|
# This is a critical step toward symmetric OOP architecture
|
|
17
17
|
# across all serialization formats.
|
|
18
18
|
class Transformation < Lutaml::Model::Transformation
|
|
19
|
+
# Builtin Value types whose cast of an already-native value is the
|
|
20
|
+
# identity — used by the serialize_value fast path.
|
|
21
|
+
NATIVE_VALUE_CLASS = {
|
|
22
|
+
::Lutaml::Model::Type::String => ::String,
|
|
23
|
+
::Lutaml::Model::Type::Integer => ::Integer,
|
|
24
|
+
::Lutaml::Model::Type::Float => ::Float,
|
|
25
|
+
::Lutaml::Model::Type::Date => ::Date,
|
|
26
|
+
::Lutaml::Model::Type::Time => ::Time,
|
|
27
|
+
::Lutaml::Model::Type::DateTime => ::DateTime,
|
|
28
|
+
::Lutaml::Model::Type::Symbol => ::Symbol,
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
19
31
|
include Lutaml::Model::RenderPolicy
|
|
20
32
|
|
|
21
33
|
autoload :RuleCompiler, "#{__dir__}/transformation/rule_compiler"
|
|
@@ -1082,7 +1094,19 @@ child_mappings, options)
|
|
|
1082
1094
|
# Wrap value in type and call to_#{format} instance method (like legacy Attribute#serialize_value)
|
|
1083
1095
|
# This allows custom type subclasses to override to_json, to_yaml, etc.
|
|
1084
1096
|
if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
|
|
1085
|
-
|
|
1097
|
+
type = rule.attribute_type
|
|
1098
|
+
# Identity fast path (TODO.perf/07): a builtin type with no custom
|
|
1099
|
+
# to_<format>/from_<format> behavior emits an already-native value
|
|
1100
|
+
# unchanged — the wrap only re-casts and re-emits it.
|
|
1101
|
+
native = NATIVE_VALUE_CLASS[type]
|
|
1102
|
+
if native && value.instance_of?(native) &&
|
|
1103
|
+
!::Lutaml::Model::Type::Value
|
|
1104
|
+
.format_type_serializer_for(format, type)&.fetch(:to, nil) &&
|
|
1105
|
+
!::Lutaml::Model::Attribute.custom_from_probe?(type)
|
|
1106
|
+
return value
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
wrapped_value = type.new(value)
|
|
1086
1110
|
wrapped_value.public_send(:"to_#{format}")
|
|
1087
1111
|
else
|
|
1088
1112
|
value
|