schemacop 3.0.36 → 3.0.37
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/CHANGELOG.md +9 -0
- data/LICENSE +1 -1
- data/README.md +1 -1
- data/README_V3.md +125 -0
- data/VERSION +1 -1
- data/lib/schemacop/v3/hash_node.rb +100 -7
- data/schemacop.gemspec +3 -3
- data/test/unit/schemacop/v3/reference_node_test.rb +398 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 56a24074bd3792c20ee7e2f0bb1a8c3f4aa6d411a9a4c8010c9a52f0e828e6a3
|
|
4
|
+
data.tar.gz: ca4a752be493fe721122a77c5fb6844098c968dddf860fdcb0e33ef9a98880c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da3e3672206befe40e6487677c6c879192a3fdaf450e3621566be86dd1ccc9c487571a70cf7f1933294d5de9cd86094791e22cd659109a97baa432917138f296
|
|
7
|
+
data.tar.gz: 69d79cf5ad0702b80f930a9227f9279c2f758b3cb51965596145d82ec735b0fe8bb6648f219b6c1858d82f8a339ccdc388db71c3767573a074562a063eefcade
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
+
## 3.0.37 (2026-02-24)
|
|
4
|
+
|
|
5
|
+
* Add inline ref support for hash nodes via `ref! nil, :SchemaName`. This
|
|
6
|
+
unpacks the referenced schema's properties directly into the parent hash
|
|
7
|
+
instead of nesting them under a key. Produces `allOf` with `$ref` in the
|
|
8
|
+
JSON/Swagger output.
|
|
9
|
+
|
|
10
|
+
Internal reference: `#146962`.
|
|
11
|
+
|
|
3
12
|
## 3.0.36 (2026-01-05)
|
|
4
13
|
|
|
5
14
|
* Fix `v3_default_options` not being applied when schemas are eager loaded in
|
data/LICENSE
CHANGED
data/README.md
CHANGED
data/README_V3.md
CHANGED
|
@@ -1494,6 +1494,131 @@ schema.validate!([{first_name: 'Joe', last_name: 'Doe'}]) # => [{"first_name"=>"
|
|
|
1494
1494
|
schema.validate!([id: 42, first_name: 'Joe']) # => Schemacop::Exceptions::ValidationError: /[0]/last_name: Value must be given. /[0]: Obsolete property "id".
|
|
1495
1495
|
```
|
|
1496
1496
|
|
|
1497
|
+
#### Inline References
|
|
1498
|
+
|
|
1499
|
+
By passing `nil` as the name, you can "inline" a referenced schema into the
|
|
1500
|
+
parent hash. Instead of nesting the referenced properties under a key, they are
|
|
1501
|
+
unpacked directly into the parent:
|
|
1502
|
+
|
|
1503
|
+
```ruby
|
|
1504
|
+
schema = Schemacop::Schema3.new :hash do
|
|
1505
|
+
scm :BasicInfo do
|
|
1506
|
+
int! :id
|
|
1507
|
+
str! :name
|
|
1508
|
+
end
|
|
1509
|
+
|
|
1510
|
+
ref! nil, :BasicInfo
|
|
1511
|
+
str! :extra
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
# Properties from the referenced schema are validated at the top level
|
|
1515
|
+
schema.validate!({id: 1, name: 'John', extra: 'info'})
|
|
1516
|
+
# => {"id"=>1, "name"=>"John", "extra"=>"info"}
|
|
1517
|
+
|
|
1518
|
+
# Required properties from the ref are enforced
|
|
1519
|
+
schema.validate!({extra: 'info'})
|
|
1520
|
+
# => Schemacop::Exceptions::ValidationError: /id: Value must be given. /name: Value must be given.
|
|
1521
|
+
|
|
1522
|
+
# Unknown properties are still rejected
|
|
1523
|
+
schema.validate!({id: 1, name: 'John', extra: 'info', unknown: 'value'})
|
|
1524
|
+
# => Schemacop::Exceptions::ValidationError: /: Obsolete property "unknown".
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
Casting works as expected — values from the inline ref are cast according to the
|
|
1528
|
+
referenced schema's types:
|
|
1529
|
+
|
|
1530
|
+
```ruby
|
|
1531
|
+
schema = Schemacop::Schema3.new :hash do
|
|
1532
|
+
scm :BasicInfo do
|
|
1533
|
+
str! :born_at, format: :date
|
|
1534
|
+
str! :name
|
|
1535
|
+
end
|
|
1536
|
+
|
|
1537
|
+
ref! nil, :BasicInfo
|
|
1538
|
+
str! :extra
|
|
1539
|
+
end
|
|
1540
|
+
|
|
1541
|
+
result = schema.validate!({born_at: '1990-01-13', name: 'John', extra: 'info'})
|
|
1542
|
+
result[:born_at] # => Date<"Sat, 13 Jan 1990">
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
You can also use multiple inline refs in the same hash:
|
|
1546
|
+
|
|
1547
|
+
```ruby
|
|
1548
|
+
schema = Schemacop::Schema3.new :hash do
|
|
1549
|
+
scm :BasicInfo do
|
|
1550
|
+
int! :id
|
|
1551
|
+
str! :name
|
|
1552
|
+
end
|
|
1553
|
+
|
|
1554
|
+
scm :Timestamps do
|
|
1555
|
+
str! :created_at, format: :date
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
ref! nil, :BasicInfo
|
|
1559
|
+
ref! nil, :Timestamps
|
|
1560
|
+
str! :extra
|
|
1561
|
+
end
|
|
1562
|
+
|
|
1563
|
+
schema.validate!({id: 1, name: 'John', created_at: '2024-01-01', extra: 'info'})
|
|
1564
|
+
# => {"id"=>1, "name"=>"John", "created_at"=>Mon, 01 Jan 2024, "extra"=>"info"}
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
If a direct property has the same name as one from the inline ref, the direct
|
|
1568
|
+
property takes precedence:
|
|
1569
|
+
|
|
1570
|
+
```ruby
|
|
1571
|
+
schema = Schemacop::Schema3.new :hash do
|
|
1572
|
+
scm :BasicInfo do
|
|
1573
|
+
str! :name
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
ref! nil, :BasicInfo
|
|
1577
|
+
int! :name # Direct property takes precedence
|
|
1578
|
+
end
|
|
1579
|
+
|
|
1580
|
+
schema.validate!({name: 42}) # => {"name"=>42}
|
|
1581
|
+
schema.validate!({name: 'John'})
|
|
1582
|
+
# => Schemacop::Exceptions::ValidationError: /name: Invalid type, got type "String", expected "integer".
|
|
1583
|
+
```
|
|
1584
|
+
|
|
1585
|
+
In the JSON / Swagger output, inline refs produce an `allOf` array containing
|
|
1586
|
+
the `$ref` entries alongside the hash's own properties:
|
|
1587
|
+
|
|
1588
|
+
```ruby
|
|
1589
|
+
schema = Schemacop::Schema3.new :hash do
|
|
1590
|
+
scm :BasicInfo do
|
|
1591
|
+
int! :id
|
|
1592
|
+
str! :name
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
ref! nil, :BasicInfo
|
|
1596
|
+
str! :extra
|
|
1597
|
+
end
|
|
1598
|
+
|
|
1599
|
+
schema.as_json
|
|
1600
|
+
# => {
|
|
1601
|
+
# "allOf" => [
|
|
1602
|
+
# { "$ref" => "#/definitions/BasicInfo" },
|
|
1603
|
+
# {
|
|
1604
|
+
# "type" => "object",
|
|
1605
|
+
# "properties" => { "extra" => { "type" => "string" } },
|
|
1606
|
+
# "additionalProperties" => false,
|
|
1607
|
+
# "required" => ["extra"]
|
|
1608
|
+
# }
|
|
1609
|
+
# ],
|
|
1610
|
+
# "definitions" => {
|
|
1611
|
+
# "BasicInfo" => {
|
|
1612
|
+
# "properties" => { "id" => { "type" => "integer" }, "name" => { "type" => "string" } },
|
|
1613
|
+
# "additionalProperties" => false,
|
|
1614
|
+
# "required" => ["id", "name"],
|
|
1615
|
+
# "type" => "object"
|
|
1616
|
+
# }
|
|
1617
|
+
# },
|
|
1618
|
+
# "type" => "object"
|
|
1619
|
+
# }
|
|
1620
|
+
```
|
|
1621
|
+
|
|
1497
1622
|
## Context
|
|
1498
1623
|
|
|
1499
1624
|
Schemacop also features the concept of a `Context`. You can define schemas in a
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0.
|
|
1
|
+
3.0.37
|
|
@@ -12,6 +12,7 @@ module Schemacop
|
|
|
12
12
|
supports_children(name: true)
|
|
13
13
|
|
|
14
14
|
attr_reader :properties
|
|
15
|
+
attr_reader :inline_refs
|
|
15
16
|
|
|
16
17
|
def self.allowed_options
|
|
17
18
|
super + ATTRIBUTES - %i[dependencies] + %i[additional_properties ignore_obsolete_properties parse_json]
|
|
@@ -22,8 +23,13 @@ module Schemacop
|
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def add_child(node)
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
if node.name.nil?
|
|
27
|
+
if node.is_a?(ReferenceNode)
|
|
28
|
+
@inline_refs << node
|
|
29
|
+
return
|
|
30
|
+
else
|
|
31
|
+
fail Exceptions::InvalidSchemaError, 'Child nodes must have a name.'
|
|
32
|
+
end
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
@properties[node.name] = node
|
|
@@ -54,6 +60,10 @@ module Schemacop
|
|
|
54
60
|
end
|
|
55
61
|
end
|
|
56
62
|
|
|
63
|
+
if @inline_refs.any?
|
|
64
|
+
return as_json_with_inline_refs(properties, pattern_properties)
|
|
65
|
+
end
|
|
66
|
+
|
|
57
67
|
json = {}
|
|
58
68
|
json[:properties] = properties.values.map { |p| [p.name, p.as_json] }.to_h if properties.any?
|
|
59
69
|
json[:patternProperties] = pattern_properties.values.map { |p| [V3.sanitize_exp(p.name), p.as_json] }.to_h if pattern_properties.any?
|
|
@@ -122,8 +132,29 @@ module Schemacop
|
|
|
122
132
|
end
|
|
123
133
|
end
|
|
124
134
|
|
|
135
|
+
# Validate inline ref properties #
|
|
136
|
+
inline_ref_property_names = Set.new
|
|
137
|
+
|
|
138
|
+
@inline_refs.each do |inline_ref|
|
|
139
|
+
target = inline_ref.target
|
|
140
|
+
next unless target
|
|
141
|
+
|
|
142
|
+
target.properties.each_value do |prop|
|
|
143
|
+
next if prop.name.is_a?(Regexp)
|
|
144
|
+
next if @properties.key?(prop.name)
|
|
145
|
+
next if inline_ref_property_names.include?(prop.name)
|
|
146
|
+
|
|
147
|
+
inline_ref_property_names << prop.name
|
|
148
|
+
|
|
149
|
+
result.in_path(prop.name) do
|
|
150
|
+
result.error "Key #{prop.name} must be given." if prop.require_key? && !data_hash.include?(prop.name)
|
|
151
|
+
prop._validate(data_hash[prop.name], result: result)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
125
156
|
# Validate additional properties #
|
|
126
|
-
specified_properties = @properties.keys.to_set
|
|
157
|
+
specified_properties = @properties.keys.to_set + inline_ref_property_names
|
|
127
158
|
additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s) }
|
|
128
159
|
|
|
129
160
|
property_patterns = {}
|
|
@@ -173,7 +204,7 @@ module Schemacop
|
|
|
173
204
|
end
|
|
174
205
|
|
|
175
206
|
def children
|
|
176
|
-
@properties.values
|
|
207
|
+
@properties.values + @inline_refs
|
|
177
208
|
end
|
|
178
209
|
|
|
179
210
|
def cast(data)
|
|
@@ -208,8 +239,36 @@ module Schemacop
|
|
|
208
239
|
end
|
|
209
240
|
end
|
|
210
241
|
|
|
242
|
+
# Cast inline ref properties
|
|
243
|
+
inline_ref_property_names = Set.new
|
|
244
|
+
|
|
245
|
+
@inline_refs.each do |inline_ref|
|
|
246
|
+
target = inline_ref.target
|
|
247
|
+
next unless target
|
|
248
|
+
|
|
249
|
+
target.properties.each_value do |prop|
|
|
250
|
+
next if prop.name.is_a?(Regexp)
|
|
251
|
+
next if @properties.key?(prop.name)
|
|
252
|
+
next if inline_ref_property_names.include?(prop.name)
|
|
253
|
+
|
|
254
|
+
inline_ref_property_names << prop.name
|
|
255
|
+
|
|
256
|
+
prop_name = prop.as&.to_s || prop.name
|
|
257
|
+
|
|
258
|
+
casted_data = prop.cast(data_hash[prop.name])
|
|
259
|
+
|
|
260
|
+
if !casted_data.nil? || data_hash.include?(prop.name)
|
|
261
|
+
result[prop_name] = casted_data
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
if result[prop_name].nil? && !data_hash.include?(prop.name)
|
|
265
|
+
result.delete(prop_name)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
211
270
|
# Handle regex properties
|
|
212
|
-
specified_properties = @properties.keys.to_set
|
|
271
|
+
specified_properties = @properties.keys.to_set + inline_ref_property_names
|
|
213
272
|
additional_properties = data_hash.reject { |k, _v| specified_properties.include?(k.to_s.to_sym) }
|
|
214
273
|
|
|
215
274
|
if additional_properties.any? && property_patterns.any?
|
|
@@ -224,8 +283,8 @@ module Schemacop
|
|
|
224
283
|
if options[:additional_properties].is_a?(TrueClass)
|
|
225
284
|
result = data_hash.merge(result)
|
|
226
285
|
elsif options[:additional_properties].is_a?(Node)
|
|
227
|
-
|
|
228
|
-
additional_properties = data_hash.reject { |k, _v|
|
|
286
|
+
add_prop_specified = @properties.keys.to_set + inline_ref_property_names
|
|
287
|
+
additional_properties = data_hash.reject { |k, _v| add_prop_specified.include?(k.to_s.to_sym) }
|
|
229
288
|
if additional_properties.any?
|
|
230
289
|
additional_properties_result = {}
|
|
231
290
|
additional_properties.each do |key, value|
|
|
@@ -240,8 +299,42 @@ module Schemacop
|
|
|
240
299
|
|
|
241
300
|
protected
|
|
242
301
|
|
|
302
|
+
def as_json_with_inline_refs(properties, pattern_properties)
|
|
303
|
+
all_of = []
|
|
304
|
+
|
|
305
|
+
# Add each inline ref
|
|
306
|
+
@inline_refs.each do |inline_ref|
|
|
307
|
+
all_of << inline_ref.as_json
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Add own properties schema if any direct properties exist
|
|
311
|
+
if properties.any? || pattern_properties.any?
|
|
312
|
+
own_schema = {}
|
|
313
|
+
own_schema[:type] = :object
|
|
314
|
+
own_schema[:properties] = properties.values.map { |p| [p.name, p.as_json] }.to_h if properties.any?
|
|
315
|
+
own_schema[:patternProperties] = pattern_properties.values.map { |p| [V3.sanitize_exp(p.name), p.as_json] }.to_h if pattern_properties.any?
|
|
316
|
+
|
|
317
|
+
if options[:additional_properties].is_a?(TrueClass)
|
|
318
|
+
own_schema[:additionalProperties] = true
|
|
319
|
+
elsif options[:additional_properties].is_a?(Node)
|
|
320
|
+
own_schema[:additionalProperties] = options[:additional_properties].as_json
|
|
321
|
+
else
|
|
322
|
+
own_schema[:additionalProperties] = false
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
required_properties = @properties.values.select(&:required?).map(&:name)
|
|
326
|
+
own_schema[:required] = required_properties if required_properties.any?
|
|
327
|
+
|
|
328
|
+
all_of << own_schema
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
json = { allOf: all_of }
|
|
332
|
+
return process_json(ATTRIBUTES, json)
|
|
333
|
+
end
|
|
334
|
+
|
|
243
335
|
def init
|
|
244
336
|
@properties = {}
|
|
337
|
+
@inline_refs = []
|
|
245
338
|
@options[:type] = :object
|
|
246
339
|
unless @options[:additional_properties].nil? || @options[:additional_properties].is_a?(TrueClass) || @options[:additional_properties].is_a?(FalseClass)
|
|
247
340
|
fail Schemacop::Exceptions::InvalidSchemaError, 'Option "additional_properties" must be a boolean value'
|
data/schemacop.gemspec
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: schemacop 3.0.
|
|
2
|
+
# stub: schemacop 3.0.37 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "schemacop".freeze
|
|
6
|
-
s.version = "3.0.
|
|
6
|
+
s.version = "3.0.37".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
10
10
|
s.authors = ["Sitrox".freeze]
|
|
11
|
-
s.date = "2026-
|
|
11
|
+
s.date = "2026-02-24"
|
|
12
12
|
s.files = [".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, ".yardopts".freeze, "CHANGELOG.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "README_V2.md".freeze, "README_V3.md".freeze, "RUBY_VERSION".freeze, "Rakefile".freeze, "VERSION".freeze, "lib/schemacop.rb".freeze, "lib/schemacop/base_schema.rb".freeze, "lib/schemacop/exceptions.rb".freeze, "lib/schemacop/railtie.rb".freeze, "lib/schemacop/schema.rb".freeze, "lib/schemacop/schema2.rb".freeze, "lib/schemacop/schema3.rb".freeze, "lib/schemacop/scoped_env.rb".freeze, "lib/schemacop/v2.rb".freeze, "lib/schemacop/v2/caster.rb".freeze, "lib/schemacop/v2/collector.rb".freeze, "lib/schemacop/v2/dupper.rb".freeze, "lib/schemacop/v2/field_node.rb".freeze, "lib/schemacop/v2/node.rb".freeze, "lib/schemacop/v2/node_resolver.rb".freeze, "lib/schemacop/v2/node_supporting_field.rb".freeze, "lib/schemacop/v2/node_supporting_type.rb".freeze, "lib/schemacop/v2/node_with_block.rb".freeze, "lib/schemacop/v2/validator/array_validator.rb".freeze, "lib/schemacop/v2/validator/boolean_validator.rb".freeze, "lib/schemacop/v2/validator/float_validator.rb".freeze, "lib/schemacop/v2/validator/hash_validator.rb".freeze, "lib/schemacop/v2/validator/integer_validator.rb".freeze, "lib/schemacop/v2/validator/nil_validator.rb".freeze, "lib/schemacop/v2/validator/number_validator.rb".freeze, "lib/schemacop/v2/validator/object_validator.rb".freeze, "lib/schemacop/v2/validator/string_validator.rb".freeze, "lib/schemacop/v2/validator/symbol_validator.rb".freeze, "lib/schemacop/v3.rb".freeze, "lib/schemacop/v3/all_of_node.rb".freeze, "lib/schemacop/v3/any_of_node.rb".freeze, "lib/schemacop/v3/array_node.rb".freeze, "lib/schemacop/v3/boolean_node.rb".freeze, "lib/schemacop/v3/combination_node.rb".freeze, "lib/schemacop/v3/context.rb".freeze, "lib/schemacop/v3/dsl_scope.rb".freeze, "lib/schemacop/v3/global_context.rb".freeze, "lib/schemacop/v3/hash_node.rb".freeze, "lib/schemacop/v3/integer_node.rb".freeze, "lib/schemacop/v3/is_not_node.rb".freeze, "lib/schemacop/v3/node.rb".freeze, "lib/schemacop/v3/node_registry.rb".freeze, "lib/schemacop/v3/number_node.rb".freeze, "lib/schemacop/v3/numeric_node.rb".freeze, "lib/schemacop/v3/object_node.rb".freeze, "lib/schemacop/v3/one_of_node.rb".freeze, "lib/schemacop/v3/reference_node.rb".freeze, "lib/schemacop/v3/result.rb".freeze, "lib/schemacop/v3/string_node.rb".freeze, "lib/schemacop/v3/symbol_node.rb".freeze, "schemacop.gemspec".freeze, "test/lib/test_helper.rb".freeze, "test/schemas/nested/group.rb".freeze, "test/schemas/user.rb".freeze, "test/unit/schemacop/v2/casting_test.rb".freeze, "test/unit/schemacop/v2/collector_test.rb".freeze, "test/unit/schemacop/v2/custom_check_test.rb".freeze, "test/unit/schemacop/v2/custom_if_test.rb".freeze, "test/unit/schemacop/v2/defaults_test.rb".freeze, "test/unit/schemacop/v2/empty_test.rb".freeze, "test/unit/schemacop/v2/nil_dis_allow_test.rb".freeze, "test/unit/schemacop/v2/node_resolver_test.rb".freeze, "test/unit/schemacop/v2/short_forms_test.rb".freeze, "test/unit/schemacop/v2/types_test.rb".freeze, "test/unit/schemacop/v2/validator_array_test.rb".freeze, "test/unit/schemacop/v2/validator_boolean_test.rb".freeze, "test/unit/schemacop/v2/validator_float_test.rb".freeze, "test/unit/schemacop/v2/validator_hash_test.rb".freeze, "test/unit/schemacop/v2/validator_integer_test.rb".freeze, "test/unit/schemacop/v2/validator_nil_test.rb".freeze, "test/unit/schemacop/v2/validator_number_test.rb".freeze, "test/unit/schemacop/v2/validator_object_test.rb".freeze, "test/unit/schemacop/v2/validator_string_test.rb".freeze, "test/unit/schemacop/v2/validator_symbol_test.rb".freeze, "test/unit/schemacop/v3/all_of_node_test.rb".freeze, "test/unit/schemacop/v3/any_of_node_test.rb".freeze, "test/unit/schemacop/v3/array_node_test.rb".freeze, "test/unit/schemacop/v3/boolean_node_test.rb".freeze, "test/unit/schemacop/v3/global_context_test.rb".freeze, "test/unit/schemacop/v3/hash_node_test.rb".freeze, "test/unit/schemacop/v3/integer_node_test.rb".freeze, "test/unit/schemacop/v3/is_not_node_test.rb".freeze, "test/unit/schemacop/v3/node_test.rb".freeze, "test/unit/schemacop/v3/number_node_test.rb".freeze, "test/unit/schemacop/v3/object_node_test.rb".freeze, "test/unit/schemacop/v3/one_of_node_test.rb".freeze, "test/unit/schemacop/v3/reference_node_test.rb".freeze, "test/unit/schemacop/v3/string_node_test.rb".freeze, "test/unit/schemacop/v3/symbol_node_test.rb".freeze]
|
|
13
13
|
s.homepage = "https://github.com/sitrox/schemacop".freeze
|
|
14
14
|
s.licenses = ["MIT".freeze]
|
|
@@ -362,6 +362,404 @@ module Schemacop
|
|
|
362
362
|
|
|
363
363
|
assert_cast({ person: { born_at: '1990-01-13' } }, { person: { born_at: Date.new(1990, 1, 13) } }.with_indifferent_access)
|
|
364
364
|
end
|
|
365
|
+
|
|
366
|
+
# --- Inline ref tests ---
|
|
367
|
+
|
|
368
|
+
def test_inline_ref_schema_builds_without_error
|
|
369
|
+
schema do
|
|
370
|
+
scm :BasicInfo do
|
|
371
|
+
int! :id
|
|
372
|
+
str! :name
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
ref! nil, :BasicInfo
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
assert_equal [], @schema.root.properties.keys
|
|
379
|
+
assert_equal 1, @schema.root.inline_refs.size
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def test_inline_ref_validation
|
|
383
|
+
schema do
|
|
384
|
+
scm :BasicInfo do
|
|
385
|
+
int! :id
|
|
386
|
+
str! :name
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
ref! nil, :BasicInfo
|
|
390
|
+
str! :extra
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
assert_validation(id: 1, name: 'John', extra: 'info')
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def test_inline_ref_validation_errors
|
|
397
|
+
schema do
|
|
398
|
+
scm :BasicInfo do
|
|
399
|
+
int! :id
|
|
400
|
+
str! :name
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
ref! nil, :BasicInfo
|
|
404
|
+
str! :extra
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
assert_validation(extra: 'info') do
|
|
408
|
+
error '/id', 'Value must be given.'
|
|
409
|
+
error '/name', 'Value must be given.'
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def test_inline_ref_validation_type_errors
|
|
414
|
+
schema do
|
|
415
|
+
scm :BasicInfo do
|
|
416
|
+
int! :id
|
|
417
|
+
str! :name
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
ref! nil, :BasicInfo
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
assert_validation(id: 'not_an_int', name: 42) do
|
|
424
|
+
error '/id', 'Invalid type, got type "String", expected "integer".'
|
|
425
|
+
error '/name', 'Invalid type, got type "Integer", expected "string".'
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def test_inline_ref_validation_obsolete_properties
|
|
430
|
+
schema do
|
|
431
|
+
scm :BasicInfo do
|
|
432
|
+
int! :id
|
|
433
|
+
str! :name
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
ref! nil, :BasicInfo
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Properties from inline ref are accepted
|
|
440
|
+
assert_validation(id: 1, name: 'John')
|
|
441
|
+
|
|
442
|
+
# Unknown properties are still rejected
|
|
443
|
+
assert_validation(id: 1, name: 'John', unknown: 'value') do
|
|
444
|
+
error '/', 'Obsolete property "unknown".'
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def test_inline_ref_cast
|
|
449
|
+
schema do
|
|
450
|
+
scm :BasicInfo do
|
|
451
|
+
str! :born_at, format: :date
|
|
452
|
+
str! :name
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
ref! nil, :BasicInfo
|
|
456
|
+
str! :extra
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
assert_cast(
|
|
460
|
+
{ born_at: '1990-01-13', name: 'John', extra: 'info' },
|
|
461
|
+
{ born_at: Date.new(1990, 1, 13), name: 'John', extra: 'info' }.with_indifferent_access
|
|
462
|
+
)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def test_inline_ref_with_defaults
|
|
466
|
+
schema do
|
|
467
|
+
scm :BasicInfo do
|
|
468
|
+
str? :name, default: 'Anonymous'
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
ref! nil, :BasicInfo
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
assert_cast({}, { name: 'Anonymous' }.with_indifferent_access)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def test_inline_ref_with_optional_properties
|
|
478
|
+
schema do
|
|
479
|
+
scm :BasicInfo do
|
|
480
|
+
int! :id
|
|
481
|
+
str? :nickname
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
ref! nil, :BasicInfo
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
assert_validation(id: 1)
|
|
488
|
+
assert_validation(id: 1, nickname: 'Johnny')
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def test_inline_ref_as_json
|
|
492
|
+
schema do
|
|
493
|
+
scm :BasicInfo do
|
|
494
|
+
int! :id
|
|
495
|
+
str! :name
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
ref! nil, :BasicInfo
|
|
499
|
+
str! :extra
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
assert_json({
|
|
503
|
+
definitions: {
|
|
504
|
+
BasicInfo: {
|
|
505
|
+
properties: {
|
|
506
|
+
id: { type: :integer },
|
|
507
|
+
name: { type: :string }
|
|
508
|
+
},
|
|
509
|
+
additionalProperties: false,
|
|
510
|
+
required: %w[id name],
|
|
511
|
+
type: :object
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
type: :object,
|
|
515
|
+
allOf: [
|
|
516
|
+
{ '$ref' => '#/definitions/BasicInfo' },
|
|
517
|
+
{
|
|
518
|
+
type: :object,
|
|
519
|
+
properties: {
|
|
520
|
+
extra: { type: :string }
|
|
521
|
+
},
|
|
522
|
+
additionalProperties: false,
|
|
523
|
+
required: %w[extra]
|
|
524
|
+
}
|
|
525
|
+
]
|
|
526
|
+
})
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def test_inline_ref_as_json_no_own_properties
|
|
530
|
+
schema do
|
|
531
|
+
scm :BasicInfo do
|
|
532
|
+
int! :id
|
|
533
|
+
str! :name
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
ref! nil, :BasicInfo
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
assert_json({
|
|
540
|
+
definitions: {
|
|
541
|
+
BasicInfo: {
|
|
542
|
+
properties: {
|
|
543
|
+
id: { type: :integer },
|
|
544
|
+
name: { type: :string }
|
|
545
|
+
},
|
|
546
|
+
additionalProperties: false,
|
|
547
|
+
required: %w[id name],
|
|
548
|
+
type: :object
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
type: :object,
|
|
552
|
+
allOf: [
|
|
553
|
+
{ '$ref' => '#/definitions/BasicInfo' }
|
|
554
|
+
]
|
|
555
|
+
})
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def test_inline_ref_swagger_json
|
|
559
|
+
schema do
|
|
560
|
+
scm :BasicInfo do
|
|
561
|
+
int! :id
|
|
562
|
+
str! :name
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
ref! nil, :BasicInfo
|
|
566
|
+
str! :extra
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
assert_swagger_json({
|
|
570
|
+
type: :object,
|
|
571
|
+
allOf: [
|
|
572
|
+
{ '$ref' => '#/components/schemas/BasicInfo' },
|
|
573
|
+
{
|
|
574
|
+
type: :object,
|
|
575
|
+
properties: {
|
|
576
|
+
extra: { type: :string }
|
|
577
|
+
},
|
|
578
|
+
additionalProperties: false,
|
|
579
|
+
required: %w[extra]
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
})
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def test_inline_ref_multiple
|
|
586
|
+
schema do
|
|
587
|
+
scm :BasicInfo do
|
|
588
|
+
int! :id
|
|
589
|
+
str! :name
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
scm :Timestamps do
|
|
593
|
+
str! :created_at, format: :date
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
ref! nil, :BasicInfo
|
|
597
|
+
ref! nil, :Timestamps
|
|
598
|
+
str! :extra
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# Validation
|
|
602
|
+
assert_validation(id: 1, name: 'John', created_at: '2024-01-01', extra: 'info')
|
|
603
|
+
assert_validation(extra: 'info') do
|
|
604
|
+
error '/id', 'Value must be given.'
|
|
605
|
+
error '/name', 'Value must be given.'
|
|
606
|
+
error '/created_at', 'Value must be given.'
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# Casting
|
|
610
|
+
assert_cast(
|
|
611
|
+
{ id: 1, name: 'John', created_at: '2024-01-01', extra: 'info' },
|
|
612
|
+
{ id: 1, name: 'John', created_at: Date.new(2024, 1, 1), extra: 'info' }.with_indifferent_access
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
# JSON
|
|
616
|
+
assert_json({
|
|
617
|
+
definitions: {
|
|
618
|
+
BasicInfo: {
|
|
619
|
+
properties: {
|
|
620
|
+
id: { type: :integer },
|
|
621
|
+
name: { type: :string }
|
|
622
|
+
},
|
|
623
|
+
additionalProperties: false,
|
|
624
|
+
required: %w[id name],
|
|
625
|
+
type: :object
|
|
626
|
+
},
|
|
627
|
+
Timestamps: {
|
|
628
|
+
properties: {
|
|
629
|
+
created_at: { type: :string, format: :date }
|
|
630
|
+
},
|
|
631
|
+
additionalProperties: false,
|
|
632
|
+
required: %w[created_at],
|
|
633
|
+
type: :object
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
type: :object,
|
|
637
|
+
allOf: [
|
|
638
|
+
{ '$ref' => '#/definitions/BasicInfo' },
|
|
639
|
+
{ '$ref' => '#/definitions/Timestamps' },
|
|
640
|
+
{
|
|
641
|
+
type: :object,
|
|
642
|
+
properties: {
|
|
643
|
+
extra: { type: :string }
|
|
644
|
+
},
|
|
645
|
+
additionalProperties: false,
|
|
646
|
+
required: %w[extra]
|
|
647
|
+
}
|
|
648
|
+
]
|
|
649
|
+
})
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def test_inline_ref_with_external_schema
|
|
653
|
+
context = Context.new
|
|
654
|
+
|
|
655
|
+
context.schema :BasicInfo do
|
|
656
|
+
int! :id
|
|
657
|
+
str! :name
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
with_context context do
|
|
661
|
+
schema do
|
|
662
|
+
ref! nil, :BasicInfo
|
|
663
|
+
str! :extra
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
assert_validation(id: 1, name: 'John', extra: 'info')
|
|
667
|
+
assert_validation(extra: 'info') do
|
|
668
|
+
error '/id', 'Value must be given.'
|
|
669
|
+
error '/name', 'Value must be given.'
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def test_inline_ref_used_external_schemas
|
|
675
|
+
context = Context.new
|
|
676
|
+
|
|
677
|
+
context.schema :BasicInfo do
|
|
678
|
+
int! :id
|
|
679
|
+
str! :name
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
with_context context do
|
|
683
|
+
schema do
|
|
684
|
+
ref! nil, :BasicInfo
|
|
685
|
+
str! :extra
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
assert_equal %i[BasicInfo], @schema.root.used_external_schemas
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def test_inline_ref_with_additional_properties_true
|
|
693
|
+
schema do
|
|
694
|
+
scm :BasicInfo do
|
|
695
|
+
int! :id
|
|
696
|
+
str! :name
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
ref! nil, :BasicInfo
|
|
700
|
+
str! :extra
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# Without additional_properties: true, unknown props are rejected
|
|
704
|
+
assert_validation(id: 1, name: 'John', extra: 'info', unknown: 'value') do
|
|
705
|
+
error '/', 'Obsolete property "unknown".'
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def test_inline_ref_property_name_collision
|
|
710
|
+
schema do
|
|
711
|
+
scm :BasicInfo do
|
|
712
|
+
str! :name
|
|
713
|
+
str? :description
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
ref! nil, :BasicInfo
|
|
717
|
+
int! :name # Direct property takes precedence
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# Direct property (int!) takes precedence — string should fail
|
|
721
|
+
assert_validation(name: 42)
|
|
722
|
+
assert_validation(name: 'John') do
|
|
723
|
+
error '/name', 'Invalid type, got type "String", expected "integer".'
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
# Optional description from inline ref still works
|
|
727
|
+
assert_validation(name: 42, description: 'A description')
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def test_inline_ref_collision_between_inline_refs
|
|
731
|
+
schema do
|
|
732
|
+
scm :BasicInfo do
|
|
733
|
+
str! :name
|
|
734
|
+
str? :description
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
scm :ExtraInfo do
|
|
738
|
+
int! :name # Clashes with BasicInfo's :name
|
|
739
|
+
str! :extra
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
ref! nil, :BasicInfo # First inline ref wins for :name
|
|
743
|
+
ref! nil, :ExtraInfo
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
# First inline ref wins: :name is validated as str! (from BasicInfo)
|
|
747
|
+
assert_validation(name: 'John', extra: 'info')
|
|
748
|
+
assert_validation(name: 42, extra: 'info') do
|
|
749
|
+
error '/name', 'Invalid type, got type "Integer", expected "string".'
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
# Properties unique to second inline ref still work
|
|
753
|
+
assert_validation(name: 'John') do
|
|
754
|
+
error '/extra', 'Value must be given.'
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Casting uses first inline ref's definition for :name
|
|
758
|
+
assert_cast(
|
|
759
|
+
{ name: 'John', description: 'A desc', extra: 'info' },
|
|
760
|
+
{ name: 'John', description: 'A desc', extra: 'info' }.with_indifferent_access
|
|
761
|
+
)
|
|
762
|
+
end
|
|
365
763
|
end
|
|
366
764
|
end
|
|
367
765
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schemacop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.37
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sitrox
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-02-24 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|