lutaml-model 0.8.60 → 0.8.62

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b57929b74102c6555ab5cc9a03246dbb3ebd4b59952bf09b6add75b8c9f067f
4
- data.tar.gz: c0e7e209ab27a89d83b63deeefa042f721c02d8077aff94f686b53550ef8ea8f
3
+ metadata.gz: 93221695e02a0814183bb7935d242d416e7b5fc98fdae729ae7b7a548276fa1a
4
+ data.tar.gz: a8c6631206d1ed3dc635012999f11a2deb9f29409a441a4b85e8d47dd7f2fa9c
5
5
  SHA512:
6
- metadata.gz: 5198b96f62132b22ead982492ca82cbb5291956ca4d508242d779957f4f89a54eaeef44935fae3260c108abab0e90c03c51259e2dac9c18c5c8b98c0a82b4d65
7
- data.tar.gz: cf919daaea830dea7b44a59cf36b9c8dfb28c67d750137d5130c072c9ac297d1a78c37cd6d6d9a29be85c1819c485f1290e0d730b0c79fdb8da762ea9a1508d4
6
+ metadata.gz: db7ee4ad6d440102cbf7c2c8452b20844c1ddb251a44f86d44f0655e14f359eb1e1fa17debc2f0857485c34b09c9199c6921c31a55b1910aa103ac0fd5e11ff8
7
+ data.tar.gz: c8a43921457c96865466b5cb0111ccd4e6aba04585bb6e398bc7ad6f808a2009f2bbffa48e8dc1348ba10937776c96c0707249ab54d0bc018d900c4799eb98e3
@@ -18,14 +18,15 @@ jobs:
18
18
  benchmark:
19
19
  runs-on: ubuntu-latest
20
20
  timeout-minutes: 60
21
- # Serializes the corpus legs: concurrent legs on shared runners
22
- # produce contention measurements that misfire the absolute gates
23
- # (lutaml-model#834: niso 0.921s vs a 0.25s main-passing gate).
24
- concurrency:
25
- group: downstream-benchmark
26
- cancel-in-progress: false
27
21
  strategy:
28
22
  fail-fast: false
23
+ # Serializes the corpus legs: concurrent legs on shared runners
24
+ # produce contention measurements that misfire the absolute gates
25
+ # (lutaml-model#834: niso 0.921s vs a 0.25s main-passing gate).
26
+ # max-parallel, NOT a job-level concurrency group: with a
27
+ # matrix, every leg lands in the same group and GitHub cancels
28
+ # four of the five at start.
29
+ max-parallel: 1
29
30
  matrix:
30
31
  include:
31
32
  - downstream: xmi
@@ -23,10 +23,13 @@ jobs:
23
23
  os: [ubuntu-latest, macos-latest, windows-latest]
24
24
  ruby: ["3.3", "3.4", "4.0"]
25
25
  experimental: [false]
26
- include:
26
+ # ruby head runs on ubuntu/macos only: windows-head cannot build
27
+ # the released ruby-ll C extension (and the vendored forks exist
28
+ # for Opal), so that cell was permanently red with zero signal
29
+ # (#832 follow-up).
30
+ exclude:
27
31
  - os: windows-latest
28
32
  ruby: head
29
- experimental: true
30
33
  steps:
31
34
  - uses: actions/checkout@v6
32
35
  with:
@@ -44,13 +44,27 @@ jobs:
44
44
  runs-on: ubuntu-latest
45
45
  steps:
46
46
  - uses: actions/checkout@v6
47
+ # The release job commits the version bump to main while this job
48
+ # runs; a checkout of the dispatch-time HEAD reads the previous
49
+ # version and smoke-tests the wrong gem (0.8.60 was smoke-checked
50
+ # for the 0.8.61 release). Pull post-bump main before resolving.
51
+ with:
52
+ fetch-depth: 5
47
53
  - uses: ruby/setup-ruby@v1
48
54
  with:
