lutaml-model 0.8.61 → 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 +4 -4
- data/.github/workflows/release.yml +12 -1
- data/lib/lutaml/model/schema/generator/definition.rb +50 -30
- data/lib/lutaml/model/version.rb +1 -1
- data/spec/lutaml/model/schema/generation_regressions_spec.rb +43 -8
- data/spec/lutaml/model/schema/json_schema_spec.rb +1 -20
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +0 -17
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93221695e02a0814183bb7935d242d416e7b5fc98fdae729ae7b7a548276fa1a
|
|
4
|
+
data.tar.gz: a8c6631206d1ed3dc635012999f11a2deb9f29409a441a4b85e8d47dd7f2fa9c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db7ee4ad6d440102cbf7c2c8452b20844c1ddb251a44f86d44f0655e14f359eb1e1fa17debc2f0857485c34b09c9199c6921c31a55b1910aa103ac0fd5e11ff8
|
|
7
|
+
data.tar.gz: c8a43921457c96865466b5cb0111ccd4e6aba04585bb6e398bc7ad6f808a2009f2bbffa48e8dc1348ba10937776c96c0707249ab54d0bc018d900c4799eb98e3
|
|
@@ -44,6 +44,12 @@ 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"
|
|
@@ -52,8 +58,13 @@ jobs:
|
|
|
52
58
|
# From the version file, not git describe: the default checkout
|
|
53
59
|
# is shallow with no tags, so git describe failed and the empty
|
|
54
60
|
# version produced `gem install "lutaml-model:"`.
|
|
55
|
-
run:
|
|
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"
|
|
56
64
|
- name: Install and smoke the released gem
|
|
57
65
|
run: |
|
|
58
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
|
|
59
70
|
ruby .github/scripts/test_installed_gem.rb
|
|
@@ -23,19 +23,22 @@ module Lutaml
|
|
|
23
23
|
},
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
# Choice validation
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
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.
|
|
32
33
|
if type.choice_attributes.any?
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
|
|
@@ -44,30 +47,47 @@ module Lutaml
|
|
|
44
47
|
|
|
45
48
|
private
|
|
46
49
|
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
key = choice.min == 1 && choice.max == 1 ? "oneOf" : "anyOf"
|
|
52
|
-
{ key => choice_branches(choice) }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def choice_branches(choice)
|
|
56
|
-
choice.attributes.map do |member|
|
|
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|
|
|
57
54
|
if member.is_a?(Lutaml::Model::Choice)
|
|
58
|
-
|
|
55
|
+
choice_leaves(member)
|
|
59
56
|
else
|
|
60
|
-
|
|
61
|
-
"type" => "object",
|
|
62
|
-
"properties" => PropertiesCollection.from_attributes(
|
|
63
|
-
[member], extract_register_from(type)
|
|
64
|
-
).to_schema,
|
|
65
|
-
"required" => [member.name.to_s],
|
|
66
|
-
}
|
|
57
|
+
[[member.name.to_s, member]]
|
|
67
58
|
end
|
|
68
59
|
end
|
|
69
60
|
end
|
|
70
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]
|
|
76
|
+
{
|
|
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
|
+
),
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
[none] + singles
|
|
89
|
+
end
|
|
90
|
+
|
|
71
91
|
def properties_to_schema(type)
|
|
72
92
|
PropertiesCollection.from_class(type).to_schema
|
|
73
93
|
end
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -70,25 +70,60 @@ RSpec.describe "JSON/YAML schema generation regressions (#863, #864, #865, #866)
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
describe "#864: nested choice blocks" do
|
|
73
|
-
it "
|
|
73
|
+
it "collapses the nested group into the outer at-most-one set" do
|
|
74
74
|
schema = JSON.parse(Lutaml::Model::Schema.to_json(SchemaRepro::Outer))
|
|
75
75
|
one_of = schema["$defs"]["SchemaRepro_Outer"]["oneOf"]
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
expect(at_branch).to be_truthy
|
|
79
|
-
expect(inner["oneOf"].map { |b| b["required"] })
|
|
80
|
-
.to contain_exactly(%w[from], %w[to])
|
|
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])
|
|
81
78
|
end
|
|
82
79
|
end
|
|
83
80
|
|
|
84
81
|
describe "#865: independent choices combine conjunctively" do
|
|
85
|
-
it "renders an allOf of oneOf groups
|
|
82
|
+
it "renders an allOf of oneOf at-most-one groups" do
|
|
86
83
|
schema = JSON.parse(Lutaml::Model::Schema.to_json(SchemaRepro::Relation))
|
|
87
84
|
all_of = schema["$defs"]["SchemaRepro_Relation"]["allOf"]
|
|
88
85
|
expect(all_of.length).to eq(2)
|
|
89
86
|
all_of.each do |group|
|
|
90
87
|
expect(group).to have_key("oneOf")
|
|
91
|
-
group["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)
|
|
92
127
|
end
|
|
93
128
|
end
|
|
94
129
|
end
|
|
@@ -161,26 +161,7 @@ RSpec.describe Lutaml::Model::Schema::JsonSchema do
|
|
|
161
161
|
"type" => ["string", "null"],
|
|
162
162
|
},
|
|
163
163
|
},
|
|
164
|
-
|
|
165
|
-
{
|
|
166
|
-
"type" => "object",
|
|
167
|
-
"properties" => {
|
|
168
|
-
"email" => {
|
|
169
|
-
"type" => ["string", "null"],
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
"required" => ["email"],
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
"type" => "object",
|
|
176
|
-
"properties" => {
|
|
177
|
-
"phone" => {
|
|
178
|
-
"type" => ["string", "null"],
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
"required" => ["phone"],
|
|
182
|
-
},
|
|
183
|
-
],
|
|
164
|
+
|
|
184
165
|
},
|
|
185
166
|
},
|
|
186
167
|
}
|
|
@@ -136,23 +136,6 @@ RSpec.describe Lutaml::Model::Schema::YamlSchema do
|
|
|
136
136
|
type:
|
|
137
137
|
- string
|
|
138
138
|
- 'null'
|
|
139
|
-
anyOf:
|
|
140
|
-
- type: object
|
|
141
|
-
properties:
|
|
142
|
-
email:
|
|
143
|
-
type:
|
|
144
|
-
- string
|
|
145
|
-
- 'null'
|
|
146
|
-
required:
|
|
147
|
-
- email
|
|
148
|
-
- type: object
|
|
149
|
-
properties:
|
|
150
|
-
phone:
|
|
151
|
-
type:
|
|
152
|
-
- string
|
|
153
|
-
- 'null'
|
|
154
|
-
required:
|
|
155
|
-
- phone
|
|
156
139
|
YAML
|
|
157
140
|
end
|
|
158
141
|
|