lutaml-model 0.8.98 → 0.8.100
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/gem-integration.adoc +85 -0
- data/docs/_guides/index.adoc +6 -1
- data/docs/_guides/schema-generation.adoc +25 -0
- data/docs/_guides/type-registers-contexts.adoc +126 -0
- data/lib/lutaml/model/attribute.rb +7 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +16 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/spec/lutaml/model/desc_spec.rb +50 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2bd54a574eb02eec487d980543835abb2bec61c12104d6eea6b898a516fb301f
|
|
4
|
+
data.tar.gz: ffc5d3ed5b0c6df7140839064baec52624a034b2d1c5cfdc764dc4ee78c199ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 191c5ee654cc6863a6f8d865448f0fbb29c31f7d602e3ab2a356148fdca7117b254aa497bb1ebdd80da1c80f495cf46c5f1029897cf87e7423a7409747e4e97c
|
|
7
|
+
data.tar.gz: 4b55a05f437df7e0c85c7545d2042990572c70a4d64b42d9ae3ee44950ca6875cd640c2089cd9620951264180556e2e3776e7b32028b1b1d9962cbdaad739ecb
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Integrating lutaml-model in a Gem
|
|
3
|
+
nav_order: 21
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
= Integrating lutaml-model in a gem
|
|
7
|
+
|
|
8
|
+
For gem authors, the integration contract is simple: **depend on
|
|
9
|
+
`lutaml-model` and nothing else format-specific.** Every XML, YAML,
|
|
10
|
+
JSON, and TOML engine is resolved at runtime, and the standard
|
|
11
|
+
adapters (Nokogiri through moxml, Psych, the `json` gem, toml-rb
|
|
12
|
+
compatible engines) cover a working default everywhere.
|
|
13
|
+
|
|
14
|
+
== Declaring the dependency
|
|
15
|
+
|
|
16
|
+
[source,ruby]
|
|
17
|
+
----
|
|
18
|
+
spec.add_dependency "lutaml-model", "~> 0.8"
|
|
19
|
+
----
|
|
20
|
+
|
|
21
|
+
Do not add Nokogiri, Ox, Oga, leptris, yeptris, or teptris to your
|
|
22
|
+
gemspec. All engines are optional at runtime:
|
|
23
|
+
|
|
24
|
+
* when an engine gem is in the end user's bundle, the matching
|
|
25
|
+
adapters prefer it (see the
|
|
26
|
+
link:../native-engines[native engines guide]);
|
|
27
|
+
* when it is not, adapters fall back to the standard engine for
|
|
28
|
+
that format — parsing and generation semantics are identical,
|
|
29
|
+
error classes are re-raised as their standard-library equivalents.
|
|
30
|
+
|
|
31
|
+
There is no code path to write, no require order to respect, and no
|
|
32
|
+
capability probe to perform in your gem.
|
|
33
|
+
|
|
34
|
+
== Testing across adapters
|
|
35
|
+
|
|
36
|
+
Your test suite should exercise the adapters your users may run.
|
|
37
|
+
Lutaml::Model's own suite parameterizes the XML adapter through an
|
|
38
|
+
environment variable; the same shape works downstream:
|
|
39
|
+
|
|
40
|
+
[source,ruby]
|
|
41
|
+
----
|
|
42
|
+
ADAPTER = ENV.fetch("XML_ADAPTER", "nokogiri").to_sym
|
|
43
|
+
|
|
44
|
+
RSpec.configure do |config|
|
|
45
|
+
config.before(:suite) do
|
|
46
|
+
Lutaml::Model::Config.xml_adapter_type = ADAPTER
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
----
|
|
50
|
+
|
|
51
|
+
Run the matrix in CI — one leg per adapter the standard runners can
|
|
52
|
+
install:
|
|
53
|
+
|
|
54
|
+
[source,sh]
|
|
55
|
+
----
|
|
56
|
+
XML_ADAPTER=nokogiri bundle exec rspec
|
|
57
|
+
XML_ADAPTER=ox bundle exec rspec
|
|
58
|
+
XML_ADAPTER=leptris bundle exec rspec # requires the leptris gem in that leg's bundle
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
Engine gems belong in the CI *Gemfile* (a `group :test` entry or a
|
|
62
|
+
separate CI gemfile), never in the gemspec. Start with the primary
|
|
63
|
+
adapter (Nokogiri) and enable further legs incrementally as they
|
|
64
|
+
pass — the same "adapter-first" strategy this library's own suite
|
|
65
|
+
uses.
|
|
66
|
+
|
|
67
|
+
== Semantics your users can rely on
|
|
68
|
+
|
|
69
|
+
* Adapter choice never changes results: the plan fast path and the
|
|
70
|
+
interpretive pipeline produce identical output; on engines below
|
|
71
|
+
a capability floor the affected models parse interpretively
|
|
72
|
+
rather than degrade (see the version-floor note in the
|
|
73
|
+
link:../native-engines[native engines guide]).
|
|
74
|
+
* Parse failures raise the standard error classes wrapped as
|
|
75
|
+
`Lutaml::Model::InvalidFormatError` on every engine — code that
|
|
76
|
+
rescues those classes works regardless of the bundle.
|
|
77
|
+
|
|
78
|
+
== Rules of thumb
|
|
79
|
+
|
|
80
|
+
* Gemspec: `lutaml-model` only. CI gemfiles: the engines you test.
|
|
81
|
+
* Expose format options you want to forward (pretty printing,
|
|
82
|
+
encoding) — never the adapter itself.
|
|
83
|
+
* If you ship models for multiple schema versions, see
|
|
84
|
+
link:../type-registers-contexts[type registers and contexts] for
|
|
85
|
+
per-version trees and substitution.
|
data/docs/_guides/index.adoc
CHANGED
|
@@ -28,8 +28,13 @@ Task-oriented guides for accomplishing specific goals with Lutaml::Model.
|
|
|
28
28
|
* link:../advanced-mapping[Advanced Attribute Mapping] - Same-name dispatch on attribute values (`when_attribute`), grouped declarations, custom methods, delegation
|
|
29
29
|
* link:../grammar-backed-formats[Grammar-Backed String Formats] - Register a grammar engine as a serialization format (identifier strings, DSLs)
|
|
30
30
|
|
|
31
|
-
==
|
|
31
|
+
== Model architecture and integration
|
|
32
|
+
|
|
33
|
+
* link:../type-registers-contexts[Type Registers and Contexts] - Multi-version class trees, derived contexts, and model substitution
|
|
34
|
+
* link:../gem-integration[Integrating lutaml-model in a Gem] - Dependency contract, adapter deferral, cross-adapter testing
|
|
32
35
|
|
|
36
|
+
== Performance
|
|
37
|
+
/
|
|
33
38
|
* link:../native-engines[Native engines (leptris / yeptris / teptris)] - Drop-in XML/YAML/JSON/TOML engine upgrades via bundle opt-in
|
|
34
39
|
|
|
35
40
|
== Linked Data serialization
|
|
@@ -901,3 +901,28 @@ The generated schema will include validation constraints:
|
|
|
901
901
|
</define>
|
|
902
902
|
</grammar>
|
|
903
903
|
----
|
|
904
|
+
|
|
905
|
+
== Documentation metadata (desc)
|
|
906
|
+
|
|
907
|
+
Models and attributes carry documentation strings through `desc` —
|
|
908
|
+
set on the model class or on any `attribute` declaration, read back
|
|
909
|
+
by documentation and schema tooling. Serialization is never affected
|
|
910
|
+
by it.
|
|
911
|
+
|
|
912
|
+
[source,ruby]
|
|
913
|
+
----
|
|
914
|
+
class Title < Lutaml::Model::Serializable
|
|
915
|
+
desc "This is a title."
|
|
916
|
+
|
|
917
|
+
attribute :type, :string, desc: "This attribute is used for..."
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
Title.desc # => "This is a title."
|
|
921
|
+
Title.attributes[:type].desc # => "This attribute is used for..."
|
|
922
|
+
----
|
|
923
|
+
|
|
924
|
+
Subclasses inherit the nearest ancestor's class description; an
|
|
925
|
+
explicit `desc` overrides it. A consumer override-free `desc` value
|
|
926
|
+
does not change any generated schema today — wiring `desc` into the
|
|
927
|
+
generated `description` fields is deliberate product scope, not a
|
|
928
|
+
free rider on this option.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Type Registers and Contexts
|
|
3
|
+
nav_order: 20
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
= Type registers and contexts
|
|
7
|
+
|
|
8
|
+
Lutaml::Model resolves every attribute type — Symbol or class —
|
|
9
|
+
through a per-application *register*: a named table of types scoped
|
|
10
|
+
so that different class trees can share mapping definitions without
|
|
11
|
+
colliding. Registers are the mechanism behind multi-version model
|
|
12
|
+
suites and model substitution.
|
|
13
|
+
|
|
14
|
+
== Registers
|
|
15
|
+
|
|
16
|
+
A register maps type ids to classes. Classes declare the register
|
|
17
|
+
they belong to:
|
|
18
|
+
|
|
19
|
+
[source,ruby]
|
|
20
|
+
----
|
|
21
|
+
module MyModels
|
|
22
|
+
class Mi < Lutaml::Model::Serializable
|
|
23
|
+
def self.lutaml_default_register
|
|
24
|
+
:my_models
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
----
|
|
29
|
+
|
|
30
|
+
Symbol attribute types resolve in the declaring class's register,
|
|
31
|
+
so the same symbol can mean different classes per tree — the
|
|
32
|
+
multi-version pattern below builds on this.
|
|
33
|
+
|
|
34
|
+
== Contexts: derived registers with substitutions
|
|
35
|
+
|
|
36
|
+
A *context* is a derived type-resolution scope: it carries a
|
|
37
|
+
registry, fallback registers, and — the part that powers model
|
|
38
|
+
substitution — a list of type substitutions. Contexts are created
|
|
39
|
+
through the global registry:
|
|
40
|
+
|
|
41
|
+
[source,ruby]
|
|
42
|
+
----
|
|
43
|
+
Lutaml::Model::GlobalContext.create_context(
|
|
44
|
+
id: :custom_models,
|
|
45
|
+
fallback_to: [:my_models],
|
|
46
|
+
substitutions: [
|
|
47
|
+
{ from_type: MyModels::Mover, to_type: Overset },
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
instance = MyModels::Math.from_xml(xml, context: :custom_models)
|
|
52
|
+
instance.mover_value.first # => #<Overset>
|
|
53
|
+
----
|
|
54
|
+
|
|
55
|
+
Every attribute whose type resolves to `MyModels::Mover` — at any
|
|
56
|
+
depth of the tree — hydrates `Overset` in that context. Substitution
|
|
57
|
+
is type-based, so "swap this class everywhere it is referenced" and
|
|
58
|
+
"swap it only inside this one path" are the same operation scoped by
|
|
59
|
+
context: create two contexts with different substitution lists.
|
|
60
|
+
|
|
61
|
+
Contexts are cheap and disposable:
|
|
62
|
+
|
|
63
|
+
[source,ruby]
|
|
64
|
+
----
|
|
65
|
+
Lutaml::Model::GlobalContext.unregister_context(:custom_models)
|
|
66
|
+
----
|
|
67
|
+
|
|
68
|
+
=== The custom-models convenience
|
|
69
|
+
|
|
70
|
+
A whole-table convenience reads exactly like a class-swap hash and
|
|
71
|
+
derives the context for you:
|
|
72
|
+
|
|
73
|
+
[source,ruby]
|
|
74
|
+
----
|
|
75
|
+
models = { MyModels::Mover => Overset, MyModels::Mi => BoldMi }
|
|
76
|
+
Lutaml::Model::GlobalContext.create_context(
|
|
77
|
+
id: :custom, fallback_to: [:my_models],
|
|
78
|
+
substitutions: models.map { |from, to| { from_type: from, to_type: to } },
|
|
79
|
+
)
|
|
80
|
+
MyModels::Math.from_xml(xml, context: :custom)
|
|
81
|
+
----
|
|
82
|
+
|
|
83
|
+
Substitute classes should inherit from the class they replace (or
|
|
84
|
+
provide its full attribute surface) so mappings and serialization
|
|
85
|
+
keep working.
|
|
86
|
+
|
|
87
|
+
=== Substituting a symbol entry instead of a class
|
|
88
|
+
|
|
89
|
+
Registers accept direct class registrations under a type id — useful
|
|
90
|
+
when a tree declares `attribute :row, :row` and you want the symbol
|
|
91
|
+
to resolve to your own class within a derived register:
|
|
92
|
+
|
|
93
|
+
[source,ruby]
|
|
94
|
+
----
|
|
95
|
+
derived = Lutaml::Model::Register.new(:derived, fallback: [:my_models])
|
|
96
|
+
derived.register_model(NewRow, id: :row)
|
|
97
|
+
MyModels::Doc.from_xml(xml, register: derived)
|
|
98
|
+
----
|
|
99
|
+
|
|
100
|
+
(`register:` is the pre-context spelling; prefer `context:` with
|
|
101
|
+
substitutions in new code.)
|
|
102
|
+
|
|
103
|
+
== Multi-version class trees
|
|
104
|
+
|
|
105
|
+
The flagship pattern combines registers with per-version classes
|
|
106
|
+
and shared mapping modules:
|
|
107
|
+
|
|
108
|
+
* a `Base::Mrow` module holds the mapping DSL once;
|
|
109
|
+
* each version declares `class Mrow` mixing the module in and
|
|
110
|
+
pointing `lutaml_default_register` at its version's register;
|
|
111
|
+
* attributes use symbol types, so `attribute :mrow, :mrow` means
|
|
112
|
+
the right class per tree;
|
|
113
|
+
* a substitution context swaps individual classes for one call
|
|
114
|
+
without re-opening or redefining anything.
|
|
115
|
+
|
|
116
|
+
Nothing re-opens generated classes, and each version's tree stays
|
|
117
|
+
independent — no hidden delegation.
|
|
118
|
+
|
|
119
|
+
== Rules of thumb
|
|
120
|
+
|
|
121
|
+
* Prefer contexts over global configuration: substitutions scoped
|
|
122
|
+
to a context cannot leak into other resolutions.
|
|
123
|
+
* Substitute classes should inherit the original; substitution then
|
|
124
|
+
preserves every mapping declared on the base.
|
|
125
|
+
* Clean up exploratory contexts with `unregister_context` in test
|
|
126
|
+
teardowns — contexts are global state.
|
|
@@ -46,6 +46,7 @@ module Lutaml
|
|
|
46
46
|
signed
|
|
47
47
|
min_length
|
|
48
48
|
max_length
|
|
49
|
+
desc
|
|
49
50
|
].freeze
|
|
50
51
|
|
|
51
52
|
# Per-type-class memo of the custom from_xml/from_json probe used by #cast.
|
|
@@ -641,6 +642,12 @@ instance_object = nil)
|
|
|
641
642
|
options[:pattern]
|
|
642
643
|
end
|
|
643
644
|
|
|
645
|
+
# Documentation metadata (#356): the `:desc` attribute option,
|
|
646
|
+
# consumed by documentation and schema tooling.
|
|
647
|
+
def desc
|
|
648
|
+
options[:desc]
|
|
649
|
+
end
|
|
650
|
+
|
|
644
651
|
# Performance: Frozen empty array to reduce allocations
|
|
645
652
|
EMPTY_VALUES_ARRAY = [].freeze
|
|
646
653
|
|
|
@@ -505,6 +505,22 @@ module Lutaml
|
|
|
505
505
|
end
|
|
506
506
|
end
|
|
507
507
|
|
|
508
|
+
# Class-level documentation metadata (#356): `desc "text"` on
|
|
509
|
+
# the model class; a zero-argument call reads it back.
|
|
510
|
+
# Subclasses inherit the nearest ancestor's description — an
|
|
511
|
+
# explicit `desc` overrides. Consumed by documentation and
|
|
512
|
+
# schema tooling; the serialization paths never read it.
|
|
513
|
+
def desc(text = nil)
|
|
514
|
+
return @lutaml_desc = text if text
|
|
515
|
+
|
|
516
|
+
return @lutaml_desc if defined?(@lutaml_desc)
|
|
517
|
+
|
|
518
|
+
if superclass.is_a?(::Class) &&
|
|
519
|
+
superclass < ::Lutaml::Model::Serializable
|
|
520
|
+
superclass.desc
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
508
524
|
# Define an attribute for the model
|
|
509
525
|
#
|
|
510
526
|
# @param name [Symbol] The attribute name
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Documentation metadata (desc)" do
|
|
6
|
+
let(:widget_class) do
|
|
7
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
8
|
+
desc "A widget."
|
|
9
|
+
|
|
10
|
+
attribute :name, :string, desc: "The widget name."
|
|
11
|
+
attribute :size, :integer
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "stores and reads the class-level description" do
|
|
16
|
+
expect(widget_class.desc).to eq("A widget.")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "returns nil when no description is set" do
|
|
20
|
+
plain = Class.new(Lutaml::Model::Serializable)
|
|
21
|
+
expect(plain.desc).to be_nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "inherits the description on subclasses" do
|
|
25
|
+
child = Class.new(widget_class)
|
|
26
|
+
expect(child.desc).to eq("A widget.")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "lets a subclass override the description" do
|
|
30
|
+
child = Class.new(widget_class) do
|
|
31
|
+
desc "A special widget."
|
|
32
|
+
end
|
|
33
|
+
expect(child.desc).to eq("A special widget.")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "does not leak an override back to the superclass" do
|
|
37
|
+
Class.new(widget_class) { desc "Child only." }
|
|
38
|
+
expect(widget_class.desc).to eq("A widget.")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "stores the attribute-level description" do
|
|
42
|
+
expect(widget_class.attributes[:name].desc).to eq("The widget name.")
|
|
43
|
+
expect(widget_class.attributes[:size].desc).to be_nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "keeps serialization untouched" do
|
|
47
|
+
widget = widget_class.new(name: "w", size: 3)
|
|
48
|
+
expect(widget.to_hash).to eq("name" => "w", "size" => 3)
|
|
49
|
+
end
|
|
50
|
+
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.100
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -207,6 +207,7 @@ files:
|
|
|
207
207
|
- docs/_guides/creating-xsd.adoc
|
|
208
208
|
- docs/_guides/custom-methods.adoc
|
|
209
209
|
- docs/_guides/document-validation.adoc
|
|
210
|
+
- docs/_guides/gem-integration.adoc
|
|
210
211
|
- docs/_guides/grammar-backed-formats.adoc
|
|
211
212
|
- docs/_guides/index.adoc
|
|
212
213
|
- docs/_guides/jsonld-serialization.adoc
|
|
@@ -220,6 +221,7 @@ files:
|
|
|
220
221
|
- docs/_guides/schema-generation.adoc
|
|
221
222
|
- docs/_guides/schema-import.adoc
|
|
222
223
|
- docs/_guides/turtle-serialization.adoc
|
|
224
|
+
- docs/_guides/type-registers-contexts.adoc
|
|
223
225
|
- docs/_guides/value-transformations.adoc
|
|
224
226
|
- docs/_guides/xml-mapping.adoc
|
|
225
227
|
- docs/_guides/xml-mappings-guide.adoc
|
|
@@ -1740,6 +1742,7 @@ files:
|
|
|
1740
1742
|
- spec/lutaml/model/deferred_type_no_materialize_spec.rb
|
|
1741
1743
|
- spec/lutaml/model/delegation_spec.rb
|
|
1742
1744
|
- spec/lutaml/model/derived_attribute_serialization_spec.rb
|
|
1745
|
+
- spec/lutaml/model/desc_spec.rb
|
|
1743
1746
|
- spec/lutaml/model/dynamic_attribute_spec.rb
|
|
1744
1747
|
- spec/lutaml/model/encoding_spec.rb
|
|
1745
1748
|
- spec/lutaml/model/enum_spec.rb
|