49
55
  ruby-version: "3.4"
50
56
  - name: Resolve the released version
51
57
  id: version
52
- run: echo "version=$(git describe --tags --abbrev=0 | sed 's/^v//')" >> "$GITHUB_OUTPUT"
58
+ # From the version file, not git describe: the default checkout
59
+ # is shallow with no tags, so git describe failed and the empty
60
+ # version produced `gem install "lutaml-model:"`.
61
+ run: |
62
+ git pull --ff-only origin main
63
+ echo "version=$(ruby -e 'print File.read(%q{lib/lutaml/model/version.rb})[/VERSION\s*=\s*"([^"]+)"/, 1]')" >> "$GITHUB_OUTPUT"
53
64
  - name: Install and smoke the released gem
54
65
  run: |
55
66
  gem install "lutaml-model:${{ steps.version.outputs.version }}" --no-document
67
+ # The round-trip script exercises all five formats; the TOML
68
+ # legs require an adapter gem to be present.
69
+ gem install tomlib toml-rb --no-document
56
70
  ruby .github/scripts/test_installed_gem.rb
@@ -250,8 +250,9 @@ module Lutaml
250
250
  adapter, type)
251
251
  end
252
252
  require adapter_path
253
- rescue LoadError
254
- raise UnknownAdapterTypeError.new(adapter, type), cause: nil
253
+ rescue LoadError => e
254
+ # Chain the cause so a missing engine gem is named, not masked.
255
+ raise UnknownAdapterTypeError.new(adapter, type), cause: e
255
256
  end
256
257
 
257
258
  # Load the Moxml adapter for XML and similar formats.
@@ -23,9 +23,23 @@ module Lutaml
23
23
  },
24
24
  }
25
25
 
26
- # Add choice validation if present
26
+ # Choice validation follows the serializer's contract: unset
27
+ # or empty members are omitted from output, and instances that
28
+ # leave a whole group empty are accepted (#869). Members are
29
+ # therefore never `required`; a max:1 group contributes an
30
+ # at-most-one constraint over its members, and independent
31
+ # groups combine conjunctively under allOf. Groups whose range
32
+ # admits several members impose no serialization constraint.
27
33
  if type.choice_attributes.any?
28
- @schema[name]["oneOf"] = generate_choice_attributes(type)
34
+ groups = type.choice_attributes
35
+ .select { |choice| choice.max == 1 }
36
+ .map { |choice| { "oneOf" => exclusive_branches(choice) } }
37
+
38
+ if groups.one?
39
+ @schema[name].merge!(groups.first)
40
+ elsif groups.any?
41
+ @schema[name]["allOf"] = groups
42
+ end
29
43
  end
30
44
 
31
45
  @schema
@@ -33,15 +47,45 @@ module Lutaml
33
47
 
34
48
  private
35
49
 
36
- def generate_choice_attributes(type)
37
- type.choice_attributes.map do |choice|
50
+ # Leaves of the group (nested choices collapse into it), as
51
+ # [name, attribute] pairs.
52
+ def choice_leaves(choice)
53
+ choice.attributes.flat_map do |member|
54
+ if member.is_a?(Lutaml::Model::Choice)
55
+ choice_leaves(member)
56
+ else
57
+ [[member.name.to_s, member]]
58
+ end
59
+ end
60
+ end
61
+
62
+ # Exactly-one-present construction over the leaves: no leaf, or
63
+ # exactly one leaf present without the others. Accepts the
64
+ # all-empty document the serializer emits; rejects documents
65
+ # with two members of the group at once (max:1).
66
+ def exclusive_branches(choice)
67
+ leaves = choice_leaves(choice)
68
+ names = leaves.map(&:first)
69
+ none = {
70
+ "not" => {
71
+ "anyOf" => names.map { |n| { "required" => [n] } },
72
+ },
73
+ }
74
+ singles = leaves.map do |name, attribute|
75
+ others = names - [name]
38
76
  {
39
- "type" => "object",
40
- "properties" => PropertiesCollection.from_attributes(
41
- choice.attributes, extract_register_from(type)
42
- ).to_schema,
77
+ "allOf" => (
78
+ [{
79
+ "type" => "object",
80
+ "properties" => PropertiesCollection.from_attributes(
81
+ [attribute], extract_register_from(type)
82
+ ).to_schema,
83
+ "required" => [name],
84
+ }] + others.map { |o| { "not" => { "required" => [o] } } }
85
+ ),
43
86
  }
