Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 44 additions & 15 deletions lib/rspec/openapi/schema_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,22 +270,38 @@
elsif property_variations.size == 1
merged_schema[:properties][key] = property_variations.first.dup
else
unique_types = property_variations.map { |p| p[:type] }.compact.uniq
has_one_of = property_variations.any? { |p| p.key?(:oneOf) }

if has_one_of
all_options = []
property_variations.each do |prop|
clean_prop = prop.reject { |k, _| k == :nullable }
if clean_prop.key?(:oneOf)
all_options.concat(clean_prop[:oneOf])
else
all_options << clean_prop unless clean_prop.empty?
end
end

if unique_types.size > 1
# Different types detected - create oneOf
unique_props = property_variations.map { |p| p.reject { |k, _| k == :nullable } }.uniq
merged_schema[:properties][key] = { oneOf: unique_props }
all_options.uniq!
merged_schema[:properties][key] = { oneOf: all_options }
else
case unique_types.first
when 'array'
merged_schema[:properties][key] = { type: 'array' }
items_variations = property_variations.map { |p| p[:items] }.compact
merged_schema[:properties][key][:items] = build_merged_schema_from_variations(items_variations)
when 'object'
merged_schema[:properties][key] = build_merged_schema_from_variations(property_variations)
unique_types = property_variations.map { |p| p[:type] }.compact.uniq

if unique_types.size > 1
unique_props = property_variations.map { |p| p.reject { |k, _| k == :nullable } }.uniq
merged_schema[:properties][key] = { oneOf: unique_props }
else
merged_schema[:properties][key] = property_variations.first.dup
case unique_types.first

Check notice

Code scanning / Rubocop

Avoid excessive block nesting. Note

Metrics/BlockNesting: Avoid more than 3 levels of block nesting.
when 'array'
merged_schema[:properties][key] = { type: 'array' }
items_variations = property_variations.map { |p| p[:items] }.compact
merged_schema[:properties][key][:items] = build_merged_schema_from_variations(items_variations)
when 'object'
merged_schema[:properties][key] = build_merged_schema_from_variations(property_variations)
else
merged_schema[:properties][key] = property_variations.first.dup
end
end
end
end
Expand Down Expand Up @@ -324,8 +340,21 @@
merged[:properties][key][:nullable] = true if has_nullable
elsif prop_variations.size > 1
prop_types = prop_variations.map { |p| p[:type] }.compact.uniq

if prop_types.size == 1
has_one_of = prop_variations.any? { |p| p.key?(:oneOf) }

if has_one_of
all_options = []
prop_variations.each do |prop|
clean_prop = prop.reject { |k, _| k == :nullable }
if clean_prop.key?(:oneOf)

Check notice

Code scanning / Rubocop

Avoid excessive block nesting. Note

Metrics/BlockNesting: Avoid more than 3 levels of block nesting.
all_options.concat(clean_prop[:oneOf])
else
all_options << clean_prop unless clean_prop.empty?
end
end
all_options.uniq!
merged[:properties][key] = { oneOf: all_options }
elsif prop_types.size == 1
# Only recursively merge if it's an object type
merged[:properties][key] = if prop_types.first == 'object'
build_merged_schema_from_variations(prop_variations)
Expand Down
21 changes: 20 additions & 1 deletion spec/apps/hanami/app/actions/array_hashes/mixed_types_nested.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,26 @@ def handle(request, response)
"ssl" => true
},
"form" => nil
}
},
{
"id" => 3,
"config" => {
"port" => "9010",
"host" => "foo.example.com",
"ssl" => true
},
"form" => [
{
"value" => false,
},
{
"value" => ['First', 'Second'],
},
{
"value" => 3,
},
]
},
]
}.to_json
end
Expand Down
105 changes: 105 additions & 0 deletions spec/apps/hanami/app/actions/array_hashes/multiple_one_of_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# frozen_string_literal: true

module HanamiTest
module Actions
module ArrayHashes
class MultipleOneOfTest < HanamiTest::Action
def handle(request, response)
response.format = :json

response.body = {
"data" => {
"form" => [
{
"inputs" => [
{
"value" => 'John Doe',
},
{
"value" =>
'[email protected]',
},
{
"value" => 'In progress',
},
{
"value" => '2025-12-11T06:25:20.770+00:00',
},
],
},
{
"inputs" => [
{
"value" => nil,
},
{
"value" => 'user_1',
},
{
"value" => 'user_2',
},
],
},
{
"inputs" => [
{
"value" => false,
},
{
"value" => 'Some organisation',
},
{
"value" => 'organisation_1',
},
{
"value" => [
'organisation_1',
'organisation_2',
'organisation_3',
],
},
],
},
{
"inputs" => [
{
"value" => 'Initialized',
},
{
"value" => 'Initialized',
},
{
"value" => 'Initialized',
},
{
"value" => 'Initialized',
},
{
"value" => nil,
},
],
},
{
"inputs" => [
{
"value" => nil,
},
{
"value" => nil,
},
{
"value" => nil,
},
{
"value" => nil,
},
],
},
],
},
}.to_json
end
end
end
end
end
1 change: 1 addition & 0 deletions spec/apps/hanami/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Routes < Hanami::Routes
get '/array_hashes/nested_arrays', to: 'array_hashes.nested_arrays'
get '/array_hashes/nested_objects', to: 'array_hashes.nested_objects'
get '/array_hashes/mixed_types_nested', to: 'array_hashes.mixed_types_nested'
get '/array_hashes/multiple_one_of_test', to: 'array_hashes.multiple_one_of_test'

get '/test_block', to: ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['A TEST']] }

Expand Down
Loading