44
87
  end
88
+ [none] + singles
45
89
  end
46
90
 
47
91
  def properties_to_schema(type)
@@ -9,20 +9,18 @@ module Lutaml
9
9
  include SharedMethods
10
10
 
11
11
  def from_class(klass)
12
- new.tap do |collection|
13
- collection << Definition.new(klass)
14
-
15
- process_attributes(collection, klass)
16
- end
12
+ collection = new
13
+ register_definition(collection, klass)
14
+ collection
17
15
  end
18
16
 
19
- def process_attributes(collection, klass)
17
+ def process_attributes(collection, klass, seen = {})
20
18
  register = extract_register_from(klass)
21
19
  klass.attributes.each_value do |attribute|
22
20
  if attribute.union?
23
- process_union_members(collection, attribute)
21
+ process_union_members(collection, attribute, seen)
24
22
  elsif attribute.serializable?(register)
25
- process_attribute(collection, attribute, register)
23
+ process_attribute(collection, attribute, register, seen)
26
24
  end
27
25
  end
28
26
  end
@@ -30,31 +28,44 @@ module Lutaml
30
28
  # A union attribute's type is Type::Union, not a model, so collect
31
29
  # definitions for each of its Serializable members directly (a
32
30
  # union schema emits a $ref per model member).
33
- def process_union_members(collection, attribute)
31
+ def process_union_members(collection, attribute, seen = {})
34
32
  attribute.union_member_types.each do |member|
35
33
  next unless member.is_a?(::Class) &&
36
34
  member.include?(Lutaml::Model::Serialize)
37
35
 
38
- collection.merge(DefinitionsCollection.from_class(member))
36
+ register_definition(collection, member, seen)
39
37
  end
40
38
  end
41
39
 
42
- def process_attribute(collection, attribute, register)
40
+ def process_attribute(collection, attribute, register, seen = {})
43
41
  attr_type = Lutaml::Model::GlobalContext.resolve_type(
44
42
  attribute.type, register
45
43
  )
46
- collection.merge(DefinitionsCollection.from_class(attr_type))
44
+ register_definition(collection, attr_type, seen)
47
45
 
48
- process_polymorphic_types(collection, attribute)
46
+ process_polymorphic_types(collection, attribute, seen)
49
47
  end
50
48
 
51
- def process_polymorphic_types(collection, attribute)
49
+ def process_polymorphic_types(collection, attribute, seen = {})
52
50
  return unless attribute.options&.[](:polymorphic)
53
51
 
54
52
  attribute.options[:polymorphic].each do |child|
55
- collection.merge(DefinitionsCollection.from_class(child))
53
+ register_definition(collection, child, seen)
56
54
  end
57
55
  end
56
+
57
+ # Recursive models (Node#child → Node, or longer include
58
+ # cycles) are valid; the seen-set registers each class once
59
+ # and lets property references point at the shared $defs
60
+ # entry instead of recursing (#863).
61
+ def register_definition(collection, klass, seen = {})
62
+ return collection if seen[klass]
63
+
64
+ seen[klass] = true
65
+ collection << Definition.new(klass)
66
+ process_attributes(collection, klass, seen)
67
+ collection
68
+ end
58
69
  end
59
70
 
60
71
  attr_reader :definitions
@@ -52,11 +52,13 @@ module Lutaml
52
52
  end
53
53
 
54
54
  def self.to_json(klass, options = {})
55
- JsonSchema.generate(klass, options)
55
+ # The generator signatures are keyword-only; splat so callers may
56
+ # pass an options hash positionally (#866).
57
+ JsonSchema.generate(klass, **options)
56
58
  end
57
59
 
58
60
  def self.to_yaml(klass, options = {})
59
- YamlSchema.generate(klass, options)
61
+ YamlSchema.generate(klass, **options)
60
62
  end
61
63
 
62
64
  # XML-specific methods (to_xsd, to_relaxng, from_xml) are registered
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.60"
5
+ VERSION = "0.8.62"
6
6
  end
7
7
  end
@@ -19,9 +19,12 @@ module Lutaml
19
19
  File.join(File.dirname(__FILE__), "adapter", type)
20
20
  end
21
21
  require adapter_path
22
- rescue LoadError
22
+ rescue LoadError => e
23
+ # Chain the cause: a known adapter whose underlying engine gem is
24
+ # missing (e.g. no nokogiri in the Gemfile) must not present as a
25
+ # bare unknown-type error — the LoadError names the real gap.
23
26
  raise Lutaml::Model::UnknownAdapterTypeError.new("xml", type),
24
- cause: nil
27
+ cause: e
25
28
  end
26
29
 
27
30
  # Load the Moxml adapter for XML
@@ -200,7 +200,15 @@ module Lutaml
200
200
  case kind
201
201
  when :content_deferred
202
202
  runs = content_runs(value)
203
- next if runs.empty?
203
+ if runs.empty?
204
+ # The interpretive pipeline marks every applied rule's
205
+ # attribute set (model_transform apply). Skipping the
206
+ # mark leaves using_default? true, so the serializer's
207
+ # render gate suppresses a mapped reader that derives
208
+ # content from other attributes (#856).
209
+ instance.value_set_for(attr.name)
210
+ next
211
+ end
204
212
 
205
213
  # Non-collection content attrs hold the joined text
206
214
  # (the interpretive path assigns element text, the runs
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+
6
+ require "spec_helper"
7
+
8
+ RSpec.describe "JSON/YAML schema generation regressions (#863, #864, #865, #866)" do
9
+ before do
10
+ stub_const("SchemaRepro::Node", node_class)
11
+ stub_const("SchemaRepro::Outer", outer_class)
12
+ stub_const("SchemaRepro::Relation", relation_class)
13
+ stub_const("SchemaRepro::Simple", simple_class)
14
+ end
15
+
16
+ let(:node_class) do
17
+ klass = Class.new(Lutaml::Model::Serializable) do
18
+ attribute :name, :string
19
+ xml { element "Node" }
20
+ end
21
+ klass.attribute :child, klass
22
+ klass
23
+ end
24
+
25
+ let(:outer_class) do
26
+ Class.new(Lutaml::Model::Serializable) do
27
+ choice(min: 1, max: 1) do
28
+ choice(min: 1, max: 1) do
29
+ attribute :from, :string
30
+ attribute :to, :string
31
+ end
32
+ attribute :at, :string
33
+ end
34
+ xml { element "Outer" }
35
+ end
36
+ end
37
+
38
+ let(:relation_class) do
39
+ Class.new(Lutaml::Model::Serializable) do
40
+ attribute :type, :string
41
+ choice(min: 1, max: 1) do
42
+ attribute :locality, :string, collection: true, initialize_empty: true
43
+ attribute :locality_stack, :string, collection: true, initialize_empty: true
44
+ end
45
+ choice(min: 1, max: 1) do
46
+ attribute :source_locality, :string, collection: true, initialize_empty: true
47
+ attribute :source_locality_stack, :string, collection: true, initialize_empty: true
48
+ end
49
+ xml { element "Relation" }
50
+ end
51
+ end
52
+
53
+ let(:simple_class) do
54
+ Class.new(Lutaml::Model::Serializable) do
55
+ attribute :x, :string
56
+ xml { element "Simple" }
57
+ end
58
+ end
59
+
60
+ describe "#863: recursive models" do
61
+ it "terminates and emits one definition per class" do
62
+ schema = JSON.parse(Lutaml::Model::Schema.to_json(SchemaRepro::Node))
63
+ expect(schema["$defs"].keys).to contain_exactly("SchemaRepro_Node")
64
+ end
65
+
66
+ it "generates through the YAML path" do
67
+ yaml = Lutaml::Model::Schema.to_yaml(SchemaRepro::Node)
68
+ expect(YAML.safe_load(yaml)).to include("$defs")
69
+ end
70
+ end
71
+
72
+ describe "#864: nested choice blocks" do
73
+ it "collapses the nested group into the outer at-most-one set" do
74
+ schema = JSON.parse(Lutaml::Model::Schema.to_json(SchemaRepro::Outer))
75
+ one_of = schema["$defs"]["SchemaRepro_Outer"]["oneOf"]
76
+ requireds = one_of.map { |b| b["allOf"].to_a.filter_map { |s| s["required"]&.first } }
77
+ expect(requireds).to contain_exactly([], %w[from], %w[to], %w[at])
78
+ end
79
+ end
80
+
81
+ describe "#865: independent choices combine conjunctively" do
82
+ it "renders an allOf of oneOf at-most-one groups" do
83
+ schema = JSON.parse(Lutaml::Model::Schema.to_json(SchemaRepro::Relation))
84
+ all_of = schema["$defs"]["SchemaRepro_Relation"]["allOf"]
85
+ expect(all_of.length).to eq(2)
86
+ all_of.each do |group|
87
+ expect(group).to have_key("oneOf")
88
+ expect(group["oneOf"].length).to eq(3)
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "#869: the serializer's own output validates" do
94
+ it "emits no required keys for initialize_empty choice members" do
95
+ schema = JSON.parse(Lutaml::Model::Schema.to_json(SchemaRepro::Relation))
96
+ definition = schema["$defs"]["SchemaRepro_Relation"]
97
+ groups = definition["allOf"] || [definition]
98
+ groups.each do |group|
99
+ group["oneOf"].each do |branch|
100
+ Array(branch["allOf"]).each do |sub|
101
+ expect(sub).not_to have_key("required") if sub.key?("not")
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ it "accepts the document the default instance serializes to" do
108
+ inst = SchemaRepro::Relation.new(type: "includes")
109
+ doc = inst.to_hash
110
+ expect(doc).to eq("type" => "includes")
111
+ schema = JSON.parse(Lutaml::Model::Schema.to_json(SchemaRepro::Relation))
112
+ definition = schema["$defs"]["SchemaRepro_Relation"]
113
+ groups = definition["allOf"] || [definition]
114
+ groups.each do |group|
115
+ satisfied = group["oneOf"].count do |branch|
116
+ Array(branch["allOf"]).all? do |sub|
117
+ if sub.key?("not")
118
+ Array(sub["not"]["anyOf"]).none? do |req|
119
+ doc.key?(req["required"].first)
120
+ end
121
+ else
122
+ doc.key?(sub["required"].first)
123
+ end
124
+ end
125
+ end
126
+ expect(satisfied).to eq(1)
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#866: Schema.to_json / to_yaml delegation" do
132
+ it "accepts a positional options hash" do
133
+ expect { Lutaml::Model::Schema.to_json(SchemaRepro::Simple) }
134
+ .not_to raise_error
135
+ expect { Lutaml::Model::Schema.to_yaml(SchemaRepro::Simple) }
136
+ .not_to raise_error
137
+ end
138
+ end
139
+ end
@@ -161,19 +161,7 @@ RSpec.describe Lutaml::Model::Schema::JsonSchema do
161
161
  "type" => ["string", "null"],
162
162
  },
163
163
  },
164
- "oneOf" => [
165
- {
166
- "type" => "object",
167
- "properties" => {
168
- "email" => {
169
- "type" => ["string", "null"],
170
- },
171
- "phone" => {
172
- "type" => ["string", "null"],
173
- },
174
- },
175
- },
176
- ],
164
+
177
165
  },
178
166
  },
179
167
  }
@@ -136,17 +136,6 @@ RSpec.describe Lutaml::Model::Schema::YamlSchema do
136
136
  type:
137
137
  - string
138
138
  - 'null'
139
- oneOf:
140
- - type: object
141
- properties:
142
- email:
143
- type:
144
- - string
145
- - 'null'
146
- phone:
147
- type:
148
- - string
149
- - 'null'
150
139
  YAML
151
140
  end
152
141
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Lutaml::Xml::AdapterLoader do
6
+ it "chains the LoadError cause instead of masking it" do
7
+ expect do
8
+ described_class.load_adapter_file("xml", "bogus_adapter")
9
+ end.to raise_error(Lutaml::Model::UnknownAdapterTypeError) do |error|
10
+ expect(error.cause).to be_a(LoadError)
11
+ expect(error.cause.message).to include("bogus_adapter")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # lutaml-model#858: leptris hydrates :string values as ASCII-8BIT
6
+ # (FFI's :string return tag) while nokogiri returns UTF-8. Values are
7
+ # character data; both adapters must agree on the encoding.
8
+ RSpec.describe "leptris string encoding parity with nokogiri",
9
+ if: defined?(Leptris::XML::Document) do
10
+ let(:xml) do
11
+ <<~XML
12
+ <Relationship Id="rId5" Target="http://example.com/x"/>
13
+ XML
14
+ end
15
+
16
+ let(:klass) do
17
+ Class.new(Lutaml::Model::Serializable) do
18
+ attribute :id, :string
19
+ attribute :target, :string
20
+
21
+ xml do
22
+ element "Relationship"
23
+ map_attribute "Id", to: :id
24
+ map_attribute "Target", to: :target
25
+ end
26
+ end
27
+ end
28
+
29
+ before do
30
+ stub_const("LeptrisEncoding::Rel", klass)
31
+ end
32
+
33
+ it "hydrates attribute values as UTF-8" do
34
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
35
+ parsed = klass.from_xml(xml)
36
+ expect(parsed.id.encoding).to eq(Encoding::UTF_8)
37
+ expect(parsed.target.encoding).to eq(Encoding::UTF_8)
38
+ end
39
+
40
+ Lutaml::Model::Config.with_adapter(xml: :leptris) do
41
+ parsed = klass.from_xml(xml)
42
+ expect(parsed.id.encoding).to eq(Encoding::UTF_8)
43
+ expect(parsed.target.encoding).to eq(Encoding::UTF_8)
44
+ end
45
+ end
46
+
47
+ it "hydrates element text as UTF-8" do
48
+ doc = "<Note>café – naïve</Note>"
49
+ text_class = Class.new(Lutaml::Model::Serializable) do
50
+ attribute :text, :string
51
+
52
+ xml do
53
+ element "Note"
54
+ map_content to: :text
55
+ end
56
+ end
57
+ stub_const("LeptrisEncoding::Note", text_class)
58
+
59
+ Lutaml::Model::Config.with_adapter(xml: :leptris) do
60
+ parsed = text_class.from_xml(doc)
61
+ expect(parsed.text.encoding).to eq(Encoding::UTF_8)
62
+ expect(parsed.text).to eq("café – naïve")
63
+ end
64
+ end
65
+ end
@@ -740,4 +740,53 @@ RSpec.describe "XML plan fast path" do
740
740
  %(<b xmlns="http://example.com/w" val="false"/>),
741
741
  ).value).to be(false)
742
742
  end
743
+
744
+ # lutaml-model#856: a deferred content row with no captured runs
745
+ # must still mark its attribute set — the interpretive pipeline
746
+ # marks every applied rule (model_transform apply). Without the
747
+ # mark, the serializer's default-suppression drops a mapped reader
748
+ # that derives content from other attributes.
749
+ it "renders derived content readers when no text was captured" do
750
+ klass = Class.new(Lutaml::Model::Serializable) do
751
+ attribute :content, :string
752
+ attribute :revision_date, :string
753
+ attribute :draft, :string
754
+
755
+ xml do
756
+ element "version"
757
+ map_content to: :content
758
+ map_element "revision-date", to: :revision_date
759
+ map_element "draft", to: :draft
760
+ end
761
+
762
+ define_method(:content) do
763
+ return @content if @content && !@content.empty?
764
+
765
+ parts = [@draft, @revision_date].compact
766
+ case parts.size
767
+ when 2 then "#{@draft} (#{@revision_date})"
768
+ when 1 then parts.first
769
+ end
770
+ end
771
+ end
772
+ stub_const("PlanFastPath::DerivedContent856", klass)
773
+
774
+ parsed = nil
775
+ Lutaml::Model::Config.with_adapter(xml: :leptris) do
776
+ parsed = klass.from_xml(
777
+ "<version><revision-date>1994-01-01</revision-date><draft>PD</draft></version>",
778
+ )
779
+ expect(parsed.using_default?(:content)).to be(false)
780
+ expect(parsed.to_xml).to eq(
781
+ "<version><revision-date>1994-01-01</revision-date>" \
782
+ "<draft>PD</draft>PD (1994-01-01)</version>",
783
+ )
784
+ end
785
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
786
+ expect(parsed.to_xml).to eq(
787
+ "<version><revision-date>1994-01-01</revision-date>" \
788
+ "<draft>PD</draft>PD (1994-01-01)</version>",
789
+ )
790
+ end
791
+ end
743
792
  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.60
4
+ version: 0.8.62
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-09-24 00:00:00.000000000 Z
11
+ date: 2026-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -1789,6 +1789,7 @@ files:
1789
1789
  - spec/lutaml/model/root_mappings_spec.rb
1790
1790
  - spec/lutaml/model/rule_value_extractor_spec.rb
1791
1791
  - spec/lutaml/model/runtime_compatibility_spec.rb
1792
+ - spec/lutaml/model/schema/generation_regressions_spec.rb
1792
1793
  - spec/lutaml/model/schema/generator/definitions_collection_spec.rb
1793
1794
  - spec/lutaml/model/schema/json_schema_spec.rb
1794
1795
  - spec/lutaml/model/schema/json_schema_to_models_spec.rb
@@ -1903,6 +1904,7 @@ files:
1903
1904
  - spec/lutaml/xml/adapter/ox_adapter_spec.rb
1904
1905
  - spec/lutaml/xml/adapter/rexml_adapter_spec.rb
1905
1906
  - spec/lutaml/xml/adapter/xml_namespace_spec.rb
1907
+ - spec/lutaml/xml/adapter_loader_spec.rb
1906
1908
  - spec/lutaml/xml/attribute_ambiguity_surface_spec.rb
1907
1909
  - spec/lutaml/xml/attribute_element_namespace_disjoint_spec.rb
1908
1910
  - spec/lutaml/xml/attribute_namespace_disjointness_spec.rb
@@ -1932,6 +1934,7 @@ files:
1932
1934
  - spec/lutaml/xml/general_namespace_spec.rb
1933
1935
  - spec/lutaml/xml/html_entities_spec.rb
1934
1936
  - spec/lutaml/xml/indent_spec.rb
1937
+ - spec/lutaml/xml/leptris_string_encoding_spec.rb
1935
1938
  - spec/lutaml/xml/line_ending_spec.rb
1936
1939
  - spec/lutaml/xml/mapping_finalization_guard_spec.rb
1937
1940
  - spec/lutaml/xml/mapping_rule_spec.